L'istruzione if else equivale a logica e && o || e dove dovrei preferire l'uno all'altro?


27

Sto imparando le strutture decisionali e mi sono imbattuto in questi codici:

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


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

Entrambi si comportano allo stesso modo. Ci sono dei vantaggi nell'usare una via dall'altra?



3
Non sono equivalenti. Vedi l'eccellente risposta di Icaro. Ad esempio, considera il caso in cui ./myfile esiste ma non è leggibile.
AlexP,


Risposte:


26

No, le costruzioni if A; then B; else C; fie nonA && B || C sono equivalenti .

Con if A; then B; else C; fi, il comando Aviene sempre valutato ed eseguito (almeno viene effettuato un tentativo di esecuzione) e quindi il comando Bo il comando Cvengono valutati ed eseguiti.

Con A && B || C, è la stessa per i comandi Ae Bma diversi per C: comando Cviene valutato e eseguito se uno A fallisce o B fallisce.

Nel tuo esempio, supponi che chmod u-r ./myfile, nonostante ci [ -f ./myfile ]riuscirai, lo faraicat /home/user/myfile

Il mio consiglio: usa A && Bo A || Btutto quello che vuoi, questo rimane facile da leggere e capire e non c'è trappola. Ma se vuoi dire se ... allora ... altro ... allora usa if A; then B; else C; fi.


29

Molte persone trovano più facile comprendere la if... then... else... fiforma.

Per il a && b || c, devi essere sicuro che britorni vero. Questa è una causa di bug sottili ed è una buona ragione per evitare questo stile. Se b non ritorna vero, questi non sono gli stessi.

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

Per test e azioni molto brevi che non hanno una clausola else, la lunghezza ridotta è attraente, ad es

 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

&&e ||sono short circuiting operators, non appena il risultato è noto, vengono saltati ulteriori test non necessari. a && b || cè raggruppato come (a && b) || c. Il primo aè eseguito. Se failsè definito come non restituisce uno stato di uscita pari a 0, il gruppo (a && b)è noto faile bnon deve essere eseguito. Il ||non conosce il risultato dell'espressione quindi deve essere eseguito c. Se ha esito apositivo (restituisce zero), l' &&operatore non è ancora a conoscenza del risultato, a && bquindi deve correre bper scoprirlo. Se ha bsuccesso, allora ha a && bsuccesso e ||sa che il risultato complessivo è successo, quindi non è necessario eseguire c. Se bfallisce allora||non conosce ancora il valore dell'espressione, quindi deve essere eseguito c.


7

L'operatore && esegue il comando successivo se il comando precedente ha avuto un'esecuzione corretta, (codice di uscita restituito ($?) 0 = logico vero).

Nella forma A && B || C, viene valutato il comando (o condizione) A e se A restituisce true (esito positivo, codice di uscita 0), viene eseguito il comando B. Se A fallisce (quindi restituirà false - codice di uscita diverso da 0) e / o B fallisce (restituendo false ), verrà eseguito il comando C.

Inoltre, l' &&operatore viene utilizzato come AND in controlli delle condizioni e l'operatore ||funziona come OR nei controlli delle condizioni.

A seconda di ciò che si desidera fare con lo script, il modulo A && B || Cpuò essere utilizzato per i controlli delle condizioni come il tuo esempio o può essere utilizzato per concatenare i comandi e garantire una serie di comandi da eseguire se i comandi precedenti avevano un codice di uscita 0 riuscito .
Questo è il motivo per cui è comune vedere i comandi come:
do_something && do_something_else_that_depended_on_something.

Esempi:
apt-get update && apt-get upgrade se l'aggiornamento non riesce, l'aggiornamento non viene eseguito (ha senso nel mondo reale ...).

mkdir test && echo "Something" > test/file
La parte echo "Something"verrà eseguita solo se l' mkdir testoperazione è andata a buon fine e l'operazione ha restituito il codice di uscita 0 .

./configure --prefix=/usr && make && sudo make install
Di solito si trova sulla compilazione di lavori per concatenare i comandi dipendenti necessari insieme.

Se provi a implementare sopra le "catene" con if - allora - altrimenti avrai bisogno di molti più comandi e controlli (e quindi più codice da scrivere - più cose da andare storto) per un semplice compito.

Inoltre, tieni presente che i comandi concatenati con && e || vengono letti dalla shell da sinistra a destra. Potrebbe essere necessario raggruppare i comandi e i controlli delle condizioni tra parentesi per dipendere dal passaggio successivo dall'output corretto di alcuni comandi precedenti. Ad esempio vedi questo:

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

O un esempio di vita reale:

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

Tieni presente che alcuni comandi restituiscono codici di uscita diversi a seconda del processo eseguito o restituiscono codici diversi a seconda delle loro azioni (ad esempio il comando GNU diff, restituisce 1 se due file differiscono e 0 se non lo fanno). Tali comandi devono essere trattati con cura in && e || .

Anche solo per avere tutti i puzzle insieme, attenzione alla concatenazione dei comandi usando l' ;operatore. Con un formato A;B;Ctutti i comandi verranno eseguiti in serie, indipendentemente dal codice di uscita del comando Ae B.


1

Gran parte della confusione su questo può essere dovuta alla documentazione bash che chiama questi elenchi AND e OR . Sebbene logicamente simili a &&e ||trovati all'interno di parentesi quadre, funzionano in modo diverso.

Alcuni esempi possono illustrare questo meglio ...

NOTA: le parentesi quadre singole e doppie ( [ ... ]e [[ ... ]]) sono comandi a sé stanti che fanno un confronto e restituiscono un codice di uscita. In realtà non hanno bisogno del if.

cmda  && cmdb  || cmdc

Se cmdaesce vero, cmdbviene eseguito.
Se cmdaesce falso, cmdbNON viene eseguito, ma lo cmdcè.

cmda; cmdb  && cmdc  || cmdd

Come le cmdauscite vengono ignorate.
Se cmdbesce vero, cmdcviene eseguito.
Se cmdbesce falso, cmdcNON viene eseguito e lo cmddè.

cmda  && cmdb; cmdc

Se cmdaesce vero, cmdbviene eseguito, seguito da cmdc.
Se cmdaesce falso, cmdbNON viene eseguito ma lo cmdcè.

Eh? Perché viene cmdceseguito?
Perché per l'interprete, un punto ;e virgola ( ) e una nuova riga significano esattamente la stessa cosa. Bash vede quella riga di codice come ...

cmda  && cmdb
cmdc  

Per ottenere ciò che ci si aspetta, dobbiamo racchiudere cmdb; cmdctra parentesi graffe per renderli un Comando composto (comando di gruppo) . Il punto e virgola aggiuntivo è solo un requisito della { ...; }sintassi. Quindi otteniamo ...

cmda && { cmdb; cmdc; }
Se cmdaesce vero, cmdbviene eseguito, seguito da cmdc.
Se cmdaesce falso, nessuno dei due cmdbo cmdcviene eseguito.
L'esecuzione continua con la riga successiva.

uso

Gli elenchi di comandi condizionali sono molto utili per tornare il più presto possibile dalle funzioni ed evitare così l'interpretazione e l'esecuzione di molto codice non necessario. Restituzioni di funzioni multiple significano però che bisogna essere ossessivi nel mantenere brevi le funzioni, quindi è più facile assicurarsi che tutte le possibili condizioni siano coperte.

Ecco un esempio da alcuni codici in esecuzione ...

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
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.