Precedenza di Pipe (|) e logica e (&&) in bash


17

Lo scenario classico con Operator Precedence, hai una linea come:

(cd ~/screenshots/ && ls screenshot* | head -n 5)

E non sai se è analizzato ((A && B) | C)o (A && B | C)...

La documentazione quasi ufficiale trovata qui non elenca la pipe nell'elenco quindi non posso semplicemente controllare nella tabella.

Inoltre, in bash, (non è solo per cambiare l'ordine delle operazioni, ma crea una subshell , quindi non sono sicuro al 100% che questa riga sia l'equivalente della riga precedente:

((cd ~/screenshots/ && ls screenshot*) | head -n 5)

Più in generale, come conoscere l' AST di una linea bash? In Python ho una funzione che mi dà l'albero in modo da poter facilmente ricontrollare l'ordine di funzionamento.


1
Potrebbe essere utile sapere che |è solo un connettore che adatta lo stdout LHS allo stdin RHS. Quindi se il cdcomando nel tuo esempio fallisce, non invierebbe alcun output head, ma headin realtà continuerebbe a executeanalizzare il nulla e non restituirà alcun output.
DopeGhoti,


1
bashsta usando un parser yacc non qualcosa di ad hoc; se lo attraversi yacc -vti fornirà y.outputuna bella grammatica che mostra quello &&e ||combina gli elenchi e gli elenchi alla fine sono costituiti da pipeline (e non viceversa); tl; dr; A && B | Cè uguale a A && { B | C; }, come previsto. Non assumere alcun ordine di esecuzione tra Be C; i comandi in una pipeline vengono eseguiti in parallelo .
mosvy,

1
Si noti che la "documentazione quasi ufficiale" a cui si fa riferimento è completamente irrilevante, in quanto riguarda gli operatori utilizzati all'interno dei [...]test e delle $((...))valutazioni aritmetiche; in particolare, ||e &&come usato con l'elenco dei comandi nel linguaggio shell hanno la stessa precedenza , a differenza dei rispettivi operatori nella Co nella valutazione aritmetica (dove si &&lega più strettamente di ||).
mosvy,

@mosvy, quel documento non dice nemmeno in quale contesto si applica la tabella, quindi sembra anche meno utile in questo senso ...
ilkkachu

Risposte:


21
cd ~/screenshots/ && ls screenshot* | head -n 5

Questo equivale a

cd ~/screenshots && { ls screenshot* | head -n 5 ; }

(il gruppo di parentesi graffe comanda insieme senza una subshell ). La precedenza di |è quindi più alta (si lega più stretta) di &&e ||. Questo è,

A && B | C

e

A || B | C

sempre significa che solo uscita di B deve essere data al C . Se necessario, puoi usare (...)o { ... ; }unire i comandi come un'unica entità per chiarire le ambiguità:

{ A && B ; } | C
A && { B | C ; } # This is the default, but you might sometimes want to be explicit

Puoi provarlo usando alcuni comandi diversi. Se corri

echo hello && echo world | tr a-z A-Z

allora otterrai

hello
WORLD

back: tr a-z A-Zmaiuscolo il suo input , e puoi vedere che solo è echo worldstato reindirizzato, mentre è echo hellopassato da solo.


Questo è definito nella grammatica della shell , anche se non in modo terribilmente chiaro: la and_orproduzione (per &&/ ||) è definita per avere aa pipelinenel suo corpo, mentre pipelinecontiene solo command, che non contiene and_or- solo la complete_commandproduzione può raggiungere and_or, ed esiste solo al livello superiore e all'interno dei corpi di costrutti strutturali come funzioni e circuiti.

Puoi applicare manualmente quella grammatica per ottenere un albero di analisi per un comando, ma Bash non fornisce nulla da solo. Non conosco alcuna shell che vada oltre a quella usata per il loro stesso analisi.

La grammatica della shell ha molti casi speciali definiti solo semi-formalmente e può essere una vera missione avere ragione. Anche Bash stesso a volte ha sbagliato , quindi gli aspetti pratici e l'ideale potrebbero essere diversi.

Esistono parser esterni che tentano di abbinare la sintassi e di produrre un albero, e di questi raccomanderò ampiamente Morbig , che tenta di essere il più affidabile.


7

TL; DR : elenca i separatori come ;. &, &&e ||decidere l'ordine di analisi.

Il manuale di bash ci dice:

Gli elenchi AND e OR sono sequenze di una o più condotte separate da && e || operatori di controllo, rispettivamente.

O come la wiki di Bash Hacker l'ha sinteticamente definita

<PIPELINE1> && <PIPELINE2>

Quindi, cd ~/screenshots/ && ls screenshot* | head -n 5 c'è una pipeline - ls screenshot* | head -n 5e un semplice comando cd ~/screenshots/. Si noti che secondo il manuale

Ogni comando in una pipeline viene eseguito come un processo separato (cioè in una subshell).

D'altra parte, (cd ~/screenshots/ && ls screenshot*) | head -n 5è diverso - hai una pipeline: a sinistra c'è la subshell e a destra hai head -n 5. In questo caso, usando la notazione di OP sarebbe(A && B) | C


Facciamo un altro esempio:

$ echo foo | false &&  echo 123 | tr 2 5
$

Qui abbiamo un elenco <pipeline1> && <pipeline2>. Poiché sappiamo che lo stato di uscita della pipeline è lo stesso dell'ultimo comando e falserestituisce uno stato negativo noto anche come non riuscito, &&non verrà eseguito sul lato destro.

$ echo foo | true &&  echo 123 | tr 2 5
153

Qui la pipeline sinistra ha lo stato di uscita corretto, quindi la pipeline destra viene eseguita e ne vediamo l'output.


Si noti che la grammatica della shell non implica l'ordine di esecuzione effettivo. Per citare una delle risposte di Gilles :

I comandi con pipe vengono eseguiti contemporaneamente. Quando esegui ps | grep ..., è la fortuna del sorteggio (o una questione di dettagli del funzionamento della shell combinato con lo scheduler fine-tuning in profondità nelle viscere del kernel) se ps o grep inizia prima, e in ogni caso continuano per eseguire contemporaneamente.

E dal manuale di bash:

Gli elenchi AND e OR vengono eseguiti con associatività a sinistra.

In base a cd ~/screenshots/ && ls screenshot* | head -n 5quello cd ~/screenshots/, verrà eseguito per primo, ls screenshot* | head -n 5se il comando precedente ha esito positivo, ma head -n 5potrebbe essere un primo processo generato anziché lsessere in una pipeline.


1
Non vedo dove la domanda ponga qualcosa sull'ordine in cui vengono generate le cose.
Michael Homer,

"Non esiste alcuna precedenza alla quale si genera per prima, cd ~/screenshots/ && ls screenshot*oppure head -n 5" Bene, sì, è il caso ((cd ~/screenshots/ && ls screenshot*) | head -n 5). Ma non dice nulla sul caso ambiguo cd ~/screenshots/ && ls screenshot* | head -n 5che sembra essere il punto della domanda (con o senza parentesi circostante).
ilkkachu,

@ilkkachu Giusto, ma le seguenti frasi lo toccano. cd ~/screenshots/ && ls screenshot* dovrebbe essere elaborato per primo perché le &&liste sono più alte nell'ordine di precedenza (almeno sulla base della risposta di l0b0). Dove pensi che dovrei migliorare la risposta?
Sergiy Kolodyazhnyy,

@SergiyKolodyazhnyy, beh, dato che la domanda ha entrambi (cd && ls | head)e ((cd && ls) | head), potrebbe essere utile essere espliciti su quale intendi.
ilkkachu,

@ilkkachu OK, lo eliminerò per ora e lo modificherò nel frattempo
Sergiy Kolodyazhnyy

3

Ecco dove è specificato in bash(1):

SHELL GRAMMAR
[...]
   Pipelines
       A  pipeline  is  a sequence of one or more commands separated by one of
       the control operators | or |&.
[...]
   Lists
       A list is a sequence of one or more pipelines separated by one  of  the
       operators ;, &, &&, or ||, and optionally terminated by one of ;, &, or
       <newline>.

Quindi, &&separa le condutture.


2

Puoi solo provare echo hello && echo world | less. Vedrai che |ha una precedenza maggiore (Una pipeline di comandi è un comando). Lì per il tuo secondo esempio NON è lo stesso. Tuttavia, poiché cdnon ha output, non vedrai alcuna differenza.

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.