¿Por qué el uso de 'yes' en las tuberías de bash * no * causa bucles infinitos?

16

Según su documentación, bash espera hasta que todos los comandos en una tubería hayan terminado de ejecutarse antes de continuar

El shell espera a que finalicen todos los comandos de la canalización antes de devolver un valor.

Entonces, ¿por qué el comando yes | truetermina de inmediato? ¿No debería el yesbucle para siempre y hacer que la tubería nunca regrese?


Y una pregunta secundaria: de acuerdo con la especificación POSIX , las tuberías de shell pueden elegir regresar después de que finalice el último comando o esperar hasta que finalicen todos los comandos. ¿Las conchas comunes tienen un comportamiento diferente en este sentido? ¿Hay algún caparazón donde yes | truese repita para siempre?

hugomg
fuente
yes | tee >(true) >/dev/nullhará lo que espera, por cierto, teehasta que todos los escritores estén muertos, por lo que truesalir no lo interrumpirá por completo.
Charles Duffy
1
truees básicamente un {return 0;}programa, por lo que no esperaría que se ejecute por mucho tiempo, y mucho menos para siempre.
Dmitry Grigoryev

Respuestas:

33

Cuando truesale, el lado de lectura de la tubería está cerrado, pero yescontinúa intentando escribir en el lado de escritura. Esta condición se denomina "tubería rota" y hace que el núcleo envíe una SIGPIPEseñal yes. Como yesno hace nada especial con esta señal, será eliminada. Si ignoró la señal, eswrite llamada fallará con el código de error EPIPE. Los programas que hacen eso deben estar preparados para notar EPIPEy dejar de escribir, o entrarán en un ciclo infinito.

Si lo haces strace yes | true1 puedes ver el kernel preparándose para ambas posibilidades:

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

straceestá viendo eventos a través de la API del depurador, que primero le informa sobre la devolución de la llamada del sistema con un error y luego sobre la señal. Sin yesembargo, desde la perspectiva, la señal ocurre primero. (Técnicamente, la señal se entrega después de que el núcleo devuelve el control al espacio del usuario, pero antes de que se ejecuten más instrucciones de la máquina, por lo que elwrite "wrapper" en la biblioteca C no tiene la oportunidad de establecer errnoy volver a la aplicación).


1 Lamentablemente, stracees específico de Linux. La mayoría de los Unix modernos tienen algún comando que hace algo similar, pero a menudo tiene un nombre diferente, probablemente no decodifique los argumentos de syscall tan a fondo, y a veces solo funciona para root.

casey
fuente
3
@hugomg en ese caso, la tubería es totalmente irrelevante.
muru
3
@hugomg porque nada yesestá conectado a la tubería.
muru
44
Es cierto, es una demostración del comportamiento documentado "espere hasta que todos los comandos terminen antes de terminar la canalización". Simplemente evita yesobtener SIGPIPE, ya que el FD en el que está escribiendo no está conectado a una tubería.
Tom Hunt
2
@hugomg, se repite para siempre de la misma manera que yes >/dev/nullse repite para siempre. No demuestra nada en absoluto sobre las tuberías que no son también ciertas para los comandos simples (como el comportamiento de espera de terminación que Tom señala también es cierto para los comandos simples).
Charles Duffy
2
@zwol: Creo que estamos usando nuestros términos con significados ligeramente diferentes aquí, o pensando en cosas desde perspectivas ligeramente diferentes ... pero en cualquier caso, write()(la función en libc) no regresa (transfiere el control a la PC después de eso) hasta después el controlador de señal se ha ejecutado, pero como el controlador de señal finaliza el programa, el control nunca se transfiere y, por lo tanto, write()nunca regresa. Sí, eso se implementa en el núcleo al tener alguna xxx_write()función de retorno -EPIPE, pero estamos depurando un programa de espacio de usuario y sin interés en eso.
Dietrich Epp
5

¿Hay conchas donde sí | cierto se repetirá para siempre?

Es poco probable, ya que el yescomando está usando la tubería, y fallará cuando la tubería se rompa. sleeppor otro lado, no usa la tubería, entonces:

sleep 100000000 | true

correrá por 100000000 segundos, al menos.

muru
fuente
2
Tenga cuidado con todos los proyectiles modernos que no se bifurcan para el último comando incorporado (más a la derecha) en una tubería y dónde trueestá un incorporado. Esto se aplica a las versiones recientes del Bourne Shell, ksh93, zsh. Si presiona ^Zcuando se ejecuta un comando de este tipo, se suspenderá la suspensión y el shell nunca podrá recuperarse sin ayuda externa.
schily
3
zsh 4.3.4 (i386-pc-solaris2.11) aquí, por lo que parece que esto se modificó recientemente. Idea interesante, tendré que ver si puedo implementar una solución similar para Bourne Shell. Todavía hay una pregunta de cómo funciona, y qué grupo de proceso se usa, como en Bourne Shell, el hecho de que el comando más correcto es un builtin se descubre después de que el grupo de proceso para el sueño ya esté configurado para siempre.
schily
2
@CharlesDuffy por lo que entiendo, schily mantiene una versión de sh, a la que respalda las mejoras de los proyectiles modernos. Ha publicado sobre esto aquí, en alguna parte.
muru
3
Bourne Shell en los archivos de la herencia se mantuvo hasta ~ 2007, pero nunca se hizo completamente portátil ya que todavía contiene llamadas a sbrk(). Una versión portátil y mantenida está en el paquete de herramientas schily y @Charles Duffy ya descubrió una ubicación para obtener información ;-)
schily
2
@muru muchas de las características que soporté en el Bourne Shell son de mi bsh(Berthold Shell de VBERTOS, una versión mejorada de memoria virtual de UNOS - el primer clon de UNIX). Bsh obtuvo muchas características de csh en 1984 y 1985, pero el mecanismo de alias de UNOS ya era superior al de csh en 1980. Otras características nuevas de Bourne Shell son de POSIX, para permitir que se acerque al cumplimiento de POSIX.
schily