Jak mogę użyć zmiennej jako warunku przypadku?

17

Próbuję użyć zmiennej składającej się z różnych ciągów znaków oddzielonych |za pomocą casetestu instrukcji. Na przykład:

string="\"foo\"|\"bar\""
read choice
case $choice in
    $string)
        echo "You chose $choice";;
    *)
        echo "Bad choice!";;
esac

Chcę być w stanie wpisać foolub barwykonać pierwszą część caseinstrukcji. Jednak oba fooi barzabierzcie mnie do drugiego:

$ foo.sh
foo
Bad choice!
$ foo.sh
bar
Bad choice!

Używanie "$string"zamiast $stringnie robi różnicy. Nie używa też string="foo|bar".

Wiem, że mogę to zrobić w ten sposób:

case $choice in
    "foo"|"bar")
        echo "You chose $choice";;
    *)
        echo "Bad choice!";;
esac

Mogę wymyślić różne obejścia, ale chciałbym wiedzieć, czy można użyć zmiennej jako casewarunku w bash. Czy to możliwe, a jeśli tak, to w jaki sposób?

terdon
źródło
2
Nie mogę się zmusić, żeby zasugerować ją jako prawdziwą odpowiedź, ale ponieważ nikt inny nie wspomniał, to mógłby zawinąć oświadczenie sprawy w sposób eval, uciekając $ wybór, nawiasy, gwiazdka, średników i znaków nowej linii. Brzydkie, ale „działa”.
Jeff Schaller
@JeffSchaller - to nie jest zły pomysł wiele razy, i może to tylko bilet w tym przypadku. Zastanawiałem się, czy go polecić, ale readkawałek mnie powstrzymał. Moim zdaniem sprawdzanie poprawności danych wejściowych przez użytkownika, czyli takie, jak się wydaje, casewzorce nie powinny znajdować się na górze listy oceny, a raczej powinny być przycinane do *domyślnego wzorca, tak aby zagwarantować, że jedyne osiągnięte wyniki są gwarantowane. nadal, ponieważ problemem jest kolejność parsowania / rozbudowy, może być potrzebna druga ocena.
mikeserv

Odpowiedzi:

14

Instrukcja bash stwierdza:

słowo case na liście [[(] pattern [| pattern] ...) ;; ] ... esac

Każdy badany wzorzec jest rozszerzany za pomocą rozszerzania tyldy, rozszerzania parametrów i zmiennych, podstawiania arytmetycznego, podstawiania poleceń i podstawiania procesu.

Brak «rozszerzenia nazwy ścieżki»

Zatem: wzorzec NIE jest rozwijany za pomocą «Rozwinięcia nazwy ścieżki».

Dlatego: wzorzec NIE może zawierać „|” wewnątrz. Tylko: dwa wzorce można połączyć za pomocą „|”.

To działa:

s1="foo"; s2="bar"    # or even s1="*foo*"; s2="*bar*"

read choice
case $choice in
    $s1|$s2 )     echo "Two val choice $choice"; ;;  # not "$s1"|"$s2"
    * )           echo "A Bad  choice! $choice"; ;;
esac

Używanie «rozszerzonego globowania»

Jest to jednak wordzgodne z patternużyciem reguł «Rozszerzenie nazwy ścieżki».
I «Extended Globbing» tutaj , tutaj i tutaj pozwala na stosowanie naprzemiennych wzorów („|”).

Działa to również:

shopt -s extglob

string='@(foo|bar)'

read choice
    case $choice in
        $string )      printf 'String  choice %-20s' "$choice"; ;;&
        $s1|$s2 )      printf 'Two val choice %-20s' "$choice"; ;;
        *)             printf 'A Bad  choice! %-20s' "$choice"; ;;
    esac
echo

Treść ciągu

Następny skrypt testowy pokazuje, że wzorzec pasujący do wszystkich wierszy zawierających jeden foolub bargdziekolwiek jest '*$(foo|bar)*'albo dwie zmienne $s1=*foo*i$s2=*bar*


Skrypt testowy:

shopt -s extglob    # comment out this line to test unset extglob.
shopt -p extglob

s1="*foo*"; s2="*bar*"

string="*foo*"
string="*foo*|*bar*"
string='@(*foo*|*bar)'
string='*@(foo|bar)*'
printf "%s\n" "$string"

while IFS= read -r choice; do
    case $choice in
        "$s1"|"$s2" )   printf 'A first choice %-20s' "$choice"; ;;&
        $string )   printf 'String  choice %-20s' "$choice"; ;;&
        $s1|$s2 )   printf 'Two val choice %-20s' "$choice"; ;;
        *)      printf 'A Bad  choice! %-20s' "$choice"; ;;
    esac
    echo
done <<-\_several_strings_
f
b
foo
bar
*foo*
*foo*|*bar*
\"foo\"
"foo"
afooline
onebarvalue
now foo with spaces
_several_strings_
Społeczność
źródło
9

Możesz użyć extglobopcji:

shopt -s extglob
string='@(foo|bar)'
choroba
źródło
Ciekawy; co wyróżnia go @(foo|bar)na tle innych foo|bar? Oba są poprawnymi wzorcami, które działają tak samo, gdy są pisane dosłownie.
chepner,
4
Ah nie ważne. |nie jest częścią wzorca foo|bar, jest częścią składni caseinstrukcji pozwalającej na stosowanie wielu wzorców w jednej klauzuli. | jest jednak częścią rozszerzonego wzorca.
chepner
3

Potrzebujesz dwóch zmiennych, caseponieważ or lub |potok są analizowane przed rozwinięciem wzorców.

v1=foo v2=bar

case foo in ("$v1"|"$v2") echo foo; esac

foo

Wzory powłoki w zmiennych są obsługiwane inaczej, gdy są cytowane lub nie są cytowane:

q=?

case a in
("$q") echo question mark;;
($q)   echo not a question mark
esac

not a question mark
mikeserv
źródło
-1

Jeśli chcesz obejść interfejs zgodny z deską rozdzielczą, możesz napisać:

  string="foo|bar"
  read choice
  awk 'BEGIN{
   n=split("'$string'",p,"|");
   for(i=1;i<=n;i++)
    if(system("\
      case \"'$choice'\" in "p[i]")\
       echo \"You chose '$choice'\";\
       exit 1;;\
      esac"))
     exit;
   print "Bad choice"
  }'

Objaśnienie: awk służy do podziału „łańcucha” i testowania każdej części osobno. Jeśli „wybór” pasuje do aktualnie testowanej części p [i], polecenie awk zostanie zakończone z „wyjściem” w wierszu 11. Do samego testu używana jest „wielkość liter” powłoki (w wywołaniu „systemowym”) , jak zapytał terdon. Dzięki temu istnieje możliwość modyfikacji zamierzonego „łańcucha” testowego, na przykład „foo * | bar”, aby pasował również do „foooo” („wzorzec wyszukiwania”), na co pozwala „wielkość liter” powłoki. Jeśli zamiast tego wolisz wyrażenia regularne, możesz pominąć wywołanie „systemowe” i zamiast tego użyć „~” lub „dopasuj” awk.

Gerald Schade
źródło
Drogi downvoter, mógłbym lepiej nauczyć się, jak unikać kandydatów na bezużyteczne rozwiązanie, gdybyś powiedział mi powód swojego przegłosowania.
Gerald Schade