L'instruction if else est-elle équivalente à logique et && ou || et où devrais-je préférer l'un à l'autre?

27

J'apprends les structures de prise de décision et je suis tombé sur ces codes:

if [ -f ./myfile ]
then
     cat ./myfile
else
     cat /home/user/myfile
fi


[ -f ./myfile ] &&
cat ./myfile ||
cat /home/user/myfile

Les deux se comportent de la même façon. Y a-t-il des avantages à utiliser dans un sens ou dans l'autre?

Subhaa Chandar
la source
3
Ils ne sont pas équivalents. Voir l'excellente réponse d'Icare. Par exemple, considérons le cas où ./myfile existe mais n'est pas lisible.
AlexP

Réponses:

26

Non, les constructions if A; then B; else C; fiet neA && B || C sont pas équivalentes .

Avec if A; then B; else C; fi, la commande Aest toujours évaluée et exécutée (au moins une tentative d'exécution est effectuée), puis la commande Bou la commande Csont évaluées et exécutées.

Avec A && B || C, c'est la même chose pour les commandes Aet Bmais différent pour C: la commande Cest évaluée et exécutée en cas d' A échec ou d' B échec.

Dans votre exemple, supposez que vous chmod u-r ./myfile, malgré les [ -f ./myfile ]succès, vouscat /home/user/myfile

Mon conseil: utilisez A && Bou A || Btout ce que vous voulez, cela reste facile à lire et à comprendre et il n'y a pas de piège. Mais si vous voulez dire si ... alors ... sinon ... alors utilisez if A; then B; else C; fi.

xhienne
la source
29

La plupart des gens trouvent plus facile de comprendre le if... then... else... fiformulaire.

Pour le a && b || c, vous devez être sûr que cela brenvoie vrai. Ceci est une cause de bugs subtils et est une bonne raison d'éviter ce style. Si b ne retourne pas vrai, ce ne sont pas les mêmes.

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

Pour les tests et actions très courts qui n'ont pas de clause else, la longueur raccourcie est intéressante, par exemple

 die(){ printf "%s: %s\n" "$0" "$*" >&2 ; exit 1; }

 [ "$#" -eq 2] || die "Needs 2 arguments, input and output"

 if [ "$#" -ne 2 ] ; then
     die "Needs 2 arguments, input and output"
 fi

&&et ||sont short circuiting operators, dès que le résultat est connu, d'autres tests inutiles sont ignorés. a && b || cest groupé comme (a && b) || c. Le premier aest exécuté. S'il failsest défini comme ne renvoyant pas un état de sortie de 0, alors le groupe (a && b)est connu failet bn'a pas besoin d'être exécuté. Le ||ne connaît pas le résultat de l'expression et doit donc s'exécuter c. Si aréussit (renvoie zéro), l' &&opérateur ne connaît pas encore le résultat de a && bdonc doit s'exécuter bpour le découvrir. Si bréussit, a && bréussit et ||sait que le résultat global est un succès, il n'est donc pas nécessaire de l'exécuter c. Si béchoue alors||ne connaît toujours pas la valeur de l'expression, il doit donc s'exécuter c.

icare
la source
7

L'opérateur && exécute la commande suivante si la commande précédente a réussi, (code de sortie retourné ($?) 0 = logique vrai).

Dans la forme A && B || C, la commande (ou la condition) A est évaluée et si A renvoie vrai (succès, code de sortie 0) alors la commande B est exécutée. Si A échoue (retourne donc faux - code de sortie autre que 0) et / ou B échoue (retourne faux ), alors la commande C sera exécutée.

L' &&opérateur est également utilisé comme ET dans les vérifications de condition et l'opérateur ||fonctionne comme OU dans les vérifications de condition.

Selon ce que vous voulez faire avec votre script, le formulaire A && B || Cpeut être utilisé pour des vérifications de conditions comme votre exemple ou peut être utilisé pour chaîner des commandes et garantir une série de commandes à exécuter si les commandes précédentes avaient un code de sortie 0 réussi .
Voilà pourquoi il est fréquent de voir des commandes telles que :
do_something && do_something_else_that_depended_on_something.

Exemples:
apt-get update && apt-get upgrade si la mise à jour échoue, la mise à niveau n'est pas exécutée (est logique dans le monde réel ...).

mkdir test && echo "Something" > test/file
La partie echo "Something"ne sera exécutée qu'en cas de mkdir testsuccès et l'opération a renvoyé le code de sortie 0 .

./configure --prefix=/usr && make && sudo make install
Habituellement trouvé lors de la compilation de travaux pour enchaîner les commandes dépendantes nécessaires.

Si vous essayez d'implémenter les "chaînes" ci-dessus avec if - alors - sinon vous aurez besoin de beaucoup plus de commandes et de vérifications (et donc plus de code à écrire - plus de choses à se tromper) pour une tâche simple.

Gardez également à l'esprit que les commandes chaînées avec && et || sont lus par shell de gauche à droite. Vous devrez peut-être regrouper les commandes et les vérifications de condition avec des crochets pour dépendre de la prochaine étape de la sortie réussie de certaines commandes précédentes. Par exemple, voyez ceci:

root@debian:$ true || true && false;echo $?
1 
#read from left to right
#true OR true=true AND false = false = exit code 1=not success

root@debian:$ true || (true && false);echo $?
0 
# true OR (true AND false)=true OR false = true = exit code 0 = success

Ou un exemple réel:

root@debian:$ a=1;b=1;c=1;[[ $a -eq 1 ]] || [[ $b -eq 1 ]] && [[ $c -eq 2 ]];echo $?
1 
#condition $a = true OR condition b = true AND condition $c = false
#=> yields false as read from left to right, thus exit code=1 = not ok

root@debian:$ a=1;b=1;c=1;[[ $a -eq 1 ]] || [[ $b -eq 1 && $c -eq 2 ]];echo $?
0 
#vars b and c are checked in a group which returns false, 
#condition check of var a returns true, thus true OR false yields true = exit code 0

Gardez à l'esprit que certaines commandes renvoient des codes de sortie différents en fonction du processus exécuté ou retournent des codes différents en fonction de leurs actions (par exemple, la commande GNU diffrenvoie 1 si deux fichiers diffèrent et 0 si ce n'est pas le cas). Ces commandes doivent être traitées avec soin dans && et || .

Aussi juste pour avoir tout le puzzle ensemble, gardez à l'esprit la concaténation des commandes à l'aide de l' ;opérateur. Avec un format A;B;Ctoutes les commandes seront exécutées en série quel que soit le code de sortie de la commande Aet B.

George Vasiliou
la source
1

Une grande partie de la confusion à ce sujet peut être due à la documentation bash appelant ces listes AND et OR . Bien qu'ils soient logiquement similaires à &&et ||trouvés entre crochets, ils fonctionnent différemment.

Quelques exemples peuvent illustrer ce mieux ...

REMARQUE: les crochets simples et doubles ( [ ... ]et [[ ... ]]) sont des commandes à part entière qui effectuent une comparaison et renvoient un code de sortie. Ils n'ont pas réellement besoin de if.

cmda  && cmdb  || cmdc

Si cmdaquitte vrai, cmdbest exécuté.
Si cmdaexit false, cmdbn'est PAS exécuté, mais l' cmdcest.

cmda; cmdb  && cmdc  || cmdd

Comment les cmdasorties sont ignorées.
Si cmdbquitte vrai, cmdcest exécuté.
Si cmdbexit false, cmdcn'est PAS exécuté et l' cmddest.

cmda  && cmdb; cmdc

Si cmdaquitte vrai, cmdbest exécuté, suivi de cmdc.
Si cmdaexit false, cmdbn'est PAS exécuté mais l' cmdcest.

Hein? Pourquoi est cmdcexécuté?
Parce que pour l'interprète, un point-virgule ( ;) et une nouvelle ligne signifient exactement la même chose. Bash voit cette ligne de code comme ...

cmda  && cmdb
cmdc  

Pour réaliser ce qui est attendu, nous devons cmdb; cmdcmettre entre accolades des accolades pour en faire une commande composée (commande de groupe) . Le point-virgule de fin supplémentaire n'est qu'une exigence de la { ...; }syntaxe. Nous obtenons donc ...

cmda && { cmdb; cmdc; }
Si cmdaquitte vrai, cmdbest exécuté, suivi de cmdc.
Si cmdaexit false, ni l'un cmdbni l'autre cmdcn'est exécuté.
L'exécution se poursuit avec la ligne suivante.

Usage

Les listes de commandes conditionnelles sont très utiles pour revenir le plus rapidement possible à partir des fonctions et éviter ainsi d'interpréter et d'exécuter beaucoup de code inutile. Les retours de fonctions multiples signifient cependant que l'on doit être obsédé par le fait de garder les fonctions courtes afin qu'il soit plus facile de s'assurer que toutes les conditions possibles sont couvertes.

Voici un exemple de code en cours d'exécution ...

fnInit () {
  :
  _fn="$1"
  ### fnInit "${FUNCNAME}" ...
  ### first argument MUST be name of the calling function
  #
  [[ "$2" == "--help-all" ]]  && { helpAll                      ; return 0; }
  ### pick from list of functions
  #
  [[ "$2" == "--note-all" ]]  && { noteAll                      ; return 0; }
  ### pick from notes in METAFILE
  #
  [[ "$2" == "--version"  ]]  && { versionShow "${_fn}" "${@:3}"; return 0; }
  #
  [[ "$2" == "--function" ]]  && {
    isFnLoaded "$3"           && { "${@:3}"                     ; return 0; }
    #
    errorShow functionnotfound "Unknown function:  $3"
    return 0
  }
  ### call any loaded function
  #
  [[ "$2" == "--help" || "$2" == "-h" ]]  && { noteShow "$_fn" "${@:3}"; return 0; }
  ### fnInit "${FUNCNAME}" --help or -h
  #
  return 1
}
DocSalvager
la source