Compruebe si $ REPLY está en un rango de números

30

Estoy escribiendo un script de shell para Linux, usando Bash, para traducir cualquier archivo de video a un MP4. Para eso, estoy usando avconvcon libvorbispara audio.

Dentro de mi script, tengo una pregunta para el usuario:

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

Mi cadena "ABITRATE" va a la avconvlínea de comando final .

Pero me gustaría darle al usuario la oportunidad de responder esa pregunta con un valor en Kb (Kilobit) y traducirla a la escala que libvorbisusa. La "escala de -2 a 10" es esta:

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

Me gustaría saber cómo verificar si mi $ REPLY está en un rango de números. Por ejemplo, me gustaría que mi script haga algo como esto:

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

¿Es esto posible (estoy dispuesto a decir 'sí, por supuesto, no debería ser difícil' pero no sé la sintaxis que debo usar)?

MrVaykadji
fuente
AFAIK, Vorbis no es un códec de audio válido en un archivo MP4 (desea usar AAC o posiblemente MP3) ...
evilsoup
Gracias, funcionó bien en VLC pero Totem no quiere leerlo. Me estoy cambiando a libvo_aacenc
MrVaykadji

Respuestas:

30

El [comando / shell incorporado tiene pruebas de comparación, por lo que puede hacer

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

donde -gesignifica mayor o igual a (y así sucesivamente). El -aes lógico "y". El [comando es solo un comando, no una sintaxis especial (en realidad es lo mismo que test: check out man test), por lo que NECESITA el espacio posterior. Si lo escribe [$REPLY, intentará encontrar un comando llamado [$REPLYy ejecutarlo, lo que no funcionará. Lo mismo vale para el cierre ].

Editar: para probar si el número es entero (si eso puede suceder en su código), primero haga la prueba

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

Por supuesto, todas estas expresiones de paréntesis devuelven 0 (verdadero) o 1 (falso) y se pueden combinar. No solo puedes poner todo en el mismo soporte, también puedes hacer

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

o algo similar.

Orión
fuente
Exactamente lo que estaba buscando, ¡gracias! ¿Podría usar en su lugar una expresión de comparación simple como >=?
MrVaykadji
Bash permite muchos tipos de soportes para pruebas. Tiene estos [soportes tradicionales , que funcionan como se ve en man test. Estos son tradicionales y a prueba de tontos. Entonces, tienes muchas bash builtins. Tiene [[cuáles son similares, pero no exactamente lo mismo, ya que este no expande los nombres de ruta (allí, <=> las comparaciones de cadenas medias y las comparaciones de enteros son las mismas que en [). Ambos también tienen muchas pruebas de existencia de archivos, permisos, etc. Entonces tienes simple (y doble ((usado en la respuesta de @ devnull. Salida man bashbajo Compound Commands.
orion
1
@MrVaykadji Le recomiendo que también pruebe si la variable es un número; de lo contrario, podría obtener resultados inesperados:foo='a'; [[ "$foo" -lt 32 ]] && echo yes
terdon
12

Simplemente podrías decir:

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

Citando del manual :

((...))

(( expression ))

La expresión aritmética se evalúa de acuerdo con las reglas que se describen a continuación (consulte Shell Aritmética ). Si el valor de la expresión no es cero, el estado de retorno es 0; de lo contrario, el estado de devolución es 1. Esto es exactamente equivalente a

let "expression"
devnull
fuente
Me gusta la simplicidad, pero ¿cuáles son las ((? Traté de usarlos rápidamente y parece funcionar, if [ ] ; thenpero no sabía que existía.
MrVaykadji
@MrVaykadji Se agregó una referencia del manual. Avísame si no está claro.
devnull
1
@MrVaykadji Además, decir if [ condition ]; then foo; fies equivalente a decir condition && foo.
devnull
Esta bien ! Me gustaría aceptar sus dos respuestas (Orion y usted) si pudiera. Muchas gracias por todo esto, aprendí mucho.
MrVaykadji
Es posible que desee quitar los ceros a la izquierda si usa esto. a=08; (( a > 1 ))se producirá un error ya que 08 se considera octal. También puedes forzar el decimal con 10#$REPLY. cmd && cmdNo es exactamente lo mismo que if cmd; then ...una vez que necesita una elseparte, el encadenamiento lógico &&y ||puede causar errores sutiles.
llua
4

Podrías hacer algo como esto:

#!/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
fuente
2

Primero, pruebe si la entrada es numérica. Por ejemplo, utilizando el operador de coincidencia de expresiones regulares de expresiones condicionales bash :

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

Para probar rangos numéricos, tiene dos posibilidades:

  • el -gtoperador de expresiones condicionales dentro de [ … ]o [[ … ]](tenga en cuenta que los operadores <y >hacen una comparación de cadenas, no una comparación de valores numéricos, por lo que [[ 10 < 9 ]]es cierto);
  • Los operadores aritméticos habituales en el interior ((…)).

Así:

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

(Es posible que desee utilizar diferentes reglas de aproximación, no sé si las que elegí son las mejores aquí).

Gilles 'SO- deja de ser malvado'
fuente
1

Para detectar correctamente si una cadena es un número (decimal), primero debemos definir qué es un número entero decimal. Una definición simple pero bastante completa es:

Una secuencia de un signo opcional (+ o -) seguido de no más de 18 dígitos decimales (significativos).

Y estos pasos son necesarios:

  1. Elimine todos los caracteres que no sean dígitos decimales (después del signo).
  2. Elimine todos los ceros iniciales opcionales. Los ceros a la izquierda harán que el shell crea que el número está en octal.
  3. Limite el tamaño máximo del entero a 18 dígitos. Por debajo de 2 ** 63-1 (número máximo de 64 bits).

Solo una expresión regular hará la mayor parte de eso:

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

El código para procesar varios números es:

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

Que imprimirá:

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

Una vez que el número está limpio y claro, la única prueba que falta es limitar el rango de valores. Este simple par de líneas hará eso:

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