Controlla se $ REPLY è compreso in un intervallo di numeri


30

Sto scrivendo uno script di shell per Linux, usando Bash, per tradurre qualsiasi file video in un MP4. Per questo, sto usando avconvcon libvorbisper l'audio.

All'interno del mio script, ho una domanda per l'utente:

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

La mia stringa "ABITRATE" va nella avconvriga di comando finale .

Ma vorrei dare all'utente la possibilità di rispondere a quella domanda con un valore in Kb (Kilobit) e tradurlo nella scala che libvorbisutilizza. La "scala da -2 a 10" è questa:

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

Vorrei sapere come verificare se il mio $ REPLY è compreso in un intervallo di numeri. Ad esempio, vorrei che il mio script facesse qualcosa del genere:

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

È possibile (sono disposto a dire "sì, certo, non dovrebbe essere difficile" ma non conosco la sintassi da usare)?


AFAIK, Vorbis non è un codec audio valido in un file MP4 (vuoi usare AAC o eventualmente MP3) ...
evilsoup

Grazie, ha funzionato bene su VLC ma Totem non vuole leggerlo. Sto passando a libvo_aacenc
MrVaykadji

Risposte:


30

Il [comando / shell incorporato ha test di confronto, quindi puoi semplicemente farlo

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

dove -gesignifica maggiore o uguale a (e così via). È -a"e" logico. Il [comando è solo un comando, non una sintassi speciale (in realtà è lo stesso di test: check out man test), quindi HA BISOGNO dello spazio dopo di esso. Se scrivi [$REPLY, proverà a trovare un comando chiamato [$REPLYed eseguirlo, che non funzionerà. Lo stesso vale per la chiusura ].

Modifica: per verificare se il numero è intero (se ciò può accadere nel codice), eseguire prima il test

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

Naturalmente tutte queste espressioni parentesi restituiscono 0 (vero) o 1 (falso) e possono essere combinate. Non solo puoi mettere tutto nella stessa parentesi, ma puoi anche farlo

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

o qualcosa di simile.


Esattamente quello che stavo cercando, grazie! Potrei usare invece un'espressione di confronto semplice come >=?
MrVaykadji,

Bash consente molti tipi di parentesi per i test. Hai queste [parentesi tradizionali , che funzionano come in man test. Questi sono tradizionali e a prova di folle. Quindi, hai molti builtin bash. Ne hai di [[simili, ma non esattamente uguali, poiché questo non espande i nomi dei percorsi (lì, <=> significa confronti di stringhe e confronti di numeri interi sono gli stessi di in [). Entrambi hanno anche molti test per l'esistenza dei file, i permessi e così via. Quindi hai usato single (e double ((nella risposta di @ devnull. Guarda man bashsotto Compound Commands.
Orione,

1
@MrVaykadji Consiglio vivamente di provare anche se la variabile è un numero, altrimenti potresti ottenere risultati imprevisti:foo='a'; [[ "$foo" -lt 32 ]] && echo yes
terdon

12

Potresti semplicemente dire:

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

Citando dal manuale :

((...))

(( expression ))

L'espressione aritmetica viene valutata in base alle regole descritte di seguito (vedere Shell Arithmetic ). Se il valore dell'espressione è diverso da zero, lo stato di ritorno è 0; altrimenti lo stato di ritorno è 1. Questo è esattamente equivalente a

let "expression"

Mi piace la semplicità, ma quali sono ((? Ho provato a usarli nel prompt e sembra funzionare come if [ ] ; thenma non sapevo che esistesse.
MrVaykadji,

@MrVaykadji Aggiunto un riferimento dal manuale. Fammi sapere se non è chiaro.
Devnull

1
@MrVaykadji Inoltre, dire if [ condition ]; then foo; fiequivale a dire condition && foo.
Devnull

Okay, va bene ! Vorrei accettare entrambi i tuoi domande (Orion e te) se potessi. Grazie mille per tutto questo, ho imparato molto.
MrVaykadji,

Potresti voler eliminare gli zeri iniziali se lo usi. a=08; (( a > 1 ))sarà errore poiché 08 è considerato ottale. potresti anche forzare il decimale con 10#$REPLY. cmd && cmdnon è la stessa cosa di if cmd; then ...Once you need a elsepart, concatenamento logico &&e ||può causare bug sottili.
llua

4

Potresti fare qualcosa del genere:

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

2

Innanzitutto, verifica se l'input è numerico. Ad esempio, utilizzando l'operatore di corrispondenza delle espressioni regolari delle espressioni condizionali bash :

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

Per testare intervalli numerici, hai due possibilità:

  • l' -gtoperatore di espressioni condizionali all'interno [ … ]o [[ … ]](attenzione che gli operatori <e >eseguono il confronto di stringhe, non il confronto di valori numerici, quindi [[ 10 < 9 ]]è vero);
  • i soliti operatori aritmetici all'interno ((…)).

Così:

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

(Potresti voler usare diverse regole di approssimazione, non so se quelle che ho scelto sono le migliori qui.)


1

Per rilevare correttamente se una stringa è un numero (decimale), dobbiamo prima definire che cos'è un numero intero decimale. Una definizione semplice ma abbastanza completa è:

Una sequenza di un segno opzionale (+ o -) seguito da non più di 18 cifre decimali (significative).

E questi passaggi sono necessari:

  1. Rimuovi tutti i caratteri che non sono cifre decimali (dopo il segno).
  2. Rimuovi tutti gli zeri iniziali opzionali. Gli zeri iniziali indurranno la shell a credere che il numero sia in ottale.
  3. Limitare la dimensione massima dell'intero a 18 cifre. Sotto 2 ** 63-1 (max 64 bit intero).

Solo una regex farà la maggior parte di ciò:

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

Il codice per elaborare più numeri è:

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

Che stamperà:

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 volta che il numero è pulito e chiaro, l'unico test mancante è limitare l'intervallo di valori. Questa semplice coppia di righe lo farà:

(( 1  <= integer && integer <= 32 )) && REPLY="-2"
(( 33 <= integer && integer <= 48 )) && REPLY="-1"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.