Sprawdź, czy $ REPLY jest w zakresie liczb


30

Piszę skrypt powłoki dla systemu Linux, używając Bash, aby przetłumaczyć dowolny plik wideo na MP4. Do tego używam avconvz libvorbisdźwiękiem.

Wewnątrz mojego skryptu mam pytanie do użytkownika:

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

Mój ciąg „ABITRATE” przechodzi do ostatniego avconvwiersza polecenia.

Chciałbym jednak dać użytkownikowi możliwość odpowiedzi na to pytanie wartością Kb (Kilobit) i przetłumaczenia go na libvorbisużywaną skalę . „Skala od -2 do 10” jest następująca:

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

Chciałbym wiedzieć, jak sprawdzić, czy mój $ REPLY mieści się w zakresie liczby. Na przykład chciałbym, aby mój skrypt zrobił coś takiego:

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

Czy to możliwe (jestem gotów powiedzieć „tak, oczywiście, nie powinno być trudne”, ale nie znam składni)?


AFAIK, Vorbis nie jest prawidłowym kodekiem audio w pliku MP4 (chcesz użyć AAC lub ewentualnie MP3) ...
evilsoup

Dziękuję, działało dobrze na VLC, ale Totem nie chce go przeczytać. Przejdę na libvo_aacenc
MrVaykadji

Odpowiedzi:


30

[Wbudowane polecenie / powłoka ma testów porównawczych, więc można po prostu zrobić

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

gdzie -geoznacza większą-lub-równą (i tak dalej). Jest -ato logiczne „i”. [Komenda jest tylko komenda, nie specjalnej składni (to faktycznie takie same, jak test: sprawdzeniu man test), więc potrzebuje miejsca po niej. Jeśli napiszesz [$REPLY, spróbuje znaleźć polecenie o nazwie [$REPLYi wykonać je, co nie będzie działać. To samo dotyczy zamknięcia ].

Edycja: aby sprawdzić, czy liczba jest liczbą całkowitą (jeśli może się tak zdarzyć w kodzie), najpierw wykonaj test

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

Oczywiście wszystkie te wyrażenia w nawiasach zwracają 0 (prawda) lub 1 (fałsz) i można je łączyć. Nie tylko możesz umieścić wszystko w tym samym przedziale, możesz także

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

lub coś podobnego.


Właśnie tego szukałem, dziękuję! Czy zamiast tego mogę użyć prostego wyrażenia porównania, takiego jak >=?
MrVaykadji

Bash pozwala na testowanie wielu typów nawiasów. Masz te tradycyjne [nawiasy, które działają jak widać w man test. Są to tradycyjne i głupie. Następnie masz wiele wbudowanych wersji bash. Masz [[podobne, ale niezupełnie takie same, ponieważ ten nie rozwija nazw ścieżek (tam <=> średnie porównania ciągów, a porównania liczb całkowitych są takie same jak w [). Oba mają również wiele testów na istnienie pliku, uprawnienia i tak dalej. Następnie masz jedno (i podwójne ((użyte w odpowiedzi @ devnull. Sprawdź man bashpod Compound Commands.
Orion

1
@MrVaykadji Bardzo polecam również przetestowanie, czy zmienna jest liczbą, w przeciwnym razie możesz uzyskać nieoczekiwane wyniki:foo='a'; [[ "$foo" -lt 32 ]] && echo yes
terdon

12

Możesz po prostu powiedzieć:

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

Cytowanie z instrukcji :

((...))

(( expression ))

Wyrażenie arytmetyczne ocenia się zgodnie z zasadami opisanymi poniżej (patrz: Arytmetyka powłoki ). Jeśli wartość wyrażenia jest różna od zera, zwracanym statusem jest 0; w przeciwnym razie zwracany jest status 1. Jest to dokładnie równoważne z

let "expression"

Podoba mi się prostota, ale jakie są ((? Próbowałem użyć ich natychmiast i wydaje się, że działa tak, if [ ] ; thenale nie wiedziałem, że istnieje.
MrVaykadji

@MrVaykadji Dodano odniesienie z instrukcji. Daj mi znać, jeśli nie jest to jasne.
devnull

1
@MrVaykadji Co więcej, powiedzenie if [ condition ]; then foo; fijest równoważne powiedzeniu condition && foo.
devnull

Ok, fajnie ! Chciałbym zaakceptować oba twoje odpowiedzi (Oriona i ciebie), gdybym mógł. Wielkie dzięki za to wszystko, wiele się nauczyłem.
MrVaykadji

Możesz użyć zera wiodących zer, jeśli go używasz. a=08; (( a > 1 ))popełni błąd, ponieważ 08 uważa się za ósemkowy. możesz również wymusić dziesiętne za pomocą 10#$REPLY. cmd && cmdto nie to samo, co if cmd; then ...raz potrzebna elseczęść, logiczne tworzenie łańcuchów &&i ||może powodować subtelne błędy.
llua

4

Możesz zrobić coś takiego:

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

Najpierw sprawdź, czy dane wejściowe są numeryczne. Na przykład za pomocą operatora dopasowania wyrażeń regularnych wyrażeń warunkowych bash :

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

Aby przetestować zakresy liczbowe, masz dwie możliwości:

  • -gtoperator wyrażeń warunkowych wewnątrz [ … ]lub [[ … ]](uwaga, że <i >operatorzy zrobić porównanie ciąg, nie numeryczny porównania wartość, więc [[ 10 < 9 ]]jest true);
  • zwykłe operatory arytmetyczne w środku ((…)).

A zatem:

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

(Być może chcesz zastosować inne reguły zbliżenia, nie wiem, czy te, które wybrałem, są tutaj najlepsze).


1

Aby poprawnie wykryć, czy łańcuch jest liczbą (dziesiętną), najpierw musimy zdefiniować, jaka jest dziesiętna liczba całkowita. Prosta, a mimo to kompletna definicja to:

Sekwencja opcjonalnego znaku (+ lub -), po której następuje nie więcej niż 18 (znaczących) cyfr dziesiętnych.

A te kroki są potrzebne:

  1. Usuń wszystkie znaki, które nie są cyframi dziesiętnymi (po znaku).
  2. Usuń wszystkie opcjonalne zera wiodące. Zera wiodące spowodują, że powłoka uwierzy, że liczba jest ósemkowa.
  3. Ogranicz maksymalny rozmiar liczby całkowitej do 18 cyfr. Poniżej 2 ** 63-1 (maks. 64-bitowa liczba całkowita).

Tylko jedno wyrażenie regularne zrobi większość tego:

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

Kod przetwarzający kilka liczb to:

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

Który wydrukuje:

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

Gdy liczba jest już czysta i wyraźna, jedynym brakującym testem jest ograniczenie zakresu wartości. Ta prosta para wierszy zrobi to:

(( 1  <= integer && integer <= 32 )) && REPLY="-2"
(( 33 <= integer && integer <= 48 )) && REPLY="-1"
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.