Comment puis-je utiliser une variable comme condition de cas?

17

J'essaie d'utiliser une variable composée de différentes chaînes séparées par un |comme casetest de déclaration. Par exemple:

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

Je veux pouvoir taper fooou baret exécuter la première partie de l' caseinstruction. Cependant, les deux fooet baramenez-moi au second:

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

Utiliser "$string"au lieu de $stringne fait aucune différence. L'emploi non plus string="foo|bar".

Je sais que je peux le faire de cette façon:

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

Je peux penser à différentes solutions de contournement, mais je voudrais savoir s'il est possible d'utiliser une variable comme casecondition dans bash. Est-ce possible et, si oui, comment?

terdon
la source
2
Je ne peux pas me résoudre à le suggérer comme une vraie réponse, mais puisque personne d'autre ne l'a mentionné, vous pouvez encapsuler l'instruction case dans un eval, en évitant $ choice, les parens, l'astérisque, les points-virgules et les nouvelles lignes. Moche, mais ça "marche".
Jeff Schaller
@JeffSchaller - ce n'est pas souvent une mauvaise idée, et c'est peut-être juste le ticket dans ce cas. j'ai pensé à le recommander aussi, mais le readbit m'a arrêté. à mon avis, la validation des entrées utilisateur, ce qui semble être le cas, les casemodèles ne devraient pas être en haut de la liste d'évaluation et devraient plutôt être élagués vers le *modèle par défaut de sorte que les seuls résultats qui y parviennent soient garantis acceptables. encore, parce que le problème est l'ordre d'analyse / d'extension, alors une deuxième évaluation pourrait être ce qui est demandé.
mikeserv

Réponses:

13

Le manuel bash déclare:

mot de casse dans la liste [[(] motif [| motif] ...) ;; ] ... esac

Chaque modèle examiné est développé en utilisant l'expansion tilde, l'expansion des paramètres et des variables, la substitution arithmétique, la substitution de commande et la substitution de processus.

Pas d '«extension de nom de chemin»

Ainsi: un motif n'est PAS étendu avec «Expansion de nom de chemin».

Par conséquent: un modèle ne peut PAS contenir "|" à l'intérieur. Seulement: deux modèles peuvent être joints avec le "|".

Cela marche:

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

Utilisation de «Extended Globbing»

Cependant, cela wordcorrespond à l' patternutilisation des règles «Expansion de noms de chemins».
Et «Extended Globbing» ici , ici et, ici permet l'utilisation de motifs alternés ("|").

Cela fonctionne également:

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

Contenu de chaîne

Le script de test suivant montre que le modèle qui correspond à toutes les lignes qui contiennent soit fooou barn'importe où est '*$(foo|bar)*'ou les deux variables $s1=*foo*et$s2=*bar*


Script de 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_
Communauté
la source
9

Vous pouvez utiliser l' extgloboption:

shopt -s extglob
string='@(foo|bar)'
choroba
la source
Intéressant; ce qui rend @(foo|bar)spécial par rapport à foo|bar? Les deux sont des modèles valides qui fonctionnent de la même manière lorsqu'ils sont saisis littéralement.
chepner
4
Ah laisse tomber. |ne fait pas partie du modèle dans foo|bar, il fait partie de la syntaxe de l' caseinstruction pour autoriser plusieurs modèles dans une clause. | fait partie du modèle étendu, cependant.
chepner
3

Vous avez besoin de deux variables casecar le ou le |tube est analysé avant que les modèles ne soient développés.

v1=foo v2=bar

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

foo

Les modèles de shell dans les variables sont traités différemment lorsqu'ils sont entre guillemets ou non:

q=?

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

not a question mark
mikeserv
la source
-1

Si vous voulez une solution de contournement compatible avec les tirets, vous pouvez écrire:

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

Explication: awk est utilisé pour diviser la "chaîne" et tester chaque partie séparément. Si "choix" correspond à la partie p [i] actuellement testée, la commande awk se terminera par "exit" à la ligne 11. Pour le test même, le "case" du shell est utilisé (dans un appel "system") , comme l'a demandé terdon. Cela conserve la possibilité de modifier la «chaîne» test par exemple en «foo * | bar» afin de faire correspondre également «foooo» («modèle de recherche»), comme le permet la «casse» du shell. Si vous préférez plutôt les expressions régulières, vous pouvez omettre l'appel "system" et utiliser à la place "~" ou "match" d'awk.

Gerald Schade
la source
Cher votant, je pourrais mieux apprendre à éviter les candidats à des solutions inutiles si vous me disiez la raison de votre vote négatif.
Gerald Schade