Obtener el estado de salida del proceso que ' s canalizó a otro

Tengo dos procesos foo y bar, conectados con una tubería:

$ foo | bar 

bar siempre sale 0; Estoy interesado en el código de salida de foo. ¿Hay alguna forma de acceder a él?

Comentarios

Answer

bash y zsh tienen una variable de matriz que contiene el estado de salida de cada elemento (comando) de la última canalización ejecutada por el shell.

Si está utilizando bash, la matriz se llama PIPESTATUS (¡el caso importa!) y los índices de la matriz comienzan en cero:

$ false | true $ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}" 1 0 

Si está utilizando zsh, la matriz se llama pipestatus (¡el caso importa!) y los índices de la matriz comienzan en uno:

$ false | true $ echo "${pipestatus[1]} ${pipestatus[2]}" 1 0 

Para combinarlos dentro de una función de una manera que no pierda los valores:

$ false | true $ retval_bash="${PIPESTATUS[0]}" retval_zsh="${pipestatus[1]}" retval_final=$? $ echo $retval_bash $retval_zsh $retval_final 1 0 

Ejecute lo anterior en bash o zsh y obtendrá los mismos resultados; solo se establecerá uno de retval_bash y retval_zsh. El otro estará en blanco. Esto permitiría que una función terminara con return $retval_bash $retval_zsh (¡tenga en cuenta la falta de comillas!).

Comentarios

  • Y pipestatus en zsh. Desafortunadamente, otras shells no ‘ tienen esta característica.
  • Nota: Las matrices en zsh comienzan de forma contraintuitiva en el índice 1, por lo que ‘ s echo "$pipestatus[1]" "$pipestatus[2]".
  • Puede verificar toda la canalización de esta manera: if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
  • @JanHudec: Quizás deberías leer las primeras cinco palabras de mi respuesta. También tenga la amabilidad de señalar dónde la pregunta solicitó una respuesta solo POSIX.
  • @JanHudec: Tampoco estaba etiquetada como POSIX. ¿Por qué asume 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.

Respuesta

Allí Hay 3 formas comunes de hacer esto:

Pipefail

La primera forma es establecer la opción pipefail (ksh, zsh o bash). Este es el más simple y lo que hace es básicamente establecer el estado de salida $? al código de salida del último programa para salir distinto de cero (o cero si todo salió correctamente).

$ false | true; echo $? 0 $ set -o pipefail $ false | true; echo $? 1 

$ PIPESTATUS

Bash también tiene una variable de matriz llamada $PIPESTATUS ($pipestatus en zsh) que contiene el estado de salida de todos los programas en la última canalización.

$ true | true; echo "${PIPESTATUS[@]}" 0 0 $ false | true; echo "${PIPESTATUS[@]}" 1 0 $ false | true; echo "${PIPESTATUS[0]}" 1 $ true | false; echo "${PIPESTATUS[@]}" 0 1 

Puede usar el tercer ejemplo de comando para obtener el valor específico en la tubería que necesita.

Ejecuciones separadas

Esta es la solución más difícil de manejar. Ejecute cada comando por separado y capture el estado

$ OUTPUT="$(echo foo)" $ STATUS_ECHO="$?" $ printf "%s" "$OUTPUT" | grep -iq "bar" $ STATUS_GREP="$?" $ echo "$STATUS_ECHO $STATUS_GREP" 0 1 

Comentarios

  • ¡Maldita sea! Solo iba a publicar sobre PIPESTATUS.
  • Como referencia, hay varias otras técnicas discutidas en esta pregunta SO: stackoverflow.com/questions/1221833/…
  • @Patrick, la solución pipestatus funciona en bash, solo más preguntas en caso de que use el script ksh, ¿cree que podemos encontrar algo similar a pipestatus? , (mientras tanto veo que ksh no admite el estado de la tubería)
  • @yael No ‘ no uso ksh, pero con un breve vistazo a su ‘ s página de manual, no ‘ es compatible con $PIPESTATUS o algo similar. Sin embargo, es compatible con la opción pipefail.
  • Decidí usar pipefail ya que me permite obtener el estado del comando fallido aquí: LOG=$(failed_command | successful_command)

Responder

Esta solución funciona sin utilizar 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 

usted quiero el estado de salida de someprog y la salida de filter.

Aquí está mi solución:

((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1 

el resultado de esta construcción es stdout de filter como stdout de la construcción y estado de salida de someprog como estado de salida de la construcción.


esta construcción también funciona con agrupaciones de comandos simples {...} en lugar de subcapas (...). Las subcapas tienen algunas implicaciones, entre otras un costo de rendimiento, que no necesitamos aquí. lea el manual fino de bash para obtener más detalles: https://www.gnu.org/software/bash/manual/html_node/Command-Grouping.html

{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; } } 4>&1 

Desafortunadamente, la gramática bash requiere espacios y punto y coma 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 y filter:

someprog() { echo "line1" echo "line2" echo "line3" return 42 } filter() { while read line; do echo "filtered $line" done } ((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1 echo $? 

Salida de ejemplo:

filtered line1 filtered line2 filtered line3 42 

Nota: el proceso hijo hereda los descriptores de archivo abiertos del padre. Eso significa que someprog heredará el descriptor de archivo abierto 3 y 4. Si someprog escribe en el descriptor de archivo 3, entonces se convertirá en el estado de salida. El estado de salida real se ignorará porque read solo se lee una vez.

Si le preocupa que su someprog pueda escribir al descriptor de archivo 3 o 4, entonces es mejor cerrar los descriptores de archivo antes de llamar a someprog.

(((((exec 3>&- 4>&-; someprog); echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1 

El exec 3>&- 4>&- antes de que someprog cierre el descriptor de archivo antes de ejecutar someprog para someprog 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:

( ( ( ( someprog; #part6 echo $? >&3 #part5 ) | filter >&4 #part4 ) 3>&1 #part3 ) | (read xs; exit $xs) #part2 ) 4>&1 #part1 

De abajo hacia arriba:

  1. Se crea una subcapa con descriptor de archivo 4 redirigido a stdout. Esto significa que todo lo que se imprima en el descriptor de archivo 4 en la subcapa terminará como la salida estándar de toda la construcción.
  2. Se crea una tubería y los comandos a la izquierda (#part3) y derecha (#part2) se ejecutan. 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.
  3. Se crea una subshell con el archivo descriptor 3 redirigido a stdout. Esto significa que todo lo que se imprima en el descriptor de archivo 3 en esta subcapa terminará en #part2 y, a su vez, será el estado de salida de toda la construcción.
  4. A Se crea la tubería y los comandos de la izquierda (#part5 y #part6) y de la derecha (filter >&4) se ejecutan. La salida de filter se redirige al descriptor de archivo 4. En #part1, el descriptor de archivo 4 se redirige a stdout. Esto significa que la salida de filter es la salida estándar de toda la construcción.
  5. Se imprime el estado de salida de #part6 al descriptor de archivo 3. En #part3, el descriptor de archivo 3 se redirigió a #part2. Esto significa que el estado de salida de #part6 será el estado de salida final para toda la construcción.
  6. someprog es ejecutado. El estado de salida se toma en #part5. La salida estándar es tomada por la tubería en #part4 y reenviada a filter. La salida de filter llegará a stdout como se explica en #part4

Comentarios

  • Bien. Para la función, podría hacer (read; exit $REPLY)
  • (exec 3>&- 4>&-; someprog) simplifica a someprog 3>&- 4>&-.
  • Este método también funciona sin subcapas: { { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1

Respuesta

Si bien no es exactamente lo que preguntaste, puedes usar

#!/bin/bash -o pipefail 

para que tus tuberías devuelvan el último retorno distinto de cero.

podría ser un poco menos codificado

Editar: Ejemplo

[root@localhost ~]# false | true [root@localhost ~]# echo $? 0 [root@localhost ~]# set -o pipefail [root@localhost ~]# false | true [root@localhost ~]# echo $? 1 

Comentarios

  • set -o pipefail dentro del script debería ser más robusto, por ejemplo en caso de que alguien ejecute el script a través de bash foo.sh.
  • ¿Cómo funciona? ¿tiene un ejemplo?
  • Tenga en cuenta que -o pipefail no está en POSIX.
  • Esto no funciona en mi BASH 3.2.25 ( 1) -liberación. 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
  • @FelipeAlvarez: algunos entornos (incluido Linux) no ‘ t analizar espacios en #! líneas más allá de la primera, por lo que se convierte en /bin/bash -o pipefail /tmp/ff, en lugar del /bin/bash -o pipefail /tmp/ffgetopt (o similar) análisis utilizando optarg, que es el siguiente elemento en ARGV, como argumento para -o, por lo que falla. Si tuviera que hacer un contenedor (por ejemplo, bash-pf que acaba de hacer exec /bin/bash -o pipefail "$@", y ponerlo en el #! línea, eso funcionaría. Vea también: en.wikipedia.org/wiki/Shebang_%28Unix%29
  • Responder

    Lo que hago cuando es posible es introducir el código de salida de foo en bar. Por ejemplo, si sé que foo nunca produce una línea con solo dígitos, entonces puedo marcar el código de salida:

    { foo; echo "$?"; } | awk "!/[^0-9]/ {exit($0)} {…}" 

    O si sé que la salida de foo nunca contiene una línea con solo .:

    { foo; echo .; echo "$?"; } | awk "/^\.$/ {getline; exit($0)} {…}" 

    Esto siempre se puede hacer si» hay alguna forma de obtener bar para trabajar en todas las líneas excepto en la última, y pasar la última línea como su código de salida.

    Si bar es una tubería compleja cuya salida no es necesario, puede omitir parte de él 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_codes suele ser foo:X bar:Y, pero podría ser bar:Y foo:X si bar se cierra antes de leer todos sus comentarios o si no tiene suerte. Creo que las escrituras en tuberías de hasta 512 bytes son atómicas en todos los sistemas, por lo que las partes foo:$? y bar:$? no se mezclarán como siempre que las cadenas de etiquetas tengan menos de 507 bytes.

    Si necesita capturar el resultado de bar, se vuelve difícil. Puede combinar las técnicas anteriores organizando para que la salida de bar nunca 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 varios scripts ejecutándose simultáneamente, o si el mismo script usa este método en varios lugares, debe hacer asegúrese de que usen diferentes nombres de archivos temporales.
    • Crear un archivo temporal de forma segura en un directorio compartido es difícil. A menudo, /tmp es el único lugar donde una secuencia de comandos seguramente podrá escribir archivos. Utilice mktemp , que no es POSIX, pero está disponible en todos los unices serios hoy en día.
    foo_ret_file=$(mktemp -t) { foo; echo "$?" >"$foo_ret_file"; } | bar bar_ret=$? foo_ret=$(cat "$foo_ret_file"; rm -f "$foo_ret_file") 

    Comentarios

    • Cuando utilizo el enfoque de archivo temporal, prefiero agregar una trampa para EXIT que elimine todos los archivos temporales para que no quede basura incluso si el script muere

    Respuesta

    Comenzando desde la canalización:

    foo | bar | baz 

    Aquí hay una solución general que usa solo el shell POSIX y no archivos temporales:

    exec 4>&1 error_statuses="`((foo || echo "0:$?" >&3) | (bar || echo "1:$?" >&3) | (baz || echo "2:$?" >&3)) 3>&1 >&4`" exec 4>&- 

    $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 comillas alrededor de $error_statuses en mis pruebas; sin ellos grep no puedo diferenciar porque las nuevas líneas se convierten en espacios.

    Respuesta

    Si tiene el paquete moreutils instalado, puede usar la utilidad mispipe que hace exactamente lo que solicitó.

    Respuesta

    Así que quería contribuir con una respuesta como la de lesmana, pero creo que la mía es quizás un poco más simple y un poco más ventajosa pura. Solución 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 desde adentro hacia afuera: command1 se ejecutará e imprimirá su salida normal en stdout (descriptor de archivo 1) , una vez hecho esto, printf ejecutará e imprimirá el código de salida de command1 en su stdout, pero esa stdout se redirige al descriptor de archivo 3.

    Mientras command1 se está ejecutando, 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 s lo que dice la tubería).Luego redirigimos la salida del comando 2 al descriptor de archivo 4, para que también permanezca fuera del descriptor de archivo 1, porque queremos que el descriptor de archivo 1 esté libre un poco más tarde, porque devolveremos la salida de printf en el descriptor de archivo 3 a descriptor de archivo 1 – porque eso es lo que capturará la sustitución del comando (las comillas inversas) y eso es lo que se colocará en la variable.

    El último fragmento de magia es que primero exec 4>&1 lo hicimos como un comando separado – abre el descriptor de archivo 4 como una copia de la salida estándar 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 «sale» de la sustitución de comandos, sigue yendo al descriptor general del archivo 1 del script.

    (El exec 4>&1 tiene para ser un comando separado porque a muchos shells comunes no les gusta cuando intenta escribir en un descriptor de archivo dentro de una sustitución de comando, que se abre en el comando «externo» que usa la sustitución. Así que esta es la forma portátil más simple para hacerlo.)

    Puede verlo de una manera menos técnica y más divertida, como si las salidas de los comandos se saltaran entre sí: command1 pasa a command2, luego la salida de printf salta sobre el comando 2 para que el comando 2 no lo capte, y luego la salida del comando 2 salta y sale de La sustitución de 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 de command2 sigue su camino alegre y se escribe en la salida estándar, al igual que en una tubería normal.

    Además, según tengo entendido, $? todavía contendrá 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 todo efectivamente transparente para el código de retorno del comando dentro de ellos, por lo que el estado de retorno del comando2 debería propagarse; esto, y no tener que definir una función adicional, es por eso que creo que esta podría ser una solución algo mejor que la propuesta por lesmana.

    De acuerdo con las advertencias que menciona lesmana, es posible que command1 en algún momento termine usando descriptores de archivo 3 o 4, por lo que para ser más robusto, debería hacer:

    exec 4>&1 exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1` exec 4>&- 

    Tenga en cuenta que utilizo comandos compuestos en mi ejemplo, pero subshells (usando ( ) en lugar de { } también funcionará, aunque quizás sea menos eficiente.)

    Los comandos heredan descriptores de archivos del proceso que los lanza, por lo que toda la segunda línea heredará el descriptor de archivo cuatro, y el comando compuesto seguido de 3>&1 heredará el descriptor de archivo tres. Por lo tanto, 4>&- se asegura de que el comando compuesto interno no herede el descriptor de archivo cuatro, y 3>&- no heredará el descriptor de archivo tres, por lo que command1 obtiene un entorno más «más limpio» y estándar. También puede mover el 4>&- interno al lado del 3>&-, pero me imagino por qué no limitar su alcance tanto como sea posible.

    No estoy seguro de la frecuencia con la que las cosas usan el descriptor de archivo tres y cuatro directamente; creo que la mayoría de las veces los programas usan llamadas al sistema que devuelven descriptores de archivo que no se usan en ese momento, pero a veces el código se escribe en el archivo descriptor 3 directamente, supongo (podría imaginarme un programa verificando un descriptor de archivo para ver si está abierto, y usándolo si lo está, o comportándose de manera diferente en consecuencia si no lo está). Así que probablemente sea mejor mantener este último en mente y uso para casos de uso general.

    Comentarios

    • Parece interesante, pero puedo ‘ No averiguo lo que espera que haga este comando, y mi computadora puede ‘ t, tampoco; obtengo -bash: 3: Bad file descriptor.
    • @ G-Man Correcto, sigo olvidando que bash no tiene idea de lo que ‘ está haciendo cuando mes a descriptores de archivos, a diferencia de los shells que normalmente uso (el ash que viene con busybox). ‘ te avisaré cuando piense en una solución alternativa que haga feliz a bash. Mientras tanto, si ‘ tienes una caja Debian a la mano, puedes probarla en el tablero, o si ‘ tienes una caja Debian a la mano. puede probarlo con el busybox ash / sh.
    • @ 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 ‘ t queda atrapado por la sustitución del comando, pero una vez fuera de la sustitución del comando, vuelve a colocar fd3 en la salida estándar, por lo que ‘ se envía como se esperaba al mando 2.Cuando command1 sale, printf dispara e imprime su estado de salida, que es capturado en la variable por la sustitución del comando. Desglose muy detallado aquí: stackoverflow.com/questions/985876/tee-and-exit-status/… También , ¿ese comentario tuyo se lee como si estuviera destinado a ser un poco insultante?
    • ¿Por dónde empezar? (1) Lamento si se sintió insultado. «Parece interesante» fue dicho con seriedad; Sería genial si algo tan compacto como ese funcionara tan bien como esperabas. Más allá de eso, estaba diciendo, simplemente, que no entendía qué se suponía que estaba haciendo tu solución. He estado trabajando / jugando con Unix durante mucho tiempo (desde antes de que existiera Linux) y, si no entiendo algo, eso 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)
    • (Continúa) … Ya que usted “… le gusta pensar … que [usted] entiende casi todo más que la persona promedio ”, tal vez debería recordar que el objetivo de Stack Exchange no es ser un servicio de escritura de comandos, produciendo miles de soluciones únicas para preguntas trivialmente distintas; sino más bien para enseñar a la gente 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 entenderlas. Mire la respuesta de lesmana para ver un ejemplo de una excelente explicación. … (Cont.)

    Respuesta

    La solución de lesmana anterior también se puede hacer sin la sobrecarga de iniciar subprocesos anidados usando { .. } en su lugar (recordando que esta forma de comandos agrupados siempre tiene que terminar con punto y coma). Algo como esto:

    { { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | stdintoexitstatus; } 4>&1 

    He comprobado esta construcción con la versión 0.5.5 de dash y las versiones 3.2.25 y 4.2.42 de bash, por lo que incluso si algunos shells no son compatibles con { .. } agrupación, sigue siendo compatible con POSIX.

    Comentarios

    • Esto funciona muy bien con la mayoría de shells I ‘ lo he probado, incluido 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 pipefail en ksh o cualquier número de wait comandos en cualquiera. Creo que, en parte, al menos, sea un problema de análisis para ksh, como si me limitara a usar subcapas, entonces funciona bien, pero incluso con un if para elegir la variante de subcapa para ksh pero dejar los comandos compuestos para otros, falla.

    Respuesta

    Lo siguiente es un complemento de la respuesta de @Patrik, en caso de que no pueda utilizar una de las soluciones comunes.

    Esta respuesta asume lo siguiente:

    • Tiene un shell que no conoce $PIPESTATUS ni set -o pipefail
    • Desea utilizar una tubería para la ejecución paralela, por lo que no hay archivos temporales.
    • Usted no quiero tener más desorden alrededor si interrumpe el script, posiblemente por un corte de energía repentino.
    • Esta solución debe ser relativamente fácil de seguir y limpia para leer.
    • Sí no desea introducir subcapas adicionales.
    • No puede jugar con los descriptores de archivo existentes, por lo que stdin / out / err no debe tocarse (cómo alguna vez puedes introducir uno nuevo temporalmente)

    Supuestos adicionales. Puede deshacerse de todos, pero esto estropea demasiado la receta, por lo que no se trata aquí:

    • Todo lo que desea saber es que todos los comandos en el 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 | baz, sin 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 de 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 luego a FD 9 para escritura y FD 8 para lectura
    • Luego, el tempfile se elimina inmediatamente. Sin embargo, permanece abierto hasta que ambos FD dejan de existir.
    • Ahora se inicia la canalización. Cada paso se agrega a FD 9 solo, si hubo un error.
    • El wait es necesario para ksh, porque ksh más no espera a que finalicen todos los comandos de tubería. Sin embargo, tenga en cuenta que hay efectos secundarios no deseados si algunas tareas en segundo plano están presentes, por lo que lo comenté de forma predeterminada.Si la espera no duele, puede comentarlo.
    • Luego se lee el contenido del archivo. Si está vacío (porque todo funcionó) read devuelve false, por lo que true indica un error

    Esto se puede utilizar como un complemento de reemplazo para un solo comando y solo necesita lo siguiente:

    • FD 9 y 8 sin usar
    • Una sola variable de entorno para contener el nombre del archivo temporal
    • Y esto La receta se puede adaptar a prácticamente cualquier shell, lo que permite la redirección de IO
    • Además, es bastante independiente de la plataforma y no necesita cosas como /proc/fd/N

    ERRORES:

    Este script tiene un error en caso de que /tmp se quede sin espacio. Si necesita protección contra este caso artificial, también, puede hacerlo de la siguiente manera, sin embargo, esto tiene la desventaja de que el número de 0 en 000 depende del número de comandos en elpipe, por lo que es un poco más complicado:

    TMPRESULTS="`mktemp`" { rm -f "$TMPRESULTS" { foo; printf "%1s" "$?" >&9; } | { bar; printf "%1s" "$?" >&9; } | { baz; printf "%1s" "$?" >&9; } #wait read TMPRESULTS <&8 [ 000 = "$TMPRESULTS" ] } 9>>"$TMPRESULTS" 8<"$TMPRESULTS" 

    Notas de portabilidad:

    • ksh y shells similares que solo esperan el último comando pipe necesitan wait sin comentar

    • El último ejemplo usa printf "%1s" "$?" en lugar de echo -n "$?" porque es más portátil. No todas las plataformas interpretan -n correctamente.

    • printf "$?" también lo haría, 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 más altos en plataformas que admiten múltiples dígitos. AFAIR, un shell compatible con POSIX solo necesita admitir un solo dígito.

    • Se probó con Debian 8.2 sh, bash, ksh, ash, sash e incluso csh

    Respuesta

    Esto es portátil, es decir funciona con cualquier shell compatible con POSIX, no requiere que se pueda escribir en el directorio actual y permite que se ejecuten simultáneamente varios scripts que utilizan el mismo truco.

    (foo;echo $?>/tmp/_$$)|(bar;exit $(cat /tmp/_$$;rm /tmp/_$$)) 

    Editar: aquí hay una versión más fuerte siguiendo los comentarios de Gilles:

    (s=/tmp/.$$_$RANDOM;((foo;echo $?>$s)|(bar)); exit $(cat $s;rm $s)) 

    Edit2: y aquí hay una variante un poco más ligera después del comentario dubiousjim:

    (s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s)) 

    Comentarios

    • Esto no ‘ t funciona por varias razones. 1. El archivo temporal puede leerse antes de ‘ s escrito. 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 se haya completado la canalización. Para resolver 2 y 3, use un archivo temporal con un nombre generado aleatoriamente o en un directorio privado.
    • +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.
    • Puede guardar algunas subcapas: (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.

    Respuesta

    Con un poco de precaución, esto debería funcionar:

    foo-status=$(mktemp -t) (foo; echo $? >$foo-status) | bar foo_status=$(cat $foo-status) 

    Comentarios

    • ¿Qué tal una limpieza como jlliagre? Don ‘ ¿no dejas un archivo llamado foo-status?
    • @Johan: Si prefieres mi sugerencia, no ‘ no dude en votarlo;) Además de no dejar un archivo, tiene la ventaja de permitir que múltiples procesos lo ejecuten simultáneamente y no es necesario que se pueda escribir en el directorio actual.

    Respuesta

    El siguiente bloque «if» se ejecutará solo si el «comando» tuvo éxito:

    if command; then # ... fi 

    Específicamente hablando, puede ejecutar algo como esto:

    haconf_out=/path/to/some/temporary/file if haconf -makerw > "$haconf_out" 2>&1; then grep -iq "Cluster already writable" "$haconf_out" # ... fi 

    Que ejecutará haconf -makerw y almacenar su stdout y stderr en «$ haconf_out». Si el valor devuelto por haconf es verdadero, entonces el bloque «if» se ejecutará y grep leerá «$ haconf_out», intentando para compararlo con el «Clúster que ya se puede escribir».

    Observe que las tuberías se limpian automáticamente; con la redirección, «tendrá que tener cuidado de eliminar» $ haconf_out «cuando haya terminado.

    No tan elegante como pipefail, pero una alternativa legítima si esta funcionalidad no está al alcance.

    Responder

    Alternate example for @lesmana solution, possibly simplified. Provides logging to file if desired. ===== $ cat z.sh TEE="cat" #TEE="tee z.log" #TEE="tee -a z.log" exec 8>&- 9>&- { { { { #BEGIN - add code below this line and before #END ./zz.sh echo ${?} 1>&8 # use exactly 1x prior to #END #END } 2>&1 | ${TEE} 1>&9 } 8>&1 } | exit $(read; printf "${REPLY}") } 9>&1 exit ${?} $ cat zz.sh echo "my script code..." exit 42 $ ./z.sh; echo "status=${?}" my script code... status=42 $ 

    Responder

    (Con bash al menos) combinado con set -e se puede usar subshell para emular explícitamente pipefail y salir en caso de error de pipe

    set -e foo | bar ( exit ${PIPESTATUS[0]} ) rest of program 

    Entonces, 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 que foo imprime su propio error, que es suficiente para comprender la razón del error)

    Respuesta

    EDIT : Esta respuesta es incorrecta, pero interesante, así que la dejaré para el futuro referencia.


    Agregar un ! al comando invierte el código de retorno.

    http://tldp.org/LDP/abs/html/exit-status.html

    # =========================================================== # # 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. # =========================================================== # 

    Comentarios

    • Creo que esto no está relacionado. En su ejemplo, quiero saber el código de salida de ls, no invertir el código de salida de bogus_command
    • Sugiero retirar esa respuesta.
    • Bueno, parece que yo ‘ soy un idiota. Yo ‘ he usado esto en un script antes de pensar que hizo lo que quería el OP. Menos mal que no ‘ lo usé para algo importante

    Deja una respuesta

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *