Perché l'uso di "yes" su piph bash * not * provoca loop infiniti?


16

Secondo la sua documentazione, bash attende che tutti i comandi in una pipeline abbiano terminato l'esecuzione prima di continuare

La shell attende che tutti i comandi nella pipeline terminino prima di restituire un valore.

Quindi perché il comando yes | truetermina immediatamente? Il yesloop non dovrebbe essere per sempre e impedire alla pipeline di tornare?


E una domanda secondaria: secondo le specifiche POSIX , le pipeline della shell possono scegliere di tornare dopo il termine dell'ultimo comando o attendere il completamento di tutti i comandi. Le shell comuni hanno comportamenti diversi in questo senso? Ci sono delle conchiglie dove yes | truerimarranno in loop per sempre?


yes | tee >(true) >/dev/nullfarà come ti aspetti, tra l'altro, come teecontinua fino a quando tutti gli scrittori sono morti, quindi trueuscire non lo interromperà del tutto.
Charles Duffy,

1
trueè fondamentalmente un {return 0;}programma, quindi non mi aspetto che duri a lungo, figuriamoci per sempre.
Dmitry Grigoryev,

Risposte:


33

Quando trueesce, il lato di lettura della pipe viene chiuso, ma yescontinua a provare a scrivere sul lato di scrittura. Questa condizione è chiamata "pipe interrotta" e fa sì che il kernel invii un SIGPIPEsegnale yes. Dal momento yesche non fa nulla di speciale su questo segnale, verrà ucciso. Se ignorasse il segnale, la sua writechiamata avrebbe esito negativo con codice di errore EPIPE. I programmi che lo fanno devono essere pronti a notare EPIPEe smettere di scrivere, altrimenti entreranno in un ciclo infinito.

Se lo fai strace yes | true1 puoi vedere il kernel prepararsi per entrambe le possibilità:

write(1, "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"..., 4096) = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=17556, si_uid=1000} ---
+++ killed by SIGPIPE +++

stracesta osservando gli eventi tramite l'API del debugger, che dapprima informa sulla chiamata di sistema che ritorna con un errore, quindi sul segnale. Dal yespunto di vista, tuttavia, il segnale si verifica per primo. (Tecnicamente, il segnale viene inviato dopo che il kernel ha restituito il controllo allo spazio utente, ma prima che vengano eseguite altre istruzioni macchina, quindi la funzione write"wrapper" nella libreria C non ha la possibilità di impostare errnoe tornare all'applicazione.)


1 Purtroppo, straceè specifico di Linux. La maggior parte degli Unix moderni ha un comando che fa qualcosa di simile, ma spesso ha un nome diverso, probabilmente non decodifica gli argomenti di syscall in modo completo e talvolta funziona solo per root.


3
@hugomg in quel caso, la pipe è totalmente irrilevante.
muru,

3
@hugomg perché nulla yesè collegato alla pipe.
muru,

4
È vero, questa è una dimostrazione del comportamento documentato "attendere fino al termine di tutti i comandi prima di terminare la pipeline". Impedisce solo yesdi ottenere SIGPIPE, poiché l'FD su cui sta scrivendo non è collegato a una pipe.
Tom Hunt,

2
@hugomg, è in loop per sempre allo stesso modo in cui yes >/dev/nullè in loop per sempre. Non dimostra nulla delle pipeline che non è vero anche per i comandi semplici (come il comportamento in attesa di terminazione di Tom è vero anche per i comandi semplici).
Charles Duffy,

2
@zwol: Penso che stiamo usando i nostri termini con significati leggermente diversi qui, o pensando a cose da prospettive leggermente diverse ... ma in entrambi i casi, write()(la funzione in libc) non ritorna (trasferimento del controllo al PC che lo segue) fino a dopo il gestore del segnale è in esecuzione, ma poiché il gestore del segnale termina il programma, il controllo non viene mai trasferito e quindi write()non ritorna mai. Sì, è implementato nel kernel con un xxx_write()ritorno di funzione -EPIPE, ma stiamo eseguendo il debug di un programma spazio utente e non ci interessa.
Dietrich Epp,

5

Ci sono delle conchiglie dove si | il vero loop per sempre?

È improbabile, poiché il yescomando utilizza la pipe e fallirà quando la pipe viene rotta. sleepd'altra parte, non usa la pipe, quindi:

sleep 100000000 | true

funzionerà per almeno 100000000 secondi.


2
Fai attenzione a tutte le shell moderne che non eseguono il fork dell'ultimo comando incorporato (più a destra) in una pipe e dove si truetrova un builtin. Questo vale per le versioni recenti del Bourne Shell, ksh93, zsh. Se premi ^Zquando un tale comando è in esecuzione, questo sospenderà il sonno e la shell non sarà mai in grado di recuperare senza un aiuto esterno.
schily

3
zsh 4.3.4 (i386-pc-solaris2.11) qui, quindi sembra che questo sia stato modificato di recente. Un'idea interessante, dovrò dare un'occhiata se posso implementare una soluzione simile per la Bourne Shell. C'è ancora una domanda su come funzioni e su quale gruppo di processi tty viene utilizzato come in Bourne Shell il fatto che il comando più giusto sia un builtin viene scoperto dopo che il gruppo di processi per il sonno è già stato impostato per sempre.
schily

2
@CharlesDuffy da quello che ho capito, schily mantiene una versione di sh, a cui sostiene i miglioramenti delle moderne shell. Ne ha pubblicato qui, da qualche parte.
muru,

3
La Bourne Shell negli archivi del cimelio è stata mantenuta fino al ~ 2007, ma non è mai stata resa completamente portatile in quanto contiene ancora chiamate sbrk(). Una versione portatile e mantenuta è nel pacchetto schily tools e @Charles Duffy ha già scoperto una posizione per informazioni ;-)
schily

2
@muru molte delle funzionalità che ho eseguito il backport su Bourne Shell provengono dal mio bsh(Berthold Shell di VBERTOS, una versione potenziata della memoria virtuale di UNOS - il primo clone UNIX). Bsh ottenne molte funzionalità csh nel 1984 e nel 1985, ma il meccanismo alias di UNOS era già superiore a quello di csh nel 1980. Altre nuove funzionalità di Bourne Shell provengono da POSIX, per consentirle di avvicinarsi alla conformità POSIX.
schily
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.