Différence entre [[expr1 || expr2]] et [[expr1]] || [[expr2]]


6

Considérons deux expressions conditionnelles expr1et expr2, par exemple $i -eq $jet $k -eq $l. Nous pouvons l'écrire de bashplusieurs façons. Voici deux possibilités

[[ expr1 || expr2 ]]

[[ expr1 ]] || [[ expr2 ]]

Je suis presque sûr d'avoir vu des recommandations selon lesquelles la seconde devrait être préférée, mais je ne trouve aucune preuve à l'appui.

Voici un exemple de script qui semble démontrer qu'il n'y a pas de différence:

for i in 0 1
do
  for j in 0 1
  do
    for k in 0 1
    do
      for l in 0 1
      do
        if [[ $i -eq $j || $k -eq $l ]]; then printf "1-yes\t"; else printf "1-no\t"; fi
        if [[ $i -eq $j ]] || [[ $k -eq $l ]]; then printf "2-yes\n"; else printf "2-no\n"; fi
      done
    done
  done
done

et sortie montrant que les deux constructions de condition produisent le même résultat:

1-yes   2-yes
1-yes   2-yes
1-yes   2-yes
1-yes   2-yes
1-yes   2-yes
1-no    2-no
1-no    2-no
1-yes   2-yes
1-yes   2-yes
1-no    2-no
1-no    2-no
1-yes   2-yes
1-yes   2-yes
1-yes   2-yes
1-yes   2-yes
1-yes   2-yes

Y a-t-il un avantage à utiliser une construction par rapport à l'autre?

Pour les points bonus, même question mais généralisant à plusieurs conditions en utilisant ||et &&. Par exemple [[ expr1 && expr2 || expr3 ]],.


En ce qui concerne la raison pour laquelle les recommandations de ce type sont souvent écrites. [ou test( pas [[ ), recherchez les OBbalises (liées à la définition "obsolète") dans la spécification POSIX pourtest . Dans test, il y a des cas pathologiques où il peut être impossible de dire si (ou )est censé avoir une signification syntaxique pour la commande de test ou une chaîne à tester, donc les personnes qui ignorent les marqueurs d'obsolescence et utilisent cette syntaxe peuvent en fait avoir besoin du contraire - "x$foo"pratique obsolète ; avec [[, ce n'est pas un problème.
Charles Duffy

Réponses:


7

Je pense que la recommandation que vous avez vue était pour POSIX sh et / ou la testcommande qui se double de la [commande, plutôt que la [[construction qui est apparue dans ksh (merci Stéphane Chazelas pour l'astuce) et est également utilisée par exemple dans bash, zsh et quelques autres coquilles.

Dans la plupart des langages, comme C, lorsqu'une clause est déjà connue pour être vraie ou fausse, il n'est pas nécessaire d'évaluer les parties restantes en fonction de l'opération: si vrai pas après une logique ou la suivant, si faux pas après une logique et , etc. Ceci permet bien sûr par exemple de s'arrêter lorsqu'un pointeur est NULL et de ne pas essayer de le déréférencer dans la clause suivante.

Mais la construction de sh[ expr1 -o expr2 ] (y compris l'implémentation de bash) ne fait pas cela: elle évalue toujours les deux côtés, quand on voudrait que seulement expr1 soit évalué. Cela pourrait avoir été fait pour la compatibilité avec l' testimplémentation de la commande. En revanche, sh's ||et &&do suivent le principe habituel: non évalué s'il ne modifie pas le résultat.

La différence à noter serait donc:

: > /tmp/effect #clear effect
if [ 1 -eq 1 -o $(echo 1; echo or-was-evaluated > /tmp/effect) -eq 1 ]; then
    echo true;
fi
cat /tmp/effect

ce qui donne:

true
or-was-evaluated

Ci-dessus, chacun [aurait pu être remplacé par /usr/bin/[un alias de la testcommande, utilisé avant d' [être intégré aux obus.

Alors que les deux constructions suivantes:

: > /tmp/effect #clear effect
if [ 1 -eq 1 ] || [ $(echo 1; echo or-was-evaluated > /tmp/effect) -eq 1 ]; then
    echo true;
fi
cat /tmp/effect

ou

: > /tmp/effect #clear effect
if [[ 1 -eq 1 || $(echo 1; echo or-was-evaluated > /tmp/effect) -eq 1 ]]; then
    echo true;
fi
cat /tmp/effect

Ne cède que trueet laisse effectvide: ||se comporte correctement et [[corrige également ce problème.


MISE À JOUR:

Comme l'a commenté @ StéphaneChazelas, j'ai raté plusieurs différences liées à la question initiale. Je vais mettre ici le plus important (du moins pour moi): la priorité des opérateurs.

Bien que le shell ne considère pas la priorité:

if true || true && false; then
    echo true
else
    echo false
fi

donne (car il n'y a pas de priorité et donc true || trueest d' abord évalué, puis && false):

false

à [[ ]]l' intérieur de l' &&opérateur a priorité sur ||:

if [[ 1 -eq 1 || 1 -eq 1 && 1 -eq 0 ]]; then
    echo true
else
    echo false
fi

rendements (car 1 -eq 1 && 1 -eq 0est groupé et est donc le 2ème membre de ||):

true

au moins pour ksh , bash , zsh .

A donc [[ ]]amélioré le comportement sur les deux [ ]et les opérateurs logiques shell direct.


1
[ x -o y ]n'a pas à évaluer les deux côtés. Bien que GNU testou le programme [intégré de bashdo, vous ne trouverez pas avec strace zsh -c '[ x -o -f /x ]'cela [n'essaie pas de stat () /x. Même chose avec mksh. Mais il est vrai que -oet -asont gravement endommagés, ne peuvent pas être utilisés de manière fiable et sont déconseillés par POSIX.
Stéphane Chazelas

1
Une différence est qu'à l'intérieur [[...]], la &&priorité est plus élevée que ||, tandis qu'à l'extérieur, ils ont la même priorité. Comparer ksh -c '[[ x || x && "" ]]'vssh -c 'true || true && false'
Stéphane Chazelas

1
Notez que le standard [/ testutilitaire n'a pas d' ==opérateur. L'opérateur d'égalité est =(notez qu'à l'intérieur de ksh [[...]], =/ ==ne sont pas des opérateurs d'égalité, mais des opérateurs de correspondance de modèle).
Stéphane Chazelas

1
C'est l'inverse. &&a la priorité sur ||inside [[...]](ou ((...))) mais pas sur les opérateurs shell &&et ||. [[ x || y && z ]]is x || (y && z)while x || y && zis (x || y) && z(opérateurs évalués de gauche à droite sans priorité). Similaire à la façon dont la *priorité est sur +( 1 + 2 * 3est 1 + (2 * 3)) mais +et -a la même priorité.
Stéphane Chazelas

1
Si &&a priorité sur ||, alors il devrait être true || (true && false), au lieu de(true || true) && false
Prvt_Yadav
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.