web-dev-qa-db-fra.com

Vérifiez si $ REPLY est dans une plage de nombres

J'écris un script Shell pour Linux, en utilisant Bash, pour traduire n'importe quel fichier vidéo en MP4. Pour cela, j'utilise avconv avec libvorbis pour l'audio.

Dans mon script, j'ai une question pour l'utilisateur:

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

Ma chaîne "ABITRATE" va dans la dernière ligne de commande avconv.

Mais je voudrais donner à l'utilisateur la possibilité de répondre à cette question avec une valeur en Kb (Kilobit), et de la traduire dans l'échelle que libvorbis utilise. L'échelle " de -2 à 10 "est la suivante:

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

Je voudrais savoir comment vérifier si ma $ REPLY est dans une plage de nombres. Par exemple, j'aimerais que mon script fasse quelque chose comme ceci:

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

Est-ce possible (je suis prêt à dire "oui bien sûr, ça ne devrait pas être difficile" mais je ne connais pas la syntaxe à utiliser)?

31
MrVaykadji

Le [ commande/Le shell intégré a des tests de comparaison, donc vous pouvez simplement faire

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

-ge signifie supérieur ou égal à (et ainsi de suite). Le [ la commande n'est qu'une commande, pas une syntaxe spéciale (c'est en fait la même chose que test: consultez man test), donc il A BESOIN de l'espace après. Si vous écrivez [$REPLY il essaiera de trouver une commande nommée [$REPLY et l'exécuter, ce qui ne fonctionnera pas. Il en va de même pour la fermeture ].

Ici, nous utilisons le && Opérateur shell pour exécuter la deuxième commande uniquement si la première réussit. [ prend également en charge -a à et deux tests, mais il est déconseillé et son utilisation doit être découragée car elle empêche les arguments d'être analysés de manière fiable.

Edit: pour tester si le nombre est entier (si cela peut arriver dans votre code), faites d'abord le test

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

Bien sûr, toutes ces expressions de parenthèses renvoient 0 (vrai) ou 1 (faux) et peuvent être combinées. Non seulement vous pouvez tout mettre dans le même support, vous pouvez également le faire

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

ou quelque chose de similaire.

34
orion

Vous pourriez simplement dire:

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

Citant du manuel :

((...))

(( expression ))

L'expression arithmétique est évaluée selon les règles décrites ci-dessous (voir Shell Arithmetic ). Si la valeur de l'expression est différente de zéro, l'état de retour est 0; sinon le statut de retour est 1. C'est exactement équivalent à

let "expression"
14
devnull

Vous pouvez faire quelque chose comme ça:

#!/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" =~ ^[0-9]+$ && "$REPLY" -gt 0 && "$REPLY" -lt 33 ]]
then
    echo "GOOD"
else
    echo "BAD"
fi
5
terdon

Tout d'abord, testez si l'entrée est numérique. Par exemple, en utilisant l'opérateur de correspondance d'expression régulière de expressions conditionnelles bash :

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

Pour tester des plages numériques, vous avez deux possibilités:

  • le -gt opérateur de expressions conditionnelles inside [ … ] ou [[ … ]] (sachez que le < et > les opérateurs font une comparaison de chaînes, pas une comparaison de valeurs numériques, donc [[ 10 < 9 ]] est vrai);
  • les opérateurs arithmétiques habituels dans ((…)).

Donc:

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

(Vous voudrez peut-être utiliser différentes règles d'approximation, je ne sais pas si celles que j'ai choisies sont les meilleures ici.)

Pour détecter correctement si une chaîne est un nombre (décimal), nous devons d'abord définir ce qu'est un nombre entier décimal. Une définition simple et pourtant assez complète est:

Une séquence d'un signe facultatif (+ ou -) suivie d'au plus 18 chiffres décimaux (significatifs).

Et ces étapes sont nécessaires:

  1. Supprimez tous les caractères qui ne sont pas des chiffres décimaux (après le signe).
  2. Supprimez tous les zéros non significatifs facultatifs. Des zéros en tête feront croire à Shell que le nombre est en octal.
  3. Limitez la taille maximale de l'entier à 18 chiffres. En dessous de 2 ** 63-1 (entier 64 bits max).

Un seul regex fera la plupart de cela:

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

Le code pour traiter plusieurs nombres est:

#!/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_

Qui imprimera:

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

Une fois que le nombre est propre et clair, le seul test manquant est de limiter la plage de valeurs. Ce simple couple de lignes fera cela:

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