2012-01-23 13 views
22

Sto usando una pipe di diversi comandi in bash. C'è un modo di configurare bash per terminare immediatamente tutti i comandi dell'intera pipeline nel caso uno dei comandi fallisca?Tubo shell: uscire immediatamente quando un comando non riesce

Nel mio caso, il primo comando, ad esempio command1, viene eseguito per un po 'finché non produce un output. Ad esempio, è possibile sostituire command1 per (sleep 5 && echo "Hello").

Ora, command1 | false non riesce dopo 5 secondi ma non immediatamente.

Questo comportamento sembra avere qualcosa a che fare con la quantità di output prodotta dal comando. Ad esempio, find/| false restituisce immediatamente.

In generale, mi chiedo perché bash si comporta in questo modo. Qualcuno può immaginare una situazione in cui è utile che il codice come command1 | non-existing-command non termini immediatamente?

PS: L'utilizzo di file temporanei non è un'opzione per me, poiché i risultati intermedi che ho intorno sono troppo grandi per essere archiviati.

PPS: Né set -eset -o pipefail sembrano influenzare questo fenomeno.

+1

Questa domanda è più adatta a http://unix.stackexchange.com. Probabilmente otterrai una buona risposta lì. – dogbane

risposta

16

La documentazione bash dice nella sua section about pipelines:

Ogni comando in una pipeline è eseguito nel proprio subshell [...]

"A suo subshell" significa che una nuova viene generato il processo bash, che quindi esegue il comando effettivo. Ogni subshell inizia correttamente, anche quando determina immediatamente che il comando che viene richiesto di eseguire non esiste.

Questo spiega perché l'intero tubo può essere impostato correttamente anche quando uno dei comandi non ha senso. Bash non controlla se è possibile eseguire ogni comando, lo delegherà alle sottochiavi. Ciò spiega anche perché, ad esempio, il comando nonexisting-command | touch hello genererà un errore "comando non trovato", ma il file hello verrà comunque creato.

Nella stessa sezione, si dice anche:

Le shell aspetta tutti i comandi nella pipeline terminino prima di restituire un valore.

In sleep 5 | nonexisting-command, come A.H. sottolineato, il sleep 5 termina dopo 5 secondi, non immediatamente, quindi la shell anche attendere 5   secondi.

Non so perché l'implementazione è stata eseguita in questo modo. In casi come il tuo, il comportamento non è sicuramente come ci si aspetterebbe.

In ogni caso, un po 'brutto soluzione è utilizzare FIFO:

mkfifo myfifo 
./long-running-script.sh > myfifo & 
whoops-a-typo < myfifo 

Qui, il long-running-script.sh viene avviata e poi gli script fallisce immediatamente sulla riga successiva. Usando FIFO mutiple, questo potrebbe essere esteso a pipe con più di due comandi.

4

sleep 5 non produce alcuna uscita fino al termine, mentre find / produce immediatamente l'output che bash tenta di inviare pipe a false.

+0

È vero, la domanda è buona ma ha esempi scarsamente scelti. –

+0

@Jaypal: Sono d'accordo, che l'esempio potrebbe essere fuorviante. Ho modificato il post e spero che ora sia più chiaro. – Tobi

+0

@Dan: Sì, ma in realtà non risponde alla mia domanda. Voglio sapere se posso fare in modo che bash interrompa tutti i comandi in una pipeline dopo che un comando ha fallito. – Tobi

2

find/|false non riesce più veloce perché la prima chiamata di sistema write(2) da find fallisce con l'errore EPIPE (tubo rotto). Questo perché false è già stato terminato e quindi la pipe tra questi due comandi è stata chiusa già su un lato.

Se find ignorasse quell'errore (potrebbe farlo in teoria), anche "fallirebbe lentamente".

(sleep 5 && echo "Hello") | false è "fallire lento", perché la prima parte, sleep, non "verifica" la pipe scrivendo su di essa. Dopo 5 secondi, lo echo riceve anche l'errore EPIPE. Se questo errore termina la prima parte in questo caso o no non è importante per la domanda.

3

Il primo programma non sa se il secondo viene terminata o meno fino a che cerca di scrivere una data nel tubo. Nel caso in cui il secondo sia terminato, il primo riceve SIGPIPE che di solito causa l'uscita immediata.

È possibile forzare la prima riga di output per essere convogliata immediatamente dopo a fissare, in questo modo:

(sleep 0.1; echo; command1) | command2 

Questa 100ms sonno ha lo scopo di aspettare fino a quando possibile uscita command2 destra dopo l'avvio. Ovviamente, se il comando2 termina dopo 2 secondi e il comando 1 sarà silente per 60 secondi, l'intero comando della shell tornerà solo dopo 60.1 secondi.

Problemi correlati