Actualmente tengo un script que hace algo como
./a | ./b | ./c
Quiero modificarlo para que si alguno de a, bo c sale con un código de error, imprimo un mensaje de error y me detengo en lugar de enviar una salida incorrecta.
¿Cuál sería la forma más sencilla / limpia de hacerlo?
shell
error-handling
pipe
hugomg
fuente
fuente
&&|
lo que significaría "solo continuar con la tubería si el comando anterior fue exitoso". Supongo que también podría haberlo hecho,|||
lo que significaría "continuar la tubería si el comando anterior falló" (y posiblemente enviar el mensaje de error como Bash 4|&
).a
,b
,c
comandos no se ejecutan secuencialmente pero en paralelo. En otras palabras, los flujos de datos de forma secuencial a partira
dec
, pero el reala
,b
yc
las órdenes de marcha (más o menos) al mismo tiempo.Respuestas:
Si realmente no desea que el segundo comando continúe hasta que se sepa que el primero es exitoso, entonces probablemente necesite usar archivos temporales. La versión simple de eso es:
La redirección '1> & 2' también se puede abreviar '> & 2'; sin embargo, una versión anterior del shell MKS manejó mal la redirección de errores sin el '1' anterior, por lo que he usado esa notación inequívoca para la confiabilidad durante años.
Esto filtra archivos si interrumpe algo. Usos de programación de shell a prueba de bombas (más o menos):
La primera línea de trampa dice 'ejecutar los comandos'
rm -f $tmp.[12]; exit 1
'cuando ocurre alguna de las señales 1 SIGHUP, 2 SIGINT, 3 SIGQUIT, 13 SIGPIPE o 15 SIGTERM, o 0 (cuando el shell sale por cualquier motivo). Si está escribiendo un script de shell, la trampa final solo necesita eliminar la trampa en 0, que es la trampa de salida de shell (puede dejar las otras señales en su lugar ya que el proceso está a punto de terminar de todos modos).En la canalización original, es factible que 'c' lea datos de 'b' antes de que 'a' haya terminado; esto suele ser deseable (por ejemplo, da trabajo a varios núcleos). Si 'b' es una fase de 'clasificación', entonces esto no se aplicará; 'b' tiene que ver todas sus entradas antes de poder generar cualquiera de sus salidas.
Si desea detectar qué comando (s) fallan, puede usar:
Esto es simple y simétrico: es trivial extenderlo a una tubería de 4 o N partes.
La simple experimentación con 'set -e' no ayudó.
fuente
mktemp
otempfile
.trap
tenga en cuenta que el usuario siempre puede enviarSIGKILL
a un proceso para finalizarlo inmediatamente, en cuyo caso su trampa no surtirá efecto. Lo mismo ocurre cuando su sistema experimenta un corte de energía. Al crear archivos temporales, asegúrese de usarlomktemp
porque eso colocará sus archivos en algún lugar, donde se limpiarán después de reiniciar (generalmente en/tmp
).En bash puedes usar
set -e
yset -o pipefail
al principio de tu archivo. Un comando posterior./a | ./b | ./c
fallará cuando falla cualquiera de los tres scripts. El código de retorno será el código de retorno del primer script fallido.Tenga en cuenta que
pipefail
no está disponible en sh estándar .fuente
set
(para 4 versiones diferentes en total), y ver cómo eso afecta la salida de la últimaecho
.También puede verificar la
${PIPESTATUS[]}
matriz después de la ejecución completa, por ejemplo, si ejecuta:Luego
${PIPESTATUS}
habrá una matriz de códigos de error de cada comando en la tubería, por lo que si el comando del medio falla,echo ${PIPESTATUS[@]}
contendrá algo como:y algo como esto se ejecuta después del comando:
le permitirá comprobar que todos los comandos de la tubería se ejecutaron correctamente.
fuente
#!/bin/sh
, porque sish
no es bash, no funcionará. (Se puede arreglar fácilmente recordando usar#!/bin/bash
en su lugar.)echo ${PIPESTATUS[@]} | grep -qE '^[0 ]+$'
- devuelve 0 si$PIPESTATUS[@]
solo contiene 0 y espacios (si todos los comandos en la tubería tienen éxito).command1 && command2 | command3
si alguno de ellos falla, su solución devuelve un valor distinto de cero.Desafortunadamente, la respuesta de Johnathan requiere archivos temporales y las respuestas de Michel e Imron requieren bash (aunque esta pregunta está etiquetada como shell). Como ya han señalado otros, no es posible abortar la tubería antes de que se inicien procesos posteriores. Todos los procesos se inician a la vez y, por lo tanto, se ejecutarán antes de que se pueda comunicar cualquier error. Pero el título de la pregunta también preguntaba sobre códigos de error. Estos se pueden recuperar e investigar después de que la tubería terminó para determinar si alguno de los procesos involucrados falló.
Aquí hay una solución que detecta todos los errores en la tubería y no solo los errores del último componente. Así que esto es como el error de tubería de bash, solo que más poderoso en el sentido de que puede recuperar todos los códigos de error.
Para detectar si algo falló, se
echo
imprime un comando en el error estándar en caso de que falle algún comando. Luego, la salida de error estándar combinada se guarda$res
y se investiga más tarde. Esta es también la razón por la que el error estándar de todos los procesos se redirige a la salida estándar. También puede enviar esa salida/dev/null
o dejarla como otro indicador más de que algo salió mal. Puede reemplazar la última redirección a/dev/null
con un archivo si desea almacenar la salida del último comando en cualquier lugar.Para jugar más con esta construcción y convencerte de que realmente hace lo que debería, reemplacé
./a
,./b
y./c
por subcapas que se ejecutanecho
,cat
yexit
. Puede usar esto para verificar que esta construcción realmente reenvía toda la salida de un proceso a otro y que los códigos de error se registran correctamente.fuente