Elle est due à un choix d'implémentation.
Exécution du même script sur Solaris avec ksh93
produit un comportement différent :
$ while /usr/bin/true ; do echo "ok" | cat ; done | exit 1
cat: write error [Broken pipe]
Ce qui déclenche le problème, c'est le pipeline interne, sans lui, la boucle se termine quel que soit le shell/OS :
$ while /usr/bin/true ; do echo "ok" ; done | exit 1
$
cat
reçoit un signal SIGPIPE sous bash mais le shell parcourt quand même la boucle.
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) ---
Frapper la documentation indique :
Le shell attend toutes les commandes dans le pipeline pour se terminer avant de renvoyer une valeur.
Ksh la documentation indique :
Chaque commande, sauf peut-être la dernière, est exécutée comme un processus séparé; le shell attend la dernière commande pour terminer.
POSIX indique :
Si le pipeline n'est pas en arrière-plan (voir Listes asynchrones), le shell attendra la dernière commande spécifié dans le pipeline pour se terminer, et peut également attendre toutes les commandes à terminer.
Ce problème m'a embêté pendant des années. Merci à jilliagre pour le coup de pouce dans la bonne direction.
En reformulant un peu la question, sur ma machine Linux, cela se termine comme prévu :
while true ; do echo "ok"; done | head
Mais si j'ajoute un tuyau, il ne le fait pas quitter comme prévu :
while true ; do echo "ok" | cat; done | head
Cela m'a frustré pendant des années. En considérant la réponse écrite par jilliagre, j'ai trouvé cette merveilleuse solution :
while true ; do echo "ok" | cat || exit; done | head
Q.E.D. ...
Eh bien, pas tout à fait. Voici quelque chose d'un peu plus compliqué :
i=0
while true; do
i=`expr $i + 1`
echo "$i" | grep '0$' || exit
done | head
Cela ne fonctionne pas correctement. J'ai ajouté le || exit
il sait donc se terminer plus tôt, mais le tout premier echo
ne correspond pas au grep
donc la boucle s'arrête tout de suite. Dans ce cas, vous n'êtes vraiment pas intéressé par le statut de sortie du grep
. Ma solution consiste à ajouter un autre cat
. Donc, voici un script artificiel appelé "dizaines":
#!/bin/bash
i=0
while true; do
i=`expr $i + 1`
echo "$i" | grep '0$' | cat || exit
done
Cela se termine correctement lorsqu'il est exécuté en tant que tens | head
. Dieu merci.