È dovuto a una scelta nell'implementazione.
Eseguire lo stesso script su Solaris con ksh93
produce un comportamento diverso:
$ while /usr/bin/true ; do echo "ok" | cat ; done | exit 1
cat: write error [Broken pipe]
Ciò che fa scattare il problema è la pipeline interna, senza di essa, il ciclo termina qualunque sia la shell/OS:
$ while /usr/bin/true ; do echo "ok" ; done | exit 1
$
cat
sta ricevendo un segnale SIGPIPE sotto bash ma la shell sta comunque iterando il ciclo.
Process 5659 suspended
[pid 28801] execve("/bin/cat", ["cat"], [/* 63 vars */]) = 0
[pid 28801] --- SIGPIPE (Broken pipe) @ 0 (0) ---
Process 5659 resumed
Process 28801 detached
Process 28800 detached
--- SIGCHLD (Child exited) @ 0 (0) ---
Process 28802 attached
Process 28803 attached
[pid 28803] execve("/bin/cat", ["cat"], [/* 63 vars */]) = 0
Process 5659 suspended
[pid 28803] --- SIGPIPE (Broken pipe) @ 0 (0) ---
Process 5659 resumed
Process 28803 detached
Process 28802 detached
--- SIGCHLD (Child exited) @ 0 (0) ---
Process 28804 attached
Process 28805 attached (waiting for parent)
Process 28805 resumed (parent 5659 ready)
Process 5659 suspended
[pid 28805] execve("/bin/cat", ["cat"], [/* 63 vars */]) = 0
[pid 28805] --- SIGPIPE (Broken pipe) @ 0 (0) ---
Process 5659 resumed
Process 28805 detached
Process 28804 detached
--- SIGCHLD (Child exited) @ 0 (0) ---
Bash la documentazione indica:
La shell aspetta tutti i comandi nella pipeline per terminare prima di restituire un valore.
Ksh la documentazione indica:
Ogni comando, tranne forse l'ultimo, viene eseguito come un processo separato; la shell aspetta l'ultimo comando terminare.
POSIX afferma:
Se la pipeline non è in background (vedi Elenchi asincroni), la shell deve attendere l'ultimo comando specificato nella pipeline per il completamento e può anche attendere tutti i comandi da completare.
Questo problema mi ha infastidito per anni. Grazie a jilliagre per la spinta nella giusta direzione.
Ribadendo un po' la domanda, sulla mia macchina Linux, questo si chiude come previsto:
while true ; do echo "ok"; done | head
Ma se aggiungo una pipe, no esci come previsto:
while true ; do echo "ok" | cat; done | head
Questo mi ha frustrato per anni. Considerando la risposta scritta da jilliagre, ho trovato questa soluzione meravigliosa:
while true ; do echo "ok" | cat || exit; done | head
Q.E.D. ...
Beh, non proprio. Ecco qualcosa di un po' più complicato:
i=0
while true; do
i=`expr $i + 1`
echo "$i" | grep '0$' || exit
done | head
Questo non funziona bene. Ho aggiunto il || exit
quindi sa come terminare in anticipo, ma il primissimo echo
non corrisponde a grep
quindi il ciclo si interrompe immediatamente. In questo caso, non sei davvero interessato allo stato di uscita del grep
. La mia soluzione è aggiungere un altro cat
. Quindi, ecco uno script artificioso chiamato "tens":
#!/bin/bash
i=0
while true; do
i=`expr $i + 1`
echo "$i" | grep '0$' | cat || exit
done
Questo termina correttamente quando viene eseguito come tens | head
. Grazie a Dio.