Precedenza degli operatori logici della shell &&, ||


126

Sto cercando di capire come funziona la precedenza dell'operatore logico in bash. Ad esempio, mi sarei aspettato che il seguente comando non riecheggiasse nulla.

true || echo aaa && echo bbb

Tuttavia, contrariamente alle mie aspettative, bbbviene stampato.

Qualcuno può spiegare, come posso dare un senso ai composti &&e agli ||operatori in bash?

Risposte:


127

In molti linguaggi informatici, gli operatori con la stessa precedenza sono associativi di sinistra . Cioè, in assenza di strutture di raggruppamento, le operazioni più a sinistra vengono eseguite per prime. Bash non fa eccezione a questa regola.

Questo è importante perché, in Bash, &&e ||hanno la stessa precedenza.

Quindi ciò che accade nel tuo esempio è che l'operazione all'estrema sinistra ( ||) viene eseguita per prima:

true || echo aaa

Poiché trueè ovviamente vero, l' ||operatore cortocircuita e l'intera affermazione è considerata vera senza la necessità di valutare echo aaacome ci si aspetterebbe. Ora resta da fare l'operazione più a destra:

(...) && echo bbb

Dal momento che la prima operazione è stata valutata come vera (cioè aveva uno stato di uscita 0), è come se stessi eseguendo

true && echo bbb

quindi &&non sarà cortocircuito, motivo per cui vedi bbbeco.

Avresti lo stesso comportamento con

false && echo aaa || echo bbb

Note basate sui commenti

  • Si noti che la regola di associatività sinistra è seguita solo quando entrambi gli operatori hanno la stessa precedenza. Questo non è il caso in cui si utilizzano questi operatori insieme a parole chiave come [[...]]o ((...))o si utilizzano gli operatori -oe -acome argomenti per i comandi testo [. In tali casi, AND ( &&o -a) ha la precedenza su OR ( ||o -o). Grazie al commento di Stephane Chazelas per aver chiarito questo punto.
  • Sembra che nei linguaggi simili a C e C &&abbia una precedenza più alta di quella ||che probabilmente è il motivo per cui ti aspettavi che il tuo costrutto originale si comportasse come

    true || (echo aaa && echo bbb). 

    Questo non è il caso di Bash, tuttavia, in cui entrambi gli operatori hanno la stessa precedenza, motivo per cui Bash analizza la tua espressione usando la regola di associatività a sinistra. Grazie al commento di Kevin per averlo sollevato.

  • Potrebbero esserci anche casi in cui vengono valutate tutte e 3 le espressioni. Se il primo comando restituisce uno stato di uscita diverso da zero, il ||cortocircuito non verrà eseguito e verrà eseguito il secondo comando. Se il secondo comando ritorna con uno stato di uscita zero, anche il &&cortocircuito non verrà eseguito e il terzo comando verrà eseguito. Grazie al commento di Ignacio Vazquez-Abrams per averlo sollevato.


26
Una piccola nota per aggiungere un po 'più di confusione: mentre gli operatori shell &&e ||come in cmd1 && cmd2 || cmd3hanno la stessa precedenza, &&in ((...))e [[...]]ha la precedenza su ||( ((a || b && c))is ((a || (b && c)))). Lo stesso vale per -a/ -oin test/ [e finde &/ |in expr.
Stéphane Chazelas,

16
Nei linguaggi simil-c, &&ha una precedenza più alta rispetto a quella ||, quindi il comportamento previsto del PO sarebbe accaduto. Gli utenti incauti abituati a tali lingue potrebbero non rendersi conto che in bash hanno la stessa precedenza, quindi può valere la pena sottolineare più esplicitamente che hanno la stessa precedenza in bash.
Kevin,

Si noti inoltre che ci sono situazioni in cui è possibile eseguire tutti e tre i comandi, ovvero se il comando centrale può restituire vero o falso.
Ignacio Vazquez-Abrams,

@Kevin Controlla la risposta modificata.
Joseph R.

1
Oggi, per me, il maggior successo di Google per "precedenza operatore bash" è tldp.org/LDP/abs/html/opprecedence.html ... che afferma che && ha una precedenza più alta di ||. Eppure @JosephR. è chiaramente giusto di fatto e anche di diritto. Dash si comporta allo stesso modo e cerca "precedenza" in pubs.opengroup.org/onlinepubs/009695399/utilities/… , vedo che è un requisito POSIX, quindi possiamo dipendere da esso. Tutto quello che ho trovato per la segnalazione di bug era l'indirizzo e-mail dell'autore, che è stato sicuramente spammato a morte negli ultimi sei anni. Ci proverò comunque ...
Martin Dorey il

62

Se vuoi che più cose dipendono dalle tue condizioni, raggruppale:

true || { echo aaa && echo bbb; }

Che non stampa nulla, mentre

true && { echo aaa && echo bbb; }

stampa entrambe le stringhe.


Il motivo per cui ciò accade è molto più semplice di quanto Joseph stia scoprendo. Ricorda cosa fa Bash con ||e &&. Riguarda lo stato di ritorno del comando precedente. Un modo letterale di guardare il tuo comando grezzo è:

( true || echo aaa ) && echo bbb

Il primo comando ( true || echo aaa) sta per uscire con 0.

$ true || echo aaa; echo $?
0
$ true && echo aaa; echo $?
aaa
0

$ false && echo aaa; echo $?
1
$ false || echo aaa; echo $?
aaa
0

7
+1 Per raggiungere il risultato previsto del PO. Nota che le parentesi che hai inserito (true || echo aaa) && echo bbbsono esattamente ciò che sto realizzando.
Joseph R.

1
Bello vedere @Oli in questa parte del mondo.
Braiam,

7
Nota la semicolonna ;alla fine. Senza questo, non funzionerà!
Serge Stroobandt,

2
Ero in difficoltà con questo perché mi mancava il punto e virgola finale dopo l'ultimo comando (ad esempio echo bbb;nei primi esempi). Una volta capito che mi mancava, questa risposta era esattamente quello che stavo cercando. +1 per avermi aiutato a capire come realizzare ciò che volevo!
Doktor J,

5
Vale la pena leggere per chiunque sia confuso da (...)e {...}notazione: manuale di raggruppamento di comandi
ANTARA

16

Gli operatori &&e non|| sono sostituzioni in linea esatte per if-then-else. Anche se usati con attenzione, possono fare più o meno la stessa cosa.

Un singolo test è semplice e inequivocabile ...

[[ A == A ]]  && echo TRUE                          # TRUE
[[ A == B ]]  && echo TRUE                          # 
[[ A == A ]]  || echo FALSE                         # 
[[ A == B ]]  || echo FALSE                         # FALSE

Tuttavia, il tentativo di aggiungere più test può produrre risultati imprevisti ...

[[ A == A ]]  && echo TRUE   || echo FALSE          # TRUE  (as expected)
[[ A == B ]]  && echo TRUE   || echo FALSE          # FALSE (as expected)
[[ A == A ]]  || echo FALSE  && echo TRUE           # TRUE  (as expected)
[[ A == B ]]  || echo FALSE  && echo TRUE           # FALSE TRUE   (huh?)

Perché vengono ripetuti sia FALSE che TRUE?

Quello che sta succedendo qui è che non ce ne siamo resi conto &&e ||sono operatori sovraccarichi che agiscono in modo diverso all'interno delle parentesi del test condizionale [[ ]]rispetto a quelli presenti nell'elenco AND e OR (esecuzione condizionale) che abbiamo qui.

Dalla manpage di bash (modificata) ...

elenchi

Un elenco è una sequenza di una o più condotte separate da uno degli operatori;, &, &&, o ││, e facoltativamente terminate da uno di;, &, o. Di questi operatori di elenco, && e ││ hanno la stessa precedenza, seguito da; e &, che hanno la stessa precedenza.

Una sequenza di una o più nuove righe può apparire in un elenco anziché in un punto e virgola per delimitare i comandi.

Se un comando viene terminato dall'operatore di controllo &, la shell esegue il comando in background in una subshell. La shell non attende il completamento del comando e lo stato di ritorno è 0. Comandi separati da a; vengono eseguiti in sequenza; la shell attende che ciascun comando termini a sua volta. Lo stato di ritorno è lo stato di uscita dell'ultimo comando eseguito.

Gli elenchi AND e OR sono sequenze di una o più condutture separate rispettivamente dagli operatori di controllo && e ││. Gli elenchi AND e OR vengono eseguiti con associatività a sinistra.

Un elenco AND ha il formato ...
command1 && command2
Command2 viene eseguito se, e solo se, command1 restituisce uno stato di uscita pari a zero.

Un elenco OR ha il formato ...
command1 ││ command2
Command2 viene eseguito se e solo se command1 restituisce uno stato di uscita diverso da zero.

Lo stato di ritorno degli elenchi AND e OR è lo stato di uscita dell'ultimo comando eseguito nell'elenco.

Tornando al nostro ultimo esempio ...

[[ A == B ]]  || echo FALSE  && echo TRUE

[[ A == B ]]  is false

     ||       Does NOT mean OR! It means...
              'execute next command if last command return code(rc) was false'

 echo FALSE   The 'echo' command rc is always true
              (i.e. it successfully echoed the word "FALSE")

     &&       Execute next command if last command rc was true

 echo TRUE    Since the 'echo FALSE' rc was true, then echo "TRUE"

Va bene. Se è corretto, allora perché il prossimo esempio fa eco a qualcosa?

[[ A == A ]]  || echo FALSE  && echo TRUE


[[ A == A ]]  is true

     ||       execute next command if last command rc was false.

 echo FALSE   Since last rc was true, shouldn't it have stopped before this?
                Nope. Instead, it skips the 'echo FALSE', does not even try to
                execute it, and continues looking for a `&&` clause.

     &&       ... which it finds here

 echo TRUE    ... so, since `[[ A == A ]]` is true, then it echos "TRUE"

Il rischio di errori logici quando si utilizza più di uno &&o ||in un elenco di comandi è piuttosto elevato.

raccomandazioni

Un singolo &&o ||in un elenco di comandi funziona come previsto, quindi è abbastanza sicuro. Se è una situazione in cui non è necessaria una clausola else, è possibile seguire qualcosa di simile al seguente (le parentesi graffe sono necessarie per raggruppare gli ultimi 2 comandi) ...

[[ $1 == --help ]]  && { echo "$HELP"; exit; }

Molteplici &&e ||operatori, in cui ogni comando tranne l'ultimo è un test (cioè tra parentesi [[ ]]), di solito sono anche sicuri poiché tutti, tranne l'ultimo operatore, si comportano come previsto. L'ultimo operatore si comporta più come una clausola theno else.


0

Anche questo mi ha confuso, ma ecco come penso al modo in cui Bash legge la tua affermazione (mentre legge i simboli da sinistra a destra):

  1. Simbolo trovato true. Questo dovrà essere valutato una volta raggiunta la fine del comando. A questo punto, non so se ha qualche argomento. Memorizza il comando nel buffer di esecuzione.
  2. Simbolo trovato ||. Il comando precedente è ora completo, quindi valutalo. Comando (buffer) in esecuzione: true. Risultato della valutazione: 0 (es. Successo). Memorizzare il risultato 0 nel registro "ultima valutazione". Ora considera il simbolo ||stesso. Ciò dipende dal fatto che l'ultima valutazione è diversa da zero. Registro "ultima valutazione" verificato e trovato 0. Poiché 0 non è diverso da zero, non è necessario valutare il comando seguente.
  3. Simbolo trovato echo. Può ignorare questo simbolo, perché non è stato necessario valutare il seguente comando.
  4. Simbolo trovato aaa. Questo è un argomento da comandare echo(3), ma poiché echo(3) non ha bisogno di essere valutato, può essere ignorato.
  5. Simbolo trovato &&. Ciò dipende dal risultato dell'ultima valutazione pari a zero. Registro "ultima valutazione" verificato e trovato 0. Poiché 0 è zero, è necessario valutare il comando seguente.
  6. Simbolo trovato echo. Questo comando deve essere valutato una volta raggiunta la fine del comando, poiché è stato necessario valutare il comando seguente. Memorizza il comando nel buffer di esecuzione.
  7. Simbolo trovato bbb. Questo è un argomento da comandare echo(6). Dato echoche doveva essere valutato, aggiungere bbbal buffer di esecuzione.
  8. Raggiunta la fine della linea. Il comando precedente è ora completo e doveva essere valutato. Comando (buffer) in esecuzione: echo bbb. Risultato della valutazione: 0 (es. Successo). Memorizzare il risultato 0 nel registro "ultima valutazione".

E, naturalmente, l'ultimo passaggio provoca bbbl'eco alla console.

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.