Wie kann ich eine Variable als Fallbedingung verwenden?

17

Ich versuche, eine Variable zu verwenden, die aus verschiedenen Zeichenfolgen besteht, die mit einem |als caseAnweisungstest getrennt sind. Beispielsweise:

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

Ich möchte den ersten Teil der Anweisung eingeben foooder barausführen können case. Aber beide foound barbring mich zum zweiten:

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

Verwenden "$string"statt $stringmacht keinen Unterschied. Weder tut mit string="foo|bar".

Ich weiß, dass ich es so machen kann:

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

Ich kann mir verschiedene Problemumgehungen vorstellen, möchte aber wissen, ob es möglich ist, eine Variable als caseBedingung in bash zu verwenden. Ist es möglich und wenn ja, wie?

terdon
quelle
2
Ich kann mich nicht dazu bringen , es als eine wirkliche Antwort vorschlagen, aber da niemand sonst es erwähnt hat, man könnte die Case - Anweisung in einem wickelt eval, $ Wahl zu entkommen, die Pars, Stern, Semikolons und Zeilenumbrüche. Hässlich, aber es "funktioniert".
Jeff Schaller
@ JeffSchaller - es ist nicht oft eine schlechte Idee, und ist vielleicht nur das Ticket in diesem Fall. Ich überlegte, es auch zu empfehlen, aber das readbisschen hielt mich auf. meiner Meinung nach der Validierung von Benutzereingaben, die sind , was dies zu sein scheint, caseMuster sollte nicht an der Spitze der Auswertungsliste sein, und eher das sollte beschneidet nach unten *Standardmustern , so dass die einzigen Ergebnisse , die es zu erreichen sind akzeptabel garantiert. Da es sich jedoch um eine Analyse- / Erweiterungsreihenfolge handelt, kann eine zweite Bewertung erforderlich sein.
mikeserv

Antworten:

13

Das Bash-Handbuch besagt:

case word in [[(] pattern [| pattern] ...) list ;; ] ... esac

Jedes untersuchte Muster wird durch Tildeerweiterung, Parameter- und Variablenerweiterung, arithmetische Substitution, Befehlssubstitution und Prozesssubstitution erweitert.

Nein «Pfadnamenerweiterung»

Also: Ein Muster wird NICHT mit «Pfadnamenerweiterung» erweitert.

Deshalb: Ein Muster darf NICHT "|" enthalten. Innerhalb. Nur: Mit dem "|" können zwei Muster verbunden werden.

Das funktioniert:

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

Verwendung von «Extended Globbing»

Allerdings wordwird mit abgestimmt patternmit «Pathname Expansion» Regeln.
Und «Extended Globbing» hier , hier und hier erlaubt die Verwendung alternierender ("|") Muster.

Das funktioniert auch:

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

String-Inhalt

Das nächste Testskript zeigt, dass das Muster, das mit allen Zeilen übereinstimmt, die entweder foooder barirgendwo enthalten, '*$(foo|bar)*'oder die beiden Variablen $s1=*foo*und ist$s2=*bar*


Testskript:

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_
Gemeinschaft
quelle
9

Sie können die extglobOption verwenden:

shopt -s extglob
string='@(foo|bar)'
Choroba
quelle
Interessant; Was macht das @(foo|bar)Besondere im Vergleich zu foo|bar? Beides sind gültige Muster, die bei der wörtlichen Eingabe gleich funktionieren.
Chepner
4
Ah vergiss es. |ist nicht Teil des Musters in foo|bar, sondern Teil der Syntax der caseAnweisung, um mehrere Muster in einer Klausel zuzulassen. | ist jedoch Teil des erweiterten Musters.
Chepner
3

Sie benötigen zwei Variablen, caseda die |Pipe or analysiert wird, bevor die Muster erweitert werden.

v1=foo v2=bar

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

foo

Shell-Muster in Variablen werden auch in Anführungszeichen oder ohne Anführungszeichen unterschiedlich behandelt:

q=?

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

not a question mark
mikeserv
quelle
-1

Wenn Sie ein Dash-kompatibles Workaround wünschen, können Sie Folgendes schreiben:

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

Erläuterung: awk wird verwendet, um "string" aufzuteilen und jedes Teil separat zu testen. Wenn "choice" mit dem aktuell getesteten Teil p [i] übereinstimmt, wird der awk-Befehl in Zeile 11 mit "exit" beendet. Für den Test wird der "case" der Shell verwendet (innerhalb eines "system" -Aufrufs). , wie von terdon gefragt. Dadurch bleibt die Möglichkeit erhalten, die beabsichtigte Test- "Zeichenfolge" beispielsweise in "foo * | bar" zu ändern, um sie auch für "foooo" ("Suchmuster") abzugleichen, wie es die "Groß- / Kleinschreibung" der Shell zulässt. Wenn Sie stattdessen reguläre Ausdrücke bevorzugen, können Sie den "system" -Aufruf weglassen und stattdessen awks "~" oder "match" verwenden.

Gerald Schade
quelle
Lieber Downvoter, ich könnte besser lernen, unnütze Lösungskandidaten zu vermeiden, wenn Sie mir den Grund für Ihre Downvote nennen würden.
Gerald Schade