Pourquoi l'utilisation de «oui» sur les pipelines bash * ne * cause-t-elle pas des boucles infinies?

16

Selon sa documentation, bash attend que toutes les commandes d'un pipeline aient fini de s'exécuter avant de continuer

Le shell attend que toutes les commandes du pipeline se terminent avant de renvoyer une valeur.

Alors pourquoi la commande se yes | truetermine-t-elle immédiatement? La yesboucle ne devrait-elle pas indéfiniment et empêcher le pipeline de revenir?


Et une sous-question: selon la spécification POSIX , les pipelines shell peuvent choisir de retourner après la fin de la dernière commande ou d'attendre que toutes les commandes se terminent. Les coquilles communes ont-elles un comportement différent dans ce sens? Y a-t-il des obus yes | truequi boucleront pour toujours?

hugomg
la source
yes | tee >(true) >/dev/nullfera ce que vous attendez, btw, comme cela teecontinue jusqu'à ce que tous les écrivains soient morts, donc la truesortie ne le perturbera pas complètement.
Charles Duffy
1
trueest fondamentalement un {return 0;}programme, donc je ne m'attendrais pas à ce qu'il fonctionne pendant longtemps, et encore moins pour toujours.
Dmitry Grigoryev

Réponses:

33

À la truesortie, le côté lecture du tuyau est fermé, mais yescontinue d'essayer d'écrire sur le côté écriture. Cette condition est appelée "pipe cassée", et elle amène le noyau à envoyer un SIGPIPEsignal à yes. Puisque yesne fait rien de spécial sur ce signal, il sera tué. S'il ignorait le signal, son writeappel échouerait avec un code d'erreur EPIPE. Les programmes qui font cela doivent être prêts à remarquer EPIPEet à arrêter d'écrire, sinon ils entreront dans une boucle infinie.

Si vous faites strace yes | true1, vous pouvez voir le noyau se préparer pour les deux possibilités:

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 +++

stracesurveille les événements via l'API de débogage, qui lui indique d'abord le retour de l'appel système avec une erreur, puis le signal. Du yespoint de vue, cependant, le signal arrive en premier. (Techniquement, le signal est délivré après que le noyau retourne le contrôle dans l'espace utilisateur, mais avant que d'autres instructions machine ne soient exécutées, donc la fonction write"wrapper" de la bibliothèque C n'a pas la possibilité de définir errnoet de revenir à l'application.)


1 Malheureusement, il straceest spécifique à Linux. La plupart des Unix modernes ont une commande qui fait quelque chose de similaire, mais elle a souvent un nom différent, elle ne décode probablement pas les arguments syscall aussi complètement, et parfois elle ne fonctionne que pour root.

casey
la source
3
@hugomg dans ce cas, le tuyau est totalement hors de propos.
muru
3
@hugomg parce que rien yesn'est connecté au tuyau.
muru
4
C'est vrai, c'est une démonstration du comportement documenté "attendez que toutes les commandes se terminent avant de terminer le pipeline". Cela empêche simplement yesd'obtenir SIGPIPE, car le FD sur lequel il écrit n'est pas connecté à un tuyau.
Tom Hunt,
2
@hugomg, c'est boucler pour toujours de la même manière que yes >/dev/nullpour toujours. Il ne montre rien du tout sur les pipelines qui ne soit pas également vrai pour les commandes simples (comme le comportement en attente de terminaison que Tom souligne est également vrai pour les commandes simples).
Charles Duffy
2
@zwol: Je pense que nous utilisons ici nos termes avec des significations légèrement différentes, ou que nous pensons à des choses sous des perspectives légèrement différentes… mais dans les deux cas, write()(la fonction dans libc) ne revient pas (contrôle du transfert vers le PC qui le suit) qu'après le gestionnaire de signaux s'est exécuté, mais puisque le gestionnaire de signaux termine le programme, le contrôle n'est jamais transféré et write()ne revient donc jamais. Oui, cela est implémenté dans le noyau en ayant un xxx_write()retour de fonction -EPIPE, mais nous déboguons un programme d'espace utilisateur et ne nous intéressons pas à cela.
Dietrich Epp du
5

Y a-t-il des coquilles où oui | vrai va boucler pour toujours?

Peu probable, car la yescommande utilise le tuyau, et il échouera lorsque le tuyau sera cassé. sleepd'autre part, n'utilise pas le tuyau, donc:

sleep 100000000 | true

fonctionnera pendant 100000000 secondes, au moins.

muru
la source
2
Soyez prudent sur tous les shells modernes qui ne bifurquent pas pour la dernière commande intégrée (la plus à droite) dans un tuyau et où se truetrouve une commande intégrée. Ceci est valable pour les versions récentes du Bourne Shell, ksh93, zsh. Si vous frappez ^Zlorsqu'une telle commande est en cours d'exécution, cela suspendra le sommeil et le shell ne pourra jamais récupérer sans aide externe.
schily
3
zsh 4.3.4 (i386-pc-solaris2.11) ici, il semble donc que cela ait été modifié récemment. Idée intéressante, je devrai voir si je peux implémenter un correctif similaire pour le Bourne Shell. Il y a encore une question de savoir comment cela fonctionne, et quel groupe de processus tty est utilisé, comme dans le Bourne Shell, le fait que la commande la plus à droite est une fonction intégrée est découvert après que le groupe de processus pour le sommeil a déjà été configuré pour toujours.
schily
2
@CharlesDuffy d'après ce que je comprends, schily maintient une version de sh, à laquelle il rétroporte les améliorations des shells modernes. Il en a parlé ici, quelque part.
muru
3
Le Bourne Shell dans les archives anciennes a été conservé jusqu'en 2007 mais n'a jamais été rendu entièrement portable car il contient toujours des appels à sbrk(). Une version portable et maintenue est dans le paquet d'outils schily et @Charles Duffy a déjà découvert un emplacement pour obtenir des informations ;-)
schily
2
@muru, bon nombre des fonctionnalités que j'ai rétroportées vers le Bourne Shell proviennent de mon bsh(Berthold Shell de VBERTOS, une version améliorée de la mémoire virtuelle d'UNOS - le premier clone UNIX). Bsh a obtenu de nombreuses fonctionnalités csh en 1984 et 1985, mais le mécanisme d'alias d'UNOS était déjà supérieur à celui de csh en 1980. D'autres nouvelles fonctionnalités de Bourne Shell proviennent de POSIX, pour lui permettre d'approcher la conformité POSIX.
schily