bash - simple - Shell pipe: sale inmediatamente cuando falla un comando
pipes linux c (4)
Estoy usando una tubería de varios comandos en bash. ¿Hay alguna forma de configurar bash para terminar todos los comandos en toda la tubería inmediatamente si falla uno de los comandos?
En mi caso, el primer comando, digamos command1
, se ejecuta durante un tiempo hasta que produce algún resultado. Puede sustituir command1
por (sleep 5 && echo "Hello")
, por ejemplo.
Ahora, command1 | false
command1 | false
falla después de 5 segundos pero no inmediatamente.
Este comportamiento parece tener algo que ver con la cantidad de salida que produce el comando. Por ejemplo, find / | false
devoluciones find / | false
inmediato.
En general, me pregunto por qué bash se comporta así. ¿Alguien puede imaginar cualquier situación en la que sea útil ese código como command1 | non-existing-command
¿ command1 | non-existing-command
no command1 | non-existing-command
no sale de una vez?
PD: el uso de archivos temporales no es una opción para mí, ya que los resultados intermedios que canalizo son demasiado grandes para ser almacenados.
PPS: Ni set -e
ni set -o pipefail
parecen influir en este fenómeno.
El primer programa no sabe si el segundo está terminado o no hasta que intenta escribir una fecha en la tubería. En caso de que se termine el segundo, el primero recibe el SIGPIPE que generalmente provoca una salida inmediata.
Puede forzar que la primera línea de salida se canalice inmediatamente después de mirar, de esta manera:
(sleep 0.1; echo; command1) | command2
Este sueño de 100 ms está destinado a esperar hasta que sea posible salir del comando 2 justo después de comenzar. Por supuesto, si el comando 2 sale después de 2 segundos, y el comando 1 permanecerá en silencio durante 60 segundos, todo el comando de shell regresará solo después de 60,1 segundos.
La documentación de bash dice en su sección sobre tuberías :
Cada comando en una tubería se ejecuta en su propia subshell [...]
"En su propia subshell" significa que se genera un nuevo proceso de bash, que luego ejecuta el comando real. Cada subshell se inicia con éxito, incluso cuando determina inmediatamente que el comando que se le pide que ejecute no existe.
Esto explica por qué todo el canal puede configurarse con éxito incluso cuando uno de los comandos no tiene sentido. Bash no comprueba si se puede ejecutar cada comando, lo delega a las subshells. Eso también explica por qué, por ejemplo, el comando nonexisting-command | touch hello
nonexisting-command | touch hello
lanzará un error de "comando no encontrado", pero el archivo hello
se creará de todos modos.
En la misma sección, también dice:
El shell espera a que todos los comandos en la tubería terminen antes de devolver un valor.
En el sleep 5 | nonexisting-command
sleep 5 | nonexisting-command
, como lo señaló AH, el sleep 5
termina después de 5 segundos, no inmediatamente, por lo que el shell también esperará 5 segundos.
No sé por qué la implementación se hizo de esta manera. En casos como el suyo, el comportamiento seguramente no es como uno esperaría.
De todos modos, una solución un poco fea es usar FIFOs:
mkfifo myfifo
./long-running-script.sh > myfifo &
whoops-a-typo < myfifo
Aquí, se inicia el long-running-script.sh
y luego los scripts fallan inmediatamente en la siguiente línea. Usando múltiples FIFOs, esto podría extenderse a tuberías con más de dos comandos.
find / |false
falla más rápido porque la primera llamada de sistema write(2)
de find
falla con el error EPIPE
(tubería rota). Esto se debe a que false
ya se ha terminado y, por lo tanto, el conducto entre estos dos comandos ya se ha cerrado en un lado.
Si la find
ignora ese error (podría hacerlo en teoría), también "fallará lentamente".
(sleep 5 && echo "Hello") | false
(sleep 5 && echo "Hello") | false
es "falla lento", porque la primera parte, la sleep
, no "prueba" la tubería al escribirle. Después de 5 segundos el echo
también recibe el error EPIPE
. Si este error termina la primera parte en este caso o no, no es importante para la pregunta.
sleep 5
no produce ningún resultado hasta que finaliza, mientras que find /
inmediatamente produce un resultado que bash intenta canalizar a false
.