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

30

J'écris un script shell pour Linux, en utilisant Bash, pour traduire n'importe quel fichier vidéo en MP4. Pour cela, j'utilise avconvavec libvorbispour 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 avconvligne de commande finale .

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 qui libvorbisutilise. "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, je voudrais 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)?

MrVaykadji
la source
AFAIK, Vorbis n'est pas un codec audio valide dans un fichier MP4 (vous voulez utiliser AAC ou éventuellement MP3) ...
evilsoup
Merci, cela a bien fonctionné sur VLC mais Totem ne veut pas le lire. Je passe à libvo_aacenc
MrVaykadji

Réponses:

30

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

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

-gesignifie supérieur ou égal à (et ainsi de suite). Le -aest logique "et". La [commande est juste une commande, pas une syntaxe spéciale (c'est en fait la même chose que test: vérifier man test), donc elle A BESOIN de l'espace après. Si vous l'écrivez, [$REPLYil essaiera de trouver une commande nommée [$REPLYet l'exécutera, ce qui ne fonctionnera pas. Il en va de même pour la fermeture ].

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 -a "$REPLY" -le 32 ]; then ...

ou quelque chose de similaire.

orion
la source
Exactement ce que je cherchais, merci! Puis-je utiliser à la place une expression de comparaison simple comme >=?
MrVaykadji
Bash autorise de nombreux types de supports pour les tests. Vous avez ces [supports traditionnels , qui fonctionnent comme indiqué dans man test. Ce sont traditionnels et infaillibles. Ensuite, vous avez beaucoup de commandes bash. Vous en avez [[qui sont similaires, mais pas exactement les mêmes, car celui-ci ne développe pas les noms de chemin (là, <=> les comparaisons de chaînes moyennes et les comparaisons d'entiers sont les mêmes que dans [). Les deux ont également beaucoup de tests pour l'existence de fichiers, les autorisations, etc. Ensuite, vous avez utilisé le simple (et le double ((dans la réponse de @ devnull. Découvrez man bashsous Compound Commands.
orion
1
@MrVaykadji Je vous recommande fortement de tester également si la variable est un nombre, vous pourriez obtenir des résultats inattendus autrement:foo='a'; [[ "$foo" -lt 32 ]] && echo yes
terdon
12

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. Ceci est exactement équivalent à

let "expression"
devnull
la source
J'aime la simplicité, mais quels sont les ((? J'ai essayé de les utiliser à l'invite et cela semble fonctionner comme if [ ] ; thenmais je ne savais pas que cela existait.
MrVaykadji
@MrVaykadji Ajout d'une référence dans le manuel. Faites-moi savoir si ce n'est pas clair.
devnull
1
@MrVaykadji De plus, dire if [ condition ]; then foo; fiest équivalent à dire condition && foo.
devnull
D'accord, bien! J'aimerais accepter vos deux demandes (Orion et vous) si je le pouvais. Merci beaucoup pour tout ça, j'ai beaucoup appris.
MrVaykadji
Vous pouvez supprimer les zéros non significatifs si vous utilisez cela. a=08; (( a > 1 ))erreur car 08 est considéré comme octal. vous pouvez également forcer la décimale avec 10#$REPLY. cmd && cmdn'est pas tout à fait la même chose qu'une if cmd; then ...fois que vous avez besoin d'une elsepièce, l'enchaînement logique &&et ||peut provoquer des bugs subtils.
llua
4

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" -gt 0 && "$REPLY" -lt 33 && "$REPLY" =~ '^[0-9]$' ]]
then
    echo "GOOD"
else
    echo "BAD"
fi
terdon
la source
2

Tout d'abord, testez si l'entrée est numérique. Par exemple, en utilisant l'opérateur de correspondance d'expressions régulières d'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:

  • l' -gtopérateur d' expressions conditionnelles à l' intérieur de [ … ]ou [[ … ]](attention, les opérateurs <et >font une comparaison de chaînes, et non une comparaison de valeurs numériques, [[ 10 < 9 ]]c'est vrai);
  • les opérateurs arithmétiques habituels à l' intérieur ((…)).

Ainsi:

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.)

Gilles 'SO- arrête d'être méchant'
la source
1

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 au 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"
Isaac
la source