Verifique se $ REPLY está em um intervalo de números

30

Estou escrevendo um shell script para Linux, usando o Bash, para converter qualquer arquivo de vídeo em um MP4. Para isso, estou usando avconvcom libvorbisáudio.

Dentro do meu script, tenho uma pergunta para o usuário:

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

Minha seqüência de caracteres "ABITRATE" vai para a avconvlinha de comando final .

Mas eu gostaria de dar ao usuário a oportunidade de responder a essa pergunta com um valor em Kb (Kilobit) e traduzi-la na escala libvorbisusada. A "escala de -2 a 10" é a seguinte:

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

Gostaria de saber como verificar se meu $ REPLY está em um intervalo de número. Por exemplo, eu gostaria que meu script fizesse algo assim:

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

Isso é possível (estou disposto a dizer 'sim, claro, não deve ser difícil', mas não conheço a sintaxe a ser usada)?

MrVaykadji
fonte
AFAIK, Vorbis não é um codec de áudio válida em um arquivo MP4 (que deseja usar AAC ou possivelmente MP3) ...
evilsoup
Obrigado, funcionou bem no VLC, mas o Totem não quer lê-lo. Eu estou mudando para libvo_aacenc
MrVaykadji

Respostas:

30

O [comando / shell interno possui testes de comparação, para que você possa fazer

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

onde -gesignifica maior ou igual a (e assim por diante). O -aé lógico "e". O [comando é apenas um comando, não uma sintaxe especial (na verdade, é o mesmo que test: check-out man test); portanto, ele precisa do espaço a seguir. Se você escrever, [$REPLYele tentará encontrar um comando chamado [$REPLYe executá-lo, o que não funcionará. O mesmo vale para o fechamento ].

Editar: para testar se o número é inteiro (se isso pode acontecer no seu código), primeiro faça o teste

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

É claro que todas essas expressões de colchete retornam 0 (verdadeiro) ou 1 (falso) e podem ser combinadas. Além de colocar tudo no mesmo suporte, você também pode

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

ou algo semelhante.

orion
fonte
Exatamente o que eu estava procurando, obrigado! Eu poderia usar uma expressão de comparação simples como >=?
MrVaykadji
O Bash permite muitos tipos de colchetes para teste. Você tem esses [colchetes tradicionais , que funcionam como visto em man test. Estes são tradicionais e à prova de idiotas. Então, você tem muitos bash builtins. Você tem [[quais são semelhantes, mas não exatamente iguais, pois este não expande nomes de caminho (lá, <=> comparações médias de strings e comparações inteiras são as mesmas que em [). Ambos também têm muitos testes para existência de arquivos, permissões e assim por diante. Então você usa o single (e o double ((na resposta do @ devnull. Confira man bashem Compound Commands.
orion
1
@MrVaykadji Eu recomendo que você também teste se a variável é um número; caso contrário, poderá obter resultados inesperados:foo='a'; [[ "$foo" -lt 32 ]] && echo yes
terdon
12

Você poderia simplesmente dizer:

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

Citando o manual :

((...))

(( expression ))

A expressão aritmética é avaliada de acordo com as regras descritas abaixo (consulte Aritmética da casca ). Se o valor da expressão for diferente de zero, o status de retorno será 0; caso contrário, o status de retorno é 1. Isso é exatamente equivalente a

let "expression"
devnull
fonte
Eu gosto da simplicidade, mas quais são as ((? Tentei usá-los rapidamente e parece que funcionava, if [ ] ; thenmas não sabia que existia.
MrVaykadji
@MrVaykadji Adicionada uma referência do manual. Deixe-me saber se não estiver claro.
22414 devnull
1
@MrVaykadji Além disso, dizer if [ condition ]; then foo; fié equivalente a dizer condition && foo.
585 devnull
Ok, legal! Eu gostaria de aceitar seus dois (Orion e você), se pudesse. Muito obrigado por tudo isso, eu aprendi muito.
MrVaykadji
Você pode remover os zeros à esquerda se usar isso. a=08; (( a > 1 ))irá erro desde 08 é considerado octal. você também pode forçar decimal com 10#$REPLY. cmd && cmdnão é exatamente o mesmo que if cmd; then ...Depois que você precisar de uma elsepeça, encadeará a lógica &&e ||poderá causar erros sutis.
llua
4

Você poderia fazer algo assim:

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

Primeiro, teste se a entrada é numérica. Por exemplo, usando o operador de correspondência de expressão regular de expressões condicionais do bash :

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

Para testar intervalos numéricos, você tem duas possibilidades:

  • o -gtoperador de expressões condicionais internas [ … ]ou [[ … ]](cuidado para que os operadores <e >façam comparação de cadeias, não comparação de valores numéricos, isso [[ 10 < 9 ]]é verdade);
  • os operadores aritméticos usuais dentro ((…)).

Portanto:

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

(Você pode usar diferentes regras de aproximação, não sei se as que escolhi são as melhores aqui.)

Gilles 'SO- parar de ser mau'
fonte
1

Para detectar corretamente se uma string é um número (decimal), primeiro precisamos definir o que é um número inteiro decimal. Uma definição simples e bastante completa é:

Uma sequência de um sinal opcional (+ ou -) seguido por não mais que 18 dígitos decimais (significativos).

E estas etapas são necessárias:

  1. Remova todos os caracteres que não são dígitos decimais (após o sinal).
  2. Remova todos os zeros iniciais opcionais. Zeros à esquerda farão com que o shell acredite que o número está em octal.
  3. Limite o tamanho máximo do número inteiro para 18 dígitos. Abaixo de 2 ** 63-1 (número máximo máximo de 64 bits).

Apenas uma regex fará a maior parte disso:

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

O código para processar vários números é:

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

Qual será impresso:

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

Quando o número estiver limpo e claro, o único teste que falta é limitar o intervalo de valores. Este simples par de linhas fará isso:

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