Ejecute lo anterior en basho zshy obtendrá los mismos resultados; solo se establecerá uno de retval_bashy retval_zsh. El otro estará en blanco. Esto permitiría que una función terminara return $retval_bash $retval_zsh(¡tenga en cuenta la falta de comillas!).
Y pipestatusen zsh. Lamentablemente, otros proyectiles no tienen esta característica.
Gilles
8
Nota: Las matrices en zsh comienzan contraintuitivamente en el índice 1, por lo que es echo "$pipestatus[1]" "$pipestatus[2]".
Christoph Wurm
55
Puede verificar toda la tubería de esta manera:if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
l0b0
77
@ JanHudec: Quizás deberías leer las primeras cinco palabras de mi respuesta. También indique amablemente dónde la pregunta solicitó una respuesta solo POSIX.
camh
12
@ JanHudec: Tampoco fue etiquetado POSIX. ¿Por qué supone que la respuesta debe ser POSIX? No se especificó, así que proporcioné una respuesta calificada. No hay nada incorrecto en mi respuesta, además hay varias respuestas para abordar otros casos.
camh
238
Hay 3 formas comunes de hacer esto:
Pipefail
La primera forma es establecer la pipefailopción ( ksh, zsho bash). Este es el más simple y lo que hace es básicamente establecer el estado $?de salida en el código de salida del último programa para salir de cero (o cero si todos salieron con éxito).
Bash también tiene una variable de matriz llamada $PIPESTATUS( $pipestatusin zsh) que contiene el estado de salida de todos los programas en la última canalización.
@Patrick la solución pipestatus funciona en bash, solo más quastion en caso de que use el script ksh, ¿crees que podemos encontrar algo similar a pipestatus? , (mientras tanto veo el pipestatus no soportado por ksh)
yael
2
@yael no lo uso ksh, pero de un breve vistazo a su página de manual, no es compatible $PIPESTATUSni nada similar. Sin embargo, admite la pipefailopción.
Patrick
1
Decidí ir con pipefail ya que me permite obtener el estado del comando fallido aquí:LOG=$(failed_command | successful_command)
vmrob
51
Esta solución funciona sin usar funciones específicas de bash o archivos temporales. Bonificación: al final, el estado de salida es en realidad un estado de salida y no una cadena en un archivo.
Situación:
someprog | filter
desea el estado de salida de someprogy la salida de filter.
el resultado de esta construcción es stdout desde filterstdout de la construcción y el estado de salida desde someprogcomo estado de salida de la construcción.
Esta construcción también funciona con la agrupación de comandos simple en {...}lugar de subcapas (...). Las subcapas tienen algunas implicaciones, entre otras, un costo de rendimiento, que no necesitamos aquí. lea el manual de fine bash para obtener más detalles: https://www.gnu.org/software/bash/manual/html_node/Command-Grouping.html
Nota: el proceso hijo hereda los descriptores de archivo abiertos del padre. Eso significa someprogque heredará el descriptor de archivo abierto 3 y 4. Si someprogescribe en el descriptor de archivo 3, se convertirá en el estado de salida. El estado de salida real se ignorará porque readsolo se lee una vez.
Si le preocupa que someprogpueda escribir en el descriptor de archivo 3 o 4, entonces es mejor cerrar los descriptores de archivo antes de llamar someprog.
El exec 3>&- 4>&-antes someprogcierra el descriptor de archivo antes de ejecutarlo, someprogpor lo que para someprogesos descriptores de archivo simplemente no existen.
Se crea una subshell con el descriptor de archivo 4 redirigido a stdout. Esto significa que todo lo que se imprima en el descriptor de archivo 4 en la subshell terminará como la salida estándar de toda la construcción.
Se crea una tubería y se ejecutan los comandos de la izquierda ( #part3) y la derecha ( #part2). exit $xstambién es el último comando de la tubería y eso significa que la cadena de stdin será el estado de salida de toda la construcción.
Se crea una subshell con el descriptor de archivo 3 redirigido a stdout. Esto significa que todo lo que se imprima en el descriptor de archivo 3 en esta subshell terminará #part2y, a su vez, será el estado de salida de toda la construcción.
Se crea una tubería y se ejecutan los comandos a la izquierda ( #part5y #part6) y a la derecha ( filter >&4). La salida de filterse redirige al descriptor de archivo 4. En #part1el descriptor de archivo 4 se redirige a stdout. Esto significa que la salida de filteres la salida estándar de toda la construcción.
El estado de salida de #part6se imprime en el descriptor de archivo 3. En #part3el descriptor de archivo 3 se redirige a #part2. Esto significa que el estado de salida de #part6será el estado de salida final para toda la construcción.
someproges ejecutado. Se toma el estado de salida #part5. La tubería toma el stdout #part4y lo reenvía filter. La salida de filtera su vez alcanzará stdout como se explica en#part4
set -o pipefaildentro del script debe ser más robusto, por ejemplo, en caso de que alguien ejecute el script a través de bash foo.sh.
maxschlepzig
¿Cómo funciona? tienes un ejemplo
Johan
2
Tenga en cuenta que -o pipefailno está en POSIX.
scy
2
Esto no funciona en mi versión BASH 3.2.25 (1). En la parte superior de / tmp / ff tengo #!/bin/bash -o pipefail. El error es:/bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
Felipe Alvarez
2
@FelipeAlvarez: Algunos entornos (incluyendo Linux) no analizar espacios en #!líneas más allá de la primera, y por lo que este se convierte /bin/bash-o pipefail/tmp/ff, en lugar de lo necesario /bin/bash-opipefail/tmp/ff- getoptel análisis sintáctico (o similar) con el optarg, que es el siguiente elemento ARGV, como el argumento a -o, entonces falla. Si tuviera que hacer una envoltura (digamos, bash-pfeso acaba de hacer exec /bin/bash -o pipefail "$@", y poner eso en la #!línea, eso funcionaría. Ver también: en.wikipedia.org/wiki/Shebang_%28Unix%29
lindes
22
Lo que hago cuando sea posible es alimentar el código de salida desde foodentro bar. Por ejemplo, si sé que foonunca produce una línea con solo dígitos, entonces puedo agregar el código de salida:
Esto siempre se puede hacer si hay alguna forma de llegar bara trabajar en todos, excepto en la última línea, y pasar la última línea como su código de salida.
Si se bartrata de una tubería compleja cuya salida no necesita, puede omitir parte de ella imprimiendo el código de salida en un descriptor de archivo diferente.
exit_codes=$({{ foo; echo foo:"$?">&3;}|{ bar >/dev/null; echo bar:"$?">&3;}}3>&1)
Después de esto $exit_codeses generalmente foo:X bar:Y, pero podría ser bar:Y foo:Xsi se barcierra antes de leer toda su entrada o si tiene mala suerte. Creo que las escrituras en las tuberías de hasta 512 bytes son atómicas en todos los sistemas Unix, por lo que los foo:$?y bar:$?las piezas no se pueden mezclar, siempre y cuando las cuerdas están bajo la etiqueta de 507 bytes.
Si necesita capturar la salida bar, se hace difícil. Puede combinar las técnicas anteriores organizando la salida de barnunca para que contenga una línea que parezca una indicación de código de salida, pero se vuelve complicada.
output=$(echo;{{ foo; echo foo:"$?">&3;}|{ bar | sed 's/^/^/'; echo bar:"$?">&3;}}3>&1)
nl='
'
foo_exit_code=${output#*${nl}foo:}; foo_exit_code=${foo_exit_code%%$nl*}
bar_exit_code=${output#*${nl}bar:}; bar_exit_code=${bar_exit_code%%$nl*}
output=$(printf %s "$output"| sed -n 's/^\^//p')
Y, por supuesto, existe la opción simple de usar un archivo temporal para almacenar el estado. Simple, pero no tan simple en producción:
Si hay varias secuencias de comandos ejecutándose simultáneamente, o si la misma secuencia de comandos usa este método en varios lugares, debe asegurarse de que usen diferentes nombres de archivos temporales.
Crear un archivo temporal de forma segura en un directorio compartido es difícil. A menudo, /tmpes el único lugar donde un script seguramente podrá escribir archivos. Uso mktemp, que no es POSIX pero está disponible en todas las unidades serias hoy en día.
Cuando se utiliza el enfoque de archivo temporal prefiero añadir una trampa para la salida que elimina todos los archivos temporales para que la basura no se dejará incluso si el guión muere
miracle173
17
A partir de la tubería:
foo | bar | baz
Aquí hay una solución general que usa solo shell POSIX y no archivos temporales:
$error_statuses contiene los códigos de estado de cualquier proceso fallido, en orden aleatorio, con índices para indicar qué comando emitió cada estado.
# if "bar" failed, output its status:
echo "$error_statuses"| grep '1:'| cut -d:-f2
# test if all commands succeeded:
test -z "$error_statuses"# test if the last command succeeded:! echo "$error_statuses"| grep '2:'>/dev/null
Tenga en cuenta las citas $error_statusesen mis pruebas; sin ellos grepno se puede diferenciar porque las nuevas líneas se convierten en espacios forzados.
Así que quería aportar una respuesta como la de lesmana, pero creo que la mía es quizás una solución un poco más simple y un poco más ventajosa de Bourne-shell:
# You want to pipe command1 through command2:
exec 4>&1
exitstatus=`{ { command1; printf $? 1>&3; } | command2 1>&4; } 3>&1`# $exitstatus now has command1's exit status.
Creo que esto se explica mejor de adentro hacia afuera: command1 se ejecutará e imprimirá su salida regular en stdout (descriptor de archivo 1), luego, una vez hecho, printf se ejecutará e imprimirá el código de salida de command1 en su stdout, pero ese stdout se redirige a descriptor de archivo 3.
Mientras se ejecuta command1, su stdout se canaliza a command2 (la salida de printf nunca llega a command2 porque lo enviamos al descriptor de archivo 3 en lugar de 1, que es lo que lee la tubería). Luego redirigimos la salida del comando 2 al descriptor de archivo 4, de modo que también quede fuera del descriptor de archivo 1, porque queremos que el descriptor de archivo 1 esté libre un poco más tarde, porque volveremos a colocar la salida de printf en el descriptor de archivo 3 en el descriptor de archivo 1 - porque eso es lo que capturará la sustitución del comando (los backticks) y eso es lo que se colocará en la variable.
La última parte de la magia es que primero exec 4>&1lo hicimos como un comando separado: abre el descriptor de archivo 4 como una copia del stdout del shell externo. La sustitución de comandos capturará todo lo que está escrito en el estándar desde la perspectiva de los comandos dentro de él, pero, dado que la salida del comando2 va al descriptor de archivo 4 en lo que respecta a la sustitución de comandos, la sustitución de comandos no lo captura, sin embargo, una vez que se "sale" de la sustitución del comando, efectivamente sigue yendo al descriptor de archivo general 1 del script.
( exec 4>&1Tiene que ser un comando separado porque a muchos shells comunes no les gusta cuando intentas escribir en un descriptor de archivo dentro de una sustitución de comando, que se abre en el comando "externo" que está usando la sustitución. Así que este es el La forma portátil más sencilla de hacerlo).
Puede verlo de una manera menos técnica y más lúdica, como si las salidas de los comandos se saltaran entre sí: command1 se canaliza hacia command2, luego la salida de printf salta sobre el comando 2 para que command2 no lo atrape, y luego La salida del comando 2 salta y sale de la sustitución del comando justo cuando printf aterriza justo a tiempo para ser capturado por la sustitución de modo que termine en la variable, y la salida del comando 2 se escribe alegremente en la salida estándar, tal como en una tubería normal
Además, según tengo entendido, $?seguirá conteniendo el código de retorno del segundo comando en la tubería, porque las asignaciones de variables, las sustituciones de comandos y los comandos compuestos son efectivamente transparentes al código de retorno del comando dentro de ellos, por lo que el estado de retorno de command2 debería propagarse; esto, y no tener que definir una función adicional, es la razón por la que creo que esta podría ser una solución algo mejor que la propuesta por lesmana.
Según las advertencias que menciona lesmana, es posible que command1 en algún momento termine usando los descriptores de archivo 3 o 4, por lo que para ser más robusto, haría lo siguiente:
Tenga en cuenta que uso comandos compuestos en mi ejemplo, pero subcapas (usar en ( )lugar de { }también funcionará, aunque tal vez sea menos eficiente).
Los comandos heredan los descriptores de archivo del proceso que los inicia, por lo que toda la segunda línea heredará el descriptor de archivo cuatro, y el comando compuesto seguido 3>&1heredará el descriptor de archivo tres. Por lo tanto, 4>&-se asegura de que el comando compuesto interno no heredará el descriptor de archivo cuatro, y 3>&-no heredará el descriptor de archivo tres, por lo que command1 obtiene un entorno más limpio y estándar. También puede mover el interior al 4>&-lado del 3>&-, pero me imagino por qué no limitar su alcance tanto como sea posible.
No estoy seguro de con qué frecuencia las cosas usan el descriptor de archivo tres y cuatro directamente; creo que la mayoría de las veces los programas usan syscalls que devuelven descriptores de archivo no utilizados en este momento, pero a veces las escrituras de código en el descriptor de archivo 3 directamente, yo Supongo (podría imaginar un programa revisando un descriptor de archivo para ver si está abierto, y usándolo si lo está, o comportándose de manera diferente si no lo está). Por lo tanto, lo último es probablemente mejor tener en cuenta y usar para casos de uso general.
Parece interesante, pero no puedo entender qué espera que haga este comando, y mi computadora tampoco; Consigo -bash: 3: Bad file descriptor.
G-Man
@ G-Man Bien, sigo olvidando que bash no tiene idea de lo que está haciendo cuando se trata de descriptores de archivos, a diferencia de los shells que uso habitualmente (la ceniza que viene con busybox). Te avisaré cuando piense en una solución que haga feliz a bash. Mientras tanto, si tienes una caja de Debian a mano, puedes probarla en el tablero, o si tienes busybox a mano, puedes probarla con la busybox ash / sh.
mtraceur
@ G-Man En cuanto a lo que espero que haga el comando, y lo que hace en otros shells, es redirigir stdout desde command1 para que no quede atrapado por la sustitución de comando, pero una vez fuera de la sustitución de comando, se cae fd3 vuelve a stdout, por lo que se canaliza como se espera que command2. Cuando sale el comando1, printf se dispara e imprime su estado de salida, que se captura en la variable mediante la sustitución del comando. Desglose muy detallado aquí: stackoverflow.com/questions/985876/tee-and-exit-status/… Además, ese comentario tuyo se lee como si fuera un insulto?
mtraceur
¿Por dónde empezaré? (1) Lo siento si te sentiste insultado. "Parece interesante" significaba con seriedad; sería genial si algo tan compacto como eso funcionara tan bien como esperabas. Más allá de eso, estaba diciendo, simplemente, que no entendía lo que se suponía que su solución debía hacer. He estado trabajando / jugando con Unix durante mucho tiempo (desde antes de que existiera Linux) y, si no entiendo algo, es una señal de alerta que, tal vez, otras personas tampoco lo entenderán, y que necesita más explicación (IMNSHO). … (Continúa)
G-Man el
(Continúa) ... Ya que "... te gusta pensar ... que [entiendes] casi todo más que la persona promedio", tal vez deberías recordar que el objetivo de Stack Exchange no es ser un servicio de redacción de comandos. miles de soluciones únicas a preguntas trivialmente distintas; sino más bien para enseñar a las personas cómo resolver sus propios problemas. Y, con ese fin, tal vez necesite explicar las cosas lo suficientemente bien como para que una "persona promedio" pueda entenderlo. Mire la respuesta de lesmana para ver un ejemplo de una excelente explicación. … (Continúa)
G-Man el
11
Si tiene instalado el paquete moreutils , puede usar la utilidad mispipe que hace exactamente lo que solicitó.
La solución anterior de lesmana también se puede hacer sin la sobrecarga de iniciar subprocesos anidados utilizando en su { .. }lugar (recordando que esta forma de comandos agrupados siempre tiene que terminar con punto y coma). Algo como esto:
He comprobado esta construcción con la versión de guión 0.5.5 y las versiones de bash 3.2.25 y 4.2.42, por lo que incluso si algunos shells no admiten la { .. }agrupación, sigue siendo compatible con POSIX.
Esto funciona muy bien con la mayoría de los shells con los que lo he probado, incluidos NetBSD sh, pdksh, mksh, dash, bash. Sin embargo, no puedo hacer que funcione con AT&T Ksh (93s +, 93u +) o zsh (4.3.9, 5.2), incluso con set -o pipefailin ksh o cualquier número de waitcomandos rociados en cualquiera de ellos. Creo que, en parte, al menos, puede ser un problema de análisis de ksh, ya que si me limito a usar subshells, entonces funciona bien, pero incluso con un ifpara elegir la variante de subshell para ksh pero dejar los comandos compuestos para otros, falla .
Greg A. Woods, el
4
Esto es portátil, es decir, funciona con cualquier shell compatible con POSIX, no requiere que el directorio actual sea editable y permite que se ejecuten simultáneamente varios scripts que usan el mismo truco.
Esto no funciona por varias razones. 1. El archivo temporal puede leerse antes de escribirse. 2. Crear un archivo temporal en un directorio compartido con un nombre predecible es inseguro (DoS trivial, carrera de enlace simbólico). 3. Si el mismo script usa este truco varias veces, siempre usará el mismo nombre de archivo. Para resolver 1, lea el archivo después de que la tubería se haya completado. Para resolver 2 y 3, use un archivo temporal con un nombre generado aleatoriamente o en un directorio privado.
Gilles
+1 Bueno, el $ {PIPESTATUS [0]} es más fácil, pero la idea básica aquí funciona si uno conoce los problemas que menciona Gilles.
Johan
Puede guardar un par de subniveles: (s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s)). @Johan: Estoy de acuerdo en que es más fácil con Bash, pero en algunos contextos, vale la pena saber cómo evitarlo.
dubiousjim
4
Siguiente se entiende como un complemento a la respuesta de @Patrik, en caso de que no pueda utilizar una de las soluciones comunes.
Esta respuesta supone lo siguiente:
Tienes un caparazón que no conoce $PIPESTATUSniset -o pipefail
Desea utilizar una tubería para ejecución paralela, por lo que no hay archivos temporales.
No desea tener un desorden adicional si interrumpe el script, posiblemente por un apagón repentino.
Esta solución debería ser relativamente fácil de seguir y limpia para leer.
No desea introducir subcapas adicionales.
No puede jugar con los descriptores de archivo existentes, por lo que no debe tocar stdin / out / err (sin embargo, puede introducir uno nuevo temporalmente)
Suposiciones adicionales. Puede deshacerse de todo, pero esto cambia demasiado la receta, por lo que no se trata aquí:
Todo lo que quiere saber es que todos los comandos en PIPE tienen el código de salida 0.
No necesita información adicional sobre la banda lateral.
Su shell espera a que regresen todos los comandos de tubería.
Antes: foo | bar | bazsin embargo, esto solo devuelve el código de salida del último comando ( baz)
Se busca: $?no debe ser 0(verdadero), si alguno de los comandos en la tubería falló
Después:
TMPRESULTS="`mktemp`"{
rm -f "$TMPRESULTS"{ foo || echo $?>&9;}|{ bar || echo $?>&9;}|{ baz || echo $?>&9;}#wait! read TMPRESULTS <&8}9>>"$TMPRESULTS"8<"$TMPRESULTS"# $? now is 0 only if all commands had exit code 0
Explicado:
Se crea un archivo temporal con mktemp. Esto generalmente crea inmediatamente un archivo en/tmp
Este archivo temporal se redirige a FD 9 para escritura y FD 8 para lectura
Luego, el archivo temporal se elimina inmediatamente. Sin embargo, permanece abierto hasta que ambos FD desaparezcan.
Ahora se inicia la tubería. Cada paso se agrega solo a FD 9, si hubo un error.
Se waitnecesita para ksh, porque de lo kshcontrario no espera a que finalicen todos los comandos de tubería. Sin embargo, tenga en cuenta que hay efectos secundarios no deseados si hay algunas tareas en segundo plano, por lo que lo comenté de forma predeterminada. Si la espera no duele, puedes comentarla.
Luego se leen los contenidos del archivo. Si está vacío (porque todo funcionó) readregresa false, entonces trueindica un error
Esto se puede usar como reemplazo de un complemento para un solo comando y solo necesita lo siguiente:
FD no utilizados 9 y 8
Una única variable de entorno para contener el nombre del archivo temporal
Y esta receta se puede adaptar a casi cualquier shell que permita la redirección de IO
También es bastante independiente de la plataforma y no necesita cosas como /proc/fd/N
Loco:
Este script tiene un error en caso de que se /tmpquede sin espacio. Si también necesita protección contra este caso artificial, puede hacerlo de la siguiente manera, sin embargo, esto tiene la desventaja de que el número de 0in 000depende del número de comandos en la tubería, por lo que es un poco más complicado:
kshy los shells similares que solo esperan el último comando de tubería necesitan el waitcomentario no comentado
El último ejemplo se usa en printf "%1s" "$?"lugar de echo -n "$?"porque es más portátil. No todas las plataformas interpretan -ncorrectamente.
printf "$?"lo haría también, sin embargo, printf "%1s"detecta algunos casos de esquina en caso de que ejecute el script en una plataforma realmente rota. (Lea: si programa en paranoia_mode=extreme).
FD 8 y FD 9 pueden ser superiores en plataformas que admiten múltiples dígitos. AFAIR un shell POSIX conforme solo necesita admitir dígitos individuales.
Se puso a prueba con Debian 8.2 sh, bash, ksh, ash, sashe inclusocsh
¿Qué tal una limpieza como jlliagre? ¿No dejas un archivo llamado foo-status?
Johan
@Johan: Si prefieres mi sugerencia, no dudes en votarla;) Además de no dejar un archivo, tiene la ventaja de permitir que múltiples procesos ejecuten esto simultáneamente y el directorio actual no necesita ser editable.
jlliagre
2
El siguiente bloque 'if' se ejecutará solo si 'command' se realizó correctamente:
if command;then# ...fi
Hablando específicamente, puede ejecutar algo como esto:
Que ejecutará haconf -makerwy almacenará su stdout y stderr en "$ haconf_out". Si el valor devuelto desde haconfes verdadero, entonces el bloque 'if' se ejecutará y grepleerá "$ haconf_out", intentando compararlo con "Cluster ya escribible".
Observe que las tuberías se limpian automáticamente; con la redirección, deberá tener cuidado de eliminar "$ haconf_out" cuando haya terminado.
No es tan elegante como pipefail, pero es una alternativa legítima si esta funcionalidad no está al alcance.
(Con bash al menos) combinado con set -euno puede usar subshell para emular explícitamente pipefail y salir en error de tubería
set-e
foo | bar
( exit ${PIPESTATUS[0]})
rest of program
Entonces, si foofalla por alguna razón, el resto del programa no se ejecutará y el script se cerrará con el código de error correspondiente. (Esto supone que fooimprime su propio error, que es suficiente para comprender el motivo del fallo)
# =========================================================== ## Preceding a _pipe_ with ! inverts the exit status returned.
ls | bogus_command # bash: bogus_command: command not found
echo $?# 127! ls | bogus_command # bash: bogus_command: command not found
echo $?# 0# Note that the ! does not change the execution of the pipe.# Only the exit status changes.# =========================================================== #
Respuestas:
Si está usando
bash
, puede usar laPIPESTATUS
variable de matriz para obtener el estado de salida de cada elemento de la tubería.Si está utilizando
zsh
, se llama a la matrizpipestatus
(¡el caso importa!) Y los índices de la matriz comienzan en uno:Para combinarlos dentro de una función de una manera que no pierda los valores:
Ejecute lo anterior en
bash
ozsh
y obtendrá los mismos resultados; solo se establecerá uno deretval_bash
yretval_zsh
. El otro estará en blanco. Esto permitiría que una función terminarareturn $retval_bash $retval_zsh
(¡tenga en cuenta la falta de comillas!).fuente
pipestatus
en zsh. Lamentablemente, otros proyectiles no tienen esta característica.echo "$pipestatus[1]" "$pipestatus[2]"
.if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
Hay 3 formas comunes de hacer esto:
Pipefail
La primera forma es establecer la
pipefail
opción (ksh
,zsh
obash
). Este es el más simple y lo que hace es básicamente establecer el estado$?
de salida en el código de salida del último programa para salir de cero (o cero si todos salieron con éxito).$ PIPESTATO
Bash también tiene una variable de matriz llamada
$PIPESTATUS
($pipestatus
inzsh
) que contiene el estado de salida de todos los programas en la última canalización.Puede usar el tercer ejemplo de comando para obtener el valor específico en la tubería que necesita.
Ejecuciones separadas
Esta es la más difícil de manejar de las soluciones. Ejecute cada comando por separado y capture el estado
fuente
ksh
, pero de un breve vistazo a su página de manual, no es compatible$PIPESTATUS
ni nada similar. Sin embargo, admite lapipefail
opción.LOG=$(failed_command | successful_command)
Esta solución funciona sin usar funciones específicas de bash o archivos temporales. Bonificación: al final, el estado de salida es en realidad un estado de salida y no una cadena en un archivo.
Situación:
desea el estado de salida de
someprog
y la salida defilter
.Aquí está mi solución:
el resultado de esta construcción es stdout desde
filter
stdout de la construcción y el estado de salida desdesomeprog
como estado de salida de la construcción.Esta construcción también funciona con la agrupación de comandos simple en
{...}
lugar de subcapas(...)
. Las subcapas tienen algunas implicaciones, entre otras, un costo de rendimiento, que no necesitamos aquí. lea el manual de fine bash para obtener más detalles: https://www.gnu.org/software/bash/manual/html_node/Command-Grouping.htmlDesafortunadamente, la gramática bash requiere espacios y puntos y comas para las llaves para que la construcción se vuelva mucho más espaciosa.
Para el resto de este texto, usaré la variante subshell.
Ejemplo
someprog
yfilter
:Salida de ejemplo:
Nota: el proceso hijo hereda los descriptores de archivo abiertos del padre. Eso significa
someprog
que heredará el descriptor de archivo abierto 3 y 4. Sisomeprog
escribe en el descriptor de archivo 3, se convertirá en el estado de salida. El estado de salida real se ignorará porqueread
solo se lee una vez.Si le preocupa que
someprog
pueda escribir en el descriptor de archivo 3 o 4, entonces es mejor cerrar los descriptores de archivo antes de llamarsomeprog
.El
exec 3>&- 4>&-
antessomeprog
cierra el descriptor de archivo antes de ejecutarlo,someprog
por lo que parasomeprog
esos descriptores de archivo simplemente no existen.También se puede escribir así:
someprog 3>&- 4>&-
Explicación paso a paso de la construcción:
De abajo hacia arriba:
#part3
) y la derecha (#part2
).exit $xs
también es el último comando de la tubería y eso significa que la cadena de stdin será el estado de salida de toda la construcción.#part2
y, a su vez, será el estado de salida de toda la construcción.#part5
y#part6
) y a la derecha (filter >&4
). La salida defilter
se redirige al descriptor de archivo 4. En#part1
el descriptor de archivo 4 se redirige a stdout. Esto significa que la salida defilter
es la salida estándar de toda la construcción.#part6
se imprime en el descriptor de archivo 3. En#part3
el descriptor de archivo 3 se redirige a#part2
. Esto significa que el estado de salida de#part6
será el estado de salida final para toda la construcción.someprog
es ejecutado. Se toma el estado de salida#part5
. La tubería toma el stdout#part4
y lo reenvíafilter
. La salida defilter
a su vez alcanzará stdout como se explica en#part4
fuente
(read; exit $REPLY)
(exec 3>&- 4>&-; someprog)
simplifica asomeprog 3>&- 4>&-
.{ { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1
Si bien no es exactamente lo que pediste, podrías usar
para que sus tuberías devuelvan el último retorno distinto de cero.
podría ser un poco menos codificación
Editar: Ejemplo
fuente
set -o pipefail
dentro del script debe ser más robusto, por ejemplo, en caso de que alguien ejecute el script a través debash foo.sh
.-o pipefail
no está en POSIX.#!/bin/bash -o pipefail
. El error es:/bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
#!
líneas más allá de la primera, y por lo que este se convierte/bin/bash
-o pipefail
/tmp/ff
, en lugar de lo necesario/bin/bash
-o
pipefail
/tmp/ff
-getopt
el análisis sintáctico (o similar) con eloptarg
, que es el siguiente elementoARGV
, como el argumento a-o
, entonces falla. Si tuviera que hacer una envoltura (digamos,bash-pf
eso acaba de hacerexec /bin/bash -o pipefail "$@"
, y poner eso en la#!
línea, eso funcionaría. Ver también: en.wikipedia.org/wiki/Shebang_%28Unix%29Lo que hago cuando sea posible es alimentar el código de salida desde
foo
dentrobar
. Por ejemplo, si sé quefoo
nunca produce una línea con solo dígitos, entonces puedo agregar el código de salida:O si sé que la salida de
foo
nunca contiene una línea con solo.
:Esto siempre se puede hacer si hay alguna forma de llegar
bar
a trabajar en todos, excepto en la última línea, y pasar la última línea como su código de salida.Si se
bar
trata de una tubería compleja cuya salida no necesita, puede omitir parte de ella imprimiendo el código de salida en un descriptor de archivo diferente.Después de esto
$exit_codes
es generalmentefoo:X bar:Y
, pero podría serbar:Y foo:X
si sebar
cierra antes de leer toda su entrada o si tiene mala suerte. Creo que las escrituras en las tuberías de hasta 512 bytes son atómicas en todos los sistemas Unix, por lo que losfoo:$?
ybar:$?
las piezas no se pueden mezclar, siempre y cuando las cuerdas están bajo la etiqueta de 507 bytes.Si necesita capturar la salida
bar
, se hace difícil. Puede combinar las técnicas anteriores organizando la salida debar
nunca para que contenga una línea que parezca una indicación de código de salida, pero se vuelve complicada.Y, por supuesto, existe la opción simple de usar un archivo temporal para almacenar el estado. Simple, pero no tan simple en producción:
/tmp
es el único lugar donde un script seguramente podrá escribir archivos. Usomktemp
, que no es POSIX pero está disponible en todas las unidades serias hoy en día.fuente
A partir de la tubería:
Aquí hay una solución general que usa solo shell POSIX y no archivos temporales:
$error_statuses
contiene los códigos de estado de cualquier proceso fallido, en orden aleatorio, con índices para indicar qué comando emitió cada estado.Tenga en cuenta las citas
$error_statuses
en mis pruebas; sin ellosgrep
no se puede diferenciar porque las nuevas líneas se convierten en espacios forzados.fuente
Así que quería aportar una respuesta como la de lesmana, pero creo que la mía es quizás una solución un poco más simple y un poco más ventajosa de Bourne-shell:
Creo que esto se explica mejor de adentro hacia afuera: command1 se ejecutará e imprimirá su salida regular en stdout (descriptor de archivo 1), luego, una vez hecho, printf se ejecutará e imprimirá el código de salida de command1 en su stdout, pero ese stdout se redirige a descriptor de archivo 3.
Mientras se ejecuta command1, su stdout se canaliza a command2 (la salida de printf nunca llega a command2 porque lo enviamos al descriptor de archivo 3 en lugar de 1, que es lo que lee la tubería). Luego redirigimos la salida del comando 2 al descriptor de archivo 4, de modo que también quede fuera del descriptor de archivo 1, porque queremos que el descriptor de archivo 1 esté libre un poco más tarde, porque volveremos a colocar la salida de printf en el descriptor de archivo 3 en el descriptor de archivo 1 - porque eso es lo que capturará la sustitución del comando (los backticks) y eso es lo que se colocará en la variable.
La última parte de la magia es que primero
exec 4>&1
lo hicimos como un comando separado: abre el descriptor de archivo 4 como una copia del stdout del shell externo. La sustitución de comandos capturará todo lo que está escrito en el estándar desde la perspectiva de los comandos dentro de él, pero, dado que la salida del comando2 va al descriptor de archivo 4 en lo que respecta a la sustitución de comandos, la sustitución de comandos no lo captura, sin embargo, una vez que se "sale" de la sustitución del comando, efectivamente sigue yendo al descriptor de archivo general 1 del script.(
exec 4>&1
Tiene que ser un comando separado porque a muchos shells comunes no les gusta cuando intentas escribir en un descriptor de archivo dentro de una sustitución de comando, que se abre en el comando "externo" que está usando la sustitución. Así que este es el La forma portátil más sencilla de hacerlo).Puede verlo de una manera menos técnica y más lúdica, como si las salidas de los comandos se saltaran entre sí: command1 se canaliza hacia command2, luego la salida de printf salta sobre el comando 2 para que command2 no lo atrape, y luego La salida del comando 2 salta y sale de la sustitución del comando justo cuando printf aterriza justo a tiempo para ser capturado por la sustitución de modo que termine en la variable, y la salida del comando 2 se escribe alegremente en la salida estándar, tal como en una tubería normal
Además, según tengo entendido,
$?
seguirá conteniendo el código de retorno del segundo comando en la tubería, porque las asignaciones de variables, las sustituciones de comandos y los comandos compuestos son efectivamente transparentes al código de retorno del comando dentro de ellos, por lo que el estado de retorno de command2 debería propagarse; esto, y no tener que definir una función adicional, es la razón por la que creo que esta podría ser una solución algo mejor que la propuesta por lesmana.Según las advertencias que menciona lesmana, es posible que command1 en algún momento termine usando los descriptores de archivo 3 o 4, por lo que para ser más robusto, haría lo siguiente:
Tenga en cuenta que uso comandos compuestos en mi ejemplo, pero subcapas (usar en
( )
lugar de{ }
también funcionará, aunque tal vez sea menos eficiente).Los comandos heredan los descriptores de archivo del proceso que los inicia, por lo que toda la segunda línea heredará el descriptor de archivo cuatro, y el comando compuesto seguido
3>&1
heredará el descriptor de archivo tres. Por lo tanto,4>&-
se asegura de que el comando compuesto interno no heredará el descriptor de archivo cuatro, y3>&-
no heredará el descriptor de archivo tres, por lo que command1 obtiene un entorno más limpio y estándar. También puede mover el interior al4>&-
lado del3>&-
, pero me imagino por qué no limitar su alcance tanto como sea posible.No estoy seguro de con qué frecuencia las cosas usan el descriptor de archivo tres y cuatro directamente; creo que la mayoría de las veces los programas usan syscalls que devuelven descriptores de archivo no utilizados en este momento, pero a veces las escrituras de código en el descriptor de archivo 3 directamente, yo Supongo (podría imaginar un programa revisando un descriptor de archivo para ver si está abierto, y usándolo si lo está, o comportándose de manera diferente si no lo está). Por lo tanto, lo último es probablemente mejor tener en cuenta y usar para casos de uso general.
fuente
-bash: 3: Bad file descriptor
.Si tiene instalado el paquete moreutils , puede usar la utilidad mispipe que hace exactamente lo que solicitó.
fuente
La solución anterior de lesmana también se puede hacer sin la sobrecarga de iniciar subprocesos anidados utilizando en su
{ .. }
lugar (recordando que esta forma de comandos agrupados siempre tiene que terminar con punto y coma). Algo como esto:He comprobado esta construcción con la versión de guión 0.5.5 y las versiones de bash 3.2.25 y 4.2.42, por lo que incluso si algunos shells no admiten la
{ .. }
agrupación, sigue siendo compatible con POSIX.fuente
set -o pipefail
in ksh o cualquier número dewait
comandos rociados en cualquiera de ellos. Creo que, en parte, al menos, puede ser un problema de análisis de ksh, ya que si me limito a usar subshells, entonces funciona bien, pero incluso con unif
para elegir la variante de subshell para ksh pero dejar los comandos compuestos para otros, falla .Esto es portátil, es decir, funciona con cualquier shell compatible con POSIX, no requiere que el directorio actual sea editable y permite que se ejecuten simultáneamente varios scripts que usan el mismo truco.
Editar: aquí hay una versión más fuerte después de los comentarios de Gilles:
Edit2: y aquí hay una variante ligeramente más ligera después del comentario dudoso de Jim:
fuente
(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))
. @Johan: Estoy de acuerdo en que es más fácil con Bash, pero en algunos contextos, vale la pena saber cómo evitarlo.Siguiente se entiende como un complemento a la respuesta de @Patrik, en caso de que no pueda utilizar una de las soluciones comunes.
Esta respuesta supone lo siguiente:
$PIPESTATUS
niset -o pipefail
Antes:
foo | bar | baz
sin embargo, esto solo devuelve el código de salida del último comando (baz
)Se busca:
$?
no debe ser0
(verdadero), si alguno de los comandos en la tubería fallóDespués:
Explicado:
mktemp
. Esto generalmente crea inmediatamente un archivo en/tmp
wait
necesita paraksh
, porque de loksh
contrario no espera a que finalicen todos los comandos de tubería. Sin embargo, tenga en cuenta que hay efectos secundarios no deseados si hay algunas tareas en segundo plano, por lo que lo comenté de forma predeterminada. Si la espera no duele, puedes comentarla.read
regresafalse
, entoncestrue
indica un errorEsto se puede usar como reemplazo de un complemento para un solo comando y solo necesita lo siguiente:
/proc/fd/N
Loco:
Este script tiene un error en caso de que se
/tmp
quede sin espacio. Si también necesita protección contra este caso artificial, puede hacerlo de la siguiente manera, sin embargo, esto tiene la desventaja de que el número de0
in000
depende del número de comandos en la tubería, por lo que es un poco más complicado:Notas de portabilidad:
ksh
y los shells similares que solo esperan el último comando de tubería necesitan elwait
comentario no comentadoEl último ejemplo se usa en
printf "%1s" "$?"
lugar deecho -n "$?"
porque es más portátil. No todas las plataformas interpretan-n
correctamente.printf "$?"
lo haría también, sin embargo,printf "%1s"
detecta algunos casos de esquina en caso de que ejecute el script en una plataforma realmente rota. (Lea: si programa enparanoia_mode=extreme
).FD 8 y FD 9 pueden ser superiores en plataformas que admiten múltiples dígitos. AFAIR un shell POSIX conforme solo necesita admitir dígitos individuales.
Se puso a prueba con Debian 8.2
sh
,bash
,ksh
,ash
,sash
e inclusocsh
fuente
Con un poco de precaución, esto debería funcionar:
fuente
El siguiente bloque 'if' se ejecutará solo si 'command' se realizó correctamente:
Hablando específicamente, puede ejecutar algo como esto:
Que ejecutará
haconf -makerw
y almacenará su stdout y stderr en "$ haconf_out". Si el valor devuelto desdehaconf
es verdadero, entonces el bloque 'if' se ejecutará ygrep
leerá "$ haconf_out", intentando compararlo con "Cluster ya escribible".Observe que las tuberías se limpian automáticamente; con la redirección, deberá tener cuidado de eliminar "$ haconf_out" cuando haya terminado.
No es tan elegante como
pipefail
, pero es una alternativa legítima si esta funcionalidad no está al alcance.fuente
fuente
(Con bash al menos) combinado con
set -e
uno puede usar subshell para emular explícitamente pipefail y salir en error de tuberíaEntonces, si
foo
falla por alguna razón, el resto del programa no se ejecutará y el script se cerrará con el código de error correspondiente. (Esto supone quefoo
imprime su propio error, que es suficiente para comprender el motivo del fallo)fuente
EDITAR : Esta respuesta es incorrecta, pero interesante, así que la dejaré para referencia futura.
Agregar
!
a el comando invierte el código de retorno.http://tldp.org/LDP/abs/html/exit-status.html
fuente
ls
, no invertir el código de salida debogus_command