Überprüfen Sie, ob $ REPLY in einem Zahlenbereich liegt

30

Ich schreibe ein Shell-Skript für Linux und verwende Bash, um jede Videodatei in ein MP4-Format zu übersetzen. Dafür verwende ich avconvmit libvorbisfür Audio.

In meinem Skript habe ich eine Frage an den Benutzer:

read -p "- Audio Quality [scale from -2 to 10] ? "
    if [ -n "$REPLY" ] ; then
    ABITRATE="-aq $REPLY"
    fi

Meine "ABITRATE" Zeichenfolge wird in die endgültige avconvBefehlszeile eingegeben .

Ich möchte dem Benutzer jedoch die Möglichkeit geben, diese Frage mit einem Wert in Kb (Kilobit) zu beantworten und in die verwendete Skala zu übersetzen libvorbis. Die "Skala von -2 bis 10" lautet wie folgt:

Quality Kbit/s  Normalization
-----------------------------
 -2      ~32        y
 -1      ~48        y
  0      ~64        y
  1      ~80        y
  2      ~96        y
  3     ~112        y
  4     ~128        n
  5     ~160        n
  6     ~192        n
  7     ~224        n
  8     ~256        n
  9     ~320        n
 10     ~500        n

Ich möchte wissen, wie ich überprüfen kann, ob sich mein $ REPLY in einem Zahlenbereich befindet. Ich möchte beispielsweise, dass mein Skript Folgendes ausführt:

if [ $REPLY is a number between 1 and 32 ] ; then 
 REPLY="-2"
elif [ $REPLY is a number between 33 and 48 ] ; then 
 REPLY="-1"
fi

Ist dies möglich (ich bin bereit zu sagen, "Ja, sollte nicht schwer sein", aber ich kenne die zu verwendende Syntax nicht)?

MrVaykadji
quelle
AFAIK, Vorbis ist kein gültiger Audio-Codec in einer MP4-Datei (Sie möchten AAC oder möglicherweise MP3 verwenden) ...
Evilsoup
Vielen Dank, es hat in VLC gut funktioniert, aber Totem will es nicht lesen. Ich wechsle zu libvo_aacenc
MrVaykadji

Antworten:

30

Der [Befehl / die eingebaute Shell verfügt über Vergleichstests, so dass Sie dies einfach tun können

if [ "$REPLY" -ge 1 -a "$REPLY" -le 32 ]; then REPLY=-2;
elif [ "$REPLY" -ge 33 -a "$REPLY" -le 48 ]; then REPLY=-1; fi

wo -gebedeutet größer oder gleich (und so weiter). Das -aist logisch "und". Der [Befehl ist nur ein Befehl, keine spezielle Syntax (es ist eigentlich dasselbe wie test: check out man test), daher BRAUCHT er den Raum danach. Wenn Sie schreiben [$REPLY, wird versucht, einen Befehl mit dem Namen zu finden [$REPLYund auszuführen, was nicht funktioniert. Gleiches gilt für das Schließen ].

Bearbeiten: Um zu testen, ob die Zahl eine Ganzzahl ist (falls dies in Ihrem Code vorkommen kann), führen Sie zuerst den Test durch

if [[ "$REPLY" =~ ^[0-9]+$ ]]; then
   existing code
else echo "$REPLY is not an integer" >&2 && exit 1; fi

Natürlich geben alle diese Klammerausdrücke 0 (wahr) oder 1 (falsch) zurück und können kombiniert werden. Sie können nicht nur alles in dieselbe Klammer setzen, sondern auch

if [[ "$REPLY" =~ ^[0-9]+$ ]] && [ "$REPLY" -ge 1 -a "$REPLY" -le 32 ]; then ...

oder etwas ähnliches.

orion
quelle
Genau das, wonach ich gesucht habe, danke! Könnte ich stattdessen einen einfachen Vergleichsausdruck wie verwenden >=?
MrVaykadji
Bash erlaubt viele Arten von Klammern zum Testen. Sie haben diese traditionellen [Klammern, die wie in gezeigt funktionieren man test. Diese sind traditionell und narrensicher. Dann haben Sie viele Bash-Buildins. Sie haben [[ähnliche, aber nicht genau dieselben, da diese Pfadnamen nicht erweitern (dort bedeuten <=> Zeichenfolgenvergleiche und Ganzzahlvergleiche dasselbe wie in [). Beide haben auch eine Menge Tests für die Existenz von Dateien, Berechtigungen und so weiter. Dann haben Sie in @ devnulls Antwort single (und double ((verwendet. Check out man bashunter Compound Commands.
Orion
1
@ MrVaykadji Ich empfehle dringend, dass Sie auch testen, ob die Variable eine Zahl ist. Andernfalls erhalten Sie möglicherweise unerwartete Ergebnisse:foo='a'; [[ "$foo" -lt 32 ]] && echo yes
terdon
12

Man könnte einfach sagen:

((REPLY>=1 && REPLY<=32)) && REPLY=-2
((REPLY>=33 && REPLY<=48)) && REPLY=-1

Zitat aus dem Handbuch :

((...))

(( expression ))

Der arithmetische Ausdruck wird nach den unten beschriebenen Regeln ausgewertet (siehe Shell-Arithmetik ). Wenn der Wert des Ausdrucks nicht Null ist, ist der Rückgabestatus 0; Andernfalls lautet der Rückgabestatus 1. Dies entspricht genau

let "expression"
devnull
quelle
Ich mag die Einfachheit, aber was sind die ((? Ich habe versucht, sie sofort zu verwenden, und es scheint zu funktionieren, if [ ] ; thenaber ich wusste nicht, dass es sie gibt.
MrVaykadji
@MrVaykadji Ein Verweis aus dem Handbuch wurde hinzugefügt. Lassen Sie mich wissen, wenn es nicht klar ist.
Devnull
1
@ MrVaykadji Darüber hinaus ist Sprechen if [ condition ]; then foo; figleichbedeutend mit Sprechen condition && foo.
Devnull
Okay gut ! Ich würde gerne beide Ihre Antworten (Orion und Sie) annehmen, wenn ich könnte. Vielen Dank für all das, ich habe viel gelernt.
MrVaykadji
Möglicherweise möchten Sie führende Nullen entfernen, wenn Sie dies verwenden. a=08; (( a > 1 ))wird fehler da 08 als oktal gilt. Sie können auch die Dezimalzahl mit erzwingen 10#$REPLY. cmd && cmdist nicht ganz das Gleiche wie if cmd; then ...Wenn Sie ein elseTeil benötigen , wird die Verkettung logisch &&und ||kann subtile Fehler verursachen.
llua
4

Sie könnten so etwas tun:

#!/usr/bin/env bash
read -p "- Audio Quality [scale from -2 to 10] ? "
if [ -n "$REPLY" ] ; then
    ABITRATE="-aq $REPLY"
fi

echo "You chose : $ABITRATE : $REPLY"
## If 0 < $REPLY < 33 and $REPLY is a number
if [[ "$REPLY" -gt 0 && "$REPLY" -lt 33 && "$REPLY" =~ '^[0-9]$' ]]
then
    echo "GOOD"
else
    echo "BAD"
fi
terdon
quelle
2

Testen Sie zunächst, ob die Eingabe numerisch ist. Verwenden Sie beispielsweise den Übereinstimmungsoperator für reguläre Ausdrücke für bedingte Bash-Ausdrücke :

if [[ $REPLY =~ -?[0-9]+ ]]; then
  echo "Invalid input (not numeric): $REPLY"
  exit 2
fi

Um numerische Bereiche zu testen, haben Sie zwei Möglichkeiten:

  • der -gtOperator für bedingte Ausdrücke innerhalb von [ … ]oder [[ … ]](beachten Sie, dass der Operator <und >einen Zeichenfolgenvergleich durchführen, nicht einen Vergleich numerischer Werte, also [[ 10 < 9 ]]ist dies wahr);
  • die üblichen arithmetischen Operatoren im Inneren ((…)).

Somit:

if ((REPLY >= -2 && REPLY <= 10)); then
  : # do nothing -- pass directly to libvorbis
elif ((REPLY <= 24)); then
  echo "Value outside supported range: $REPLY"
  exit 2
elif ((REPLY <= 135)); then
  REPLY=$(((REPLY+8) / 16 - 4))
elif ((REPLY <= 271)); then
  REPLY=$(((REPLY+16) / 32))
elif ((REPLY <= 400)); then
  REPLY=9
elif ((REPLY <= 707)); then
  REPLY=10
else
  echo "Value outside supported range: $REPLY"
  exit 2
fi

(Möglicherweise möchten Sie andere Approximationsregeln verwenden. Ich weiß nicht, ob die von mir ausgewählten hier die besten sind.)

Gilles 'SO - hör auf böse zu sein'
quelle
1

Um richtig zu erkennen, ob eine Zeichenfolge eine (Dezimal-) Zahl ist, müssen Sie zunächst definieren, was eine dezimale Ganzzahl ist. Eine einfache und doch recht vollständige Definition lautet:

Eine Folge von optionalen Zeichen (+ oder -) gefolgt von maximal 18 (signifikanten) Dezimalstellen.

Und diese Schritte sind nötig:

  1. Entfernen Sie alle Zeichen, die keine Dezimalstellen sind (nach dem Vorzeichen).
  2. Entfernen Sie alle optionalen führenden Nullen. Führende Nullen lassen die Shell glauben, dass die Zahl oktal ist.
  3. Begrenzen Sie die maximale Größe der Ganzzahl auf 18 Stellen. Unter 2 ** 63-1 (max. 64-Bit-Ganzzahl).

Nur ein regulärer Ausdruck erledigt das meiste:

re='^([+-])?0*([0-9]{1,18})$'
[[ $number =~ $re ]] && integer=${BASH_REMATCH[*]:1}

Der Code zum Verarbeiten mehrerer Zahlen lautet:

#!/bin/bash
DebugLevel=4     # 1:fatal 2:error 3:warn 4:info 5:debug 6:trace

SayMsg    (){   local a; a=$1; shift ;            # Log level
                [[ $a -le $DebugLevel ]] && printf '%s' "$@" $'\n' >&2 ;
            }
SayError  (){   a=$1; shift; printf '%s' "$@" $'\n' >&2; exit   "$a";   }

parseint  (){   local re # Parse the first argument as an integer or fail
                re='^([+-])?0*([0-9]{1,18})$'
                [[ $1 =~ $re ]] || { SayMsg 4 "Invalid number $1"; return 2; }
                integer=${BASH_REMATCH[1]}${BASH_REMATCH[2]}
                echo "integer=$integer"
             }

while read val; do
    parseint "$val"
    done <<-\_EOT_
    0
    1
    10
    100
    2345
    123456789012345678
    923456789012345678
    999999999999999999
    0000000012345
    +023
    -00045
    -76
    ""
    ''
    a
    abc
    1234567890123456789
    7.23
    -8.17
    1e3
    10+11
    _EOT_

Welches wird drucken:

integer=0
integer=1
integer=10
integer=100
integer=2345
integer=123456789012345678
integer=923456789012345678
integer=999999999999999999
integer=12345
integer=+23
integer=-45
integer=-76
Invalid number ""
Invalid number ''
Invalid number 
Invalid number a
Invalid number abc
Invalid number 1234567890123456789
Invalid number 7.23
Invalid number -8.17
Invalid number 1e3
Invalid number 10+11

Sobald die Zahl sauber und klar ist, besteht der einzige fehlende Test darin, den Wertebereich zu begrenzen. Diese einfachen Zeilen machen das:

(( 1  <= integer && integer <= 32 )) && REPLY="-2"
(( 33 <= integer && integer <= 48 )) && REPLY="-1"
Isaac
quelle