Come posso usare una variabile come condizione del caso?


17

Sto cercando di utilizzare una variabile composta da stringhe diverse separate |da un casetest di istruzione. Per esempio:

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

Voglio essere in grado di digitare fooo bared eseguire la prima parte casedell'istruzione. Tuttavia, entrambi fooe barportami al secondo:

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

L'uso "$string"invece di $stringnon fa differenza. Neanche usando string="foo|bar".

So di poterlo fare in questo modo:

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

Mi vengono in mente varie soluzioni alternative, ma vorrei sapere se è possibile utilizzare una variabile come casecondizione in bash. È possibile e, in tal caso, come?


2
Non posso portare io a suggerire come una vera risposta, ma dal momento che nessun altro ha parlato, si poteva avvolgere l'istruzione case in una eval, sfuggendo $ scelta, le parens, asterisco, punto e virgola, e ritorni a capo. Brutto, ma "funziona".
Jeff Schaller

@JeffSchaller - non è una cattiva idea molte volte, e forse è solo il biglietto in questo caso. ho pensato di consigliarlo anche io, ma il readpezzo mi ha fermato. a mio avviso, la convalida dell'input dell'utente, che è ciò che sembra essere, i casemodelli non dovrebbero essere in cima all'elenco di valutazione e dovrebbero piuttosto essere eliminati al *modello predefinito in modo tale che gli unici risultati che vi raggiungono siano garantiti accettabili. tuttavia, poiché il problema è l'ordine di analisi / espansione, una seconda valutazione potrebbe essere ciò che è richiesto.
Mikeserv,

Risposte:


13

Il manuale di bash afferma:

la parola maiuscola nell'elenco [[(] modello [| modello] ...) ;; ] ... esac

Ogni modello esaminato viene espanso utilizzando l'espansione della tilde, l'espansione di parametri e variabili, la sostituzione aritmetica, la sostituzione dei comandi e la sostituzione dei processi.

Nessuna «espansione del nome del percorso»

Pertanto: un modello NON viene espanso con «Espansione percorso».

Pertanto: un modello NON può contenere "|" dentro. Solo: due motivi possono essere uniti con "|".

Questo funziona:

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

Utilizzo di «Globbing esteso»

Tuttavia, wordè associato patternall'uso delle regole di «Espansione del nome di percorso».
E «Extended Globbing» qui , qui e, qui consente l'uso di modelli alternati ("|").

Questo funziona anche:

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

Contenuto della stringa

Il prossimo script di test mostra che il modello che corrisponde a tutte le righe che contengono una fooo barovunque sia '*$(foo|bar)*'o le due variabili $s1=*foo*e$s2=*bar*


Script di test:

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_

9

Puoi usare l' extglobopzione:

shopt -s extglob
string='@(foo|bar)'

Interessante; cosa rende @(foo|bar)speciale rispetto a foo|bar? Entrambi sono schemi validi che funzionano allo stesso modo quando vengono digitati letteralmente.
Chepner,

4
Ah, non importa. |non fa parte del pattern in foo|bar, fa parte della sintassi casedell'istruzione per consentire più pattern in una clausola. | fa parte del modello esteso, però.
Chepner,

3

Sono necessarie due variabili caseperché il |pipe o viene analizzato prima che i pattern vengano espansi.

v1=foo v2=bar

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

foo

I modelli di shell nelle variabili vengono gestiti in modo diverso anche quando vengono citati o non quotati:

q=?

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

not a question mark

-1

Se desideri una soluzione alternativa compatibile con il trattino, puoi scrivere:

  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"
  }'

Spiegazione: awk viene utilizzato per dividere "stringa" e testare ciascuna parte separatamente. Se "scelta" corrisponde alla parte attualmente testata p [i], il comando awk verrà terminato con "esci" nella riga 11. Per lo stesso test, viene utilizzato il "caso" della shell (all'interno di una chiamata "system") , come chiesto da Terdon. Ciò mantiene la possibilità di modificare la "stringa" di test prevista, ad esempio, in "foo * | bar" in modo che corrisponda anche a "foooo" ("modello di ricerca"), come consente il "caso" della shell. Se invece preferissi le espressioni regolari, potresti omettere il "sistema" -call e usare invece "~" o "match" di awk.


Caro downvoter, potrei imparare meglio come evitare inutili candidati alla soluzione se tu mi dicessi il motivo del tuo downvote.
Gerald Schade,
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.