Opérateurs logiques multiples, ((A || B) && C) et «erreur de syntaxe près d'un jeton inattendu»

24

Je travaille avec Bash 3 et j'essaye de former un conditionnel. En C / C ++, sa mort simple: ((A || B) && C). Dans Bash, cela ne s'est pas avéré (je pense que les auteurs Git ont dû contribuer à ce code avant de passer à d'autres projets).

Cela ne fonctionne pas. Notez que ce <0 or 1>n'est pas un littéral de chaîne; cela signifie un 0 ou 1 (vient généralement de grep -i).

A=<0 or 1>
B=<0 or 1>
C=<0 or 1>
if [ [ "$A" -eq "0" ] || [ "$B" -ne "0" ] ] && [ "$C" -eqe "0" ]; then ... fi

Il en résulte:

line 322: syntax error near unexpected token `[['

J'ai ensuite essayé:

A=<0 or 1>
B=<0 or 1>
C=<0 or 1>
if [ ([ "$A" -eq "0" ]) || ([ "$B" -ne "0" ]) ] && [ "$C" -eq "0" ]; then ... fi

il en résulte:

line 322: syntax error near unexpected token `[['

Une partie du problème est que les résultats de recherche sont les exemples triviaux, et non les exemples plus complexes avec des conditions composées.

Comment effectuer un simple ((A || B) && C)dans Bash?


Je suis prêt à le dérouler et à répéter les mêmes commandes dans plusieurs blocs:

A=<0 or 1>
B=<0 or 1>
C=<0 or 1>

if [ "$A" -eq "0" ] && [ "$C" -eq "0" ]; then
    ...
elif [ "$B" -ne "0" ] && [ "$C" -eq "0" ]; then
    ... 
fi

la source

Réponses:

42

La syntaxe de bash n'est pas de type C, même si une petite partie est inspirée de C. Vous ne pouvez pas simplement essayer d'écrire du code C et vous attendre à ce qu'il fonctionne.

Le point principal d'un shell est d'exécuter des commandes. La commande open-bracket [est une commande qui effectue un seul test¹. Vous pouvez même l'écrire comme test(sans le crochet de fermeture final). Les opérateurs ||et &&sont des opérateurs shell, ils combinent des commandes , pas des tests.

Alors quand tu écris

[ [ "$A" -eq "0" ] || [ "$B" -ne "0" ] ] && [ "$C" -eq "0" ]

c'est analysé comme

[ [ "$A" -eq "0" ] ||
[ "$B" -ne "0" ] ] &&
[ "$C" -eq "0" ]

ce qui est le même que

test [ "$A" -eq "0" ||
test "$B" -ne "0" ] &&
test "$C" -eq "0"

Remarquez les parenthèses déséquilibrées? Ouais, ce n'est pas bon. Votre tentative entre parenthèses a le même problème: les crochets parasites.

La syntaxe pour regrouper les commandes est les accolades. La façon dont les accolades sont analysées nécessite une commande complète avant eux, vous devrez donc terminer la commande à l'intérieur des accolades avec une nouvelle ligne ou un point-virgule.

if { [ "$A" -eq "0" ] || [ "$B" -ne "0" ]; } && [ "$C" -eq "0" ]; then 

Il existe une autre façon d'utiliser des crochets doubles. Contrairement aux crochets simples, les crochets doubles sont une syntaxe de shell spéciale. Ils délimitent les expressions conditionnelles . À l'intérieur des crochets doubles, vous pouvez utiliser des parenthèses et des opérateurs comme &&et ||. Étant donné que les doubles crochets sont la syntaxe du shell, le shell sait que lorsque ces opérateurs sont à l'intérieur des crochets, ils font partie de la syntaxe d'expression conditionnelle, pas de la syntaxe de commande shell standard.

if [[ ($A -eq 0 || $B -ne 0) && $C -eq 0 ]]; then 

Si tous vos tests sont numériques, il existe encore un autre moyen, qui délimite les expressions artihmétiques . Les expressions arithmétiques effectuent des calculs entiers avec une syntaxe très semblable à C.

if (((A == 0 || B != 0) && C == 0)); then 

Vous pouvez trouver mon amorce de support bash utile.

[peut être utilisé en sh simple. [[et(( sont spécifiques à bash (et ksh et zsh).

¹ Il peut également combiner plusieurs tests avec des opérateurs booléens, mais cela est lourd à utiliser et présente des pièges subtils, donc je ne l'expliquerai pas.

Gilles 'SO- arrête d'être méchant'
la source
Merci Giles. Cela vaut-il pour Bash 2 à Bash 4? J'ai besoin de quelque chose de légèrement portable, mais Kusalananda a mentionné que certaines choses que je ne faisais pas sont portables (cela signifie-t-il que ce que je fais n'est pas portable?). Ici, signifie légèrement Bash 2 à Bash 4.
@jww a [[ … ]]été ajouté dans bash 2.02. ((…))a été ajouté dans bash 2.0.
Gilles 'SO- arrête d'être méchant'
@Giles - merci. Bash 2 est probablement risible pour la plupart. C'est dû à notre gouvernance et à la réticence à abandonner les anciennes plateformes. Bash 2 et GCC 3 apparaissent sur nos tests Fedora 1. Nous laisserons une ancienne plate-forme disparaître à l'occasion, mais il doit y avoir des raisons. Nous ne souscrivons pas à la politique d'abandonware d'Apple, Microsoft, Mozilla, etc.
@jww Veuillez comprendre que la priorité de ||et &&passe de [à [[et ((. L' égalité de priorité dans [(utilisation parenthèses pour contrôler l'ordre d'évaluation), mais && a une priorité plus élevée dans [[et ((que ||(comme dans C).
6

Utilisation [[:

if [[ ( "$A" -eq "0" || "$B" -ne "0" ) && "$C" -eq "0" ]]; then ...

Si vous préférez [, les travaux suivants:

if [ \( "$A" -eq "0" -o "$B" -ne "0" \) -a "$C" -eq "0" ]; then ...
Stephen Kitt
la source
3

Utilisez l' -oopérateur au lieu de votre || imbriqué. Vous pouvez également utiliser -apour remplacer && si nécessaire dans vos autres déclarations.

   EXPRESSION1 -a EXPRESSION2
          both EXPRESSION1 and EXPRESSION2 are true

   EXPRESSION1 -o EXPRESSION2
          either EXPRESSION1 or EXPRESSION2 is true


if [  "$A" -eq "0" -o "$B" -ne "0"  ] && [ "$C" -eq "0" ]; then echo success;fi
Zachary Brady
la source
Merci. Je suis tombé sur le -aet -o, mais je ne l'ai pas expérimenté. Quelqu'un sur Stack Overflow a dit de l'éviter. Je suis surtout d'accord avec eux car le script est moins lisible en les utilisant.
... mais plus portable.
Kusalananda
@Kusalananda - Merci pour les avertissements sur la portabilité. Le script doit s'exécuter sur les systèmes avec Bash 2 à Bash 4. Aurez- [[ ( "$A" -eq "0" || "$B" -ne "0" ) && "$C" -eq "0" ]]vous des problèmes avec eux?
Il n'y aura aucun problème tant que vous restez sur bashou tout autre shell qui comprend [[ ... ]].
Kusalananda