Quiero ejecutar un comando de larga duración en Bash, y tanto la captura de su estado de salida, y el primer golpe su salida.
Entonces hago esto:
command | tee out.txt
ST=$?
El problema es que la variable ST captura el estado de salida tee
y no del comando. ¿Como puedo resolver esto?
Tenga en cuenta que el comando se está ejecutando durante mucho tiempo y redirigir la salida a un archivo para verlo más tarde no es una buena solución para mí.
bash
shell
error-handling
pipe
flybywire
fuente
fuente
Respuestas:
Hay una variable interna de Bash llamada
$PIPESTATUS
; es una matriz que contiene el estado de salida de cada comando en su última tubería de comandos en primer plano.Otra alternativa que también funciona con otros shells (como zsh) sería habilitar pipefail:
La primera opción no funciona
zsh
debido a una sintaxis un poco diferente.fuente
exit ${PIPESTATUS[0]}
.usar bash
set -o pipefail
es útilfuente
( set -o pipefail; command | tee out.txt ); ST=$?
set -o pipefail
y luego hacer el comando, e inmediatamente despuésset +o pipefail
deshabilitar la opción.-o pipefail
él sabría si la tubería falla, pero si fallan tanto 'comando' como 'tee', recibiría el código de salida de 'tee'.Solución tonta: conectarlos a través de una tubería con nombre (mkfifo). Entonces el comando se puede ejecutar en segundo lugar.
fuente
mkfifo
y pueden requerirlomknod -p
si no recuerdo mal.mkfifo
omknod -p
: en mi caso de comando apropiado para crear el archivo de la tubería eramknod FILE_NAME p
.Hay una matriz que le da el estado de salida de cada comando en una tubería.
fuente
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:
Consulte mi respuesta para la misma pregunta en unix.stackexchange.com para obtener una explicación detallada y una alternativa sin subcapas y algunas advertencias.
fuente
Al combinar
PIPESTATUS[0]
y el resultado de ejecutar elexit
comando en una subshell, puede acceder directamente al valor de retorno de su comando inicial:command | tee ; ( exit ${PIPESTATUS[0]} )
Aquí hay un ejemplo:
Te regalaré:
return value: 1
fuente
VALUE=$(might_fail | piping)
que no establecerá PIPESTATUS en el shell maestro pero establecerá su nivel de error. Al usar:VALUE=$(might_fail | piping; exit ${PIPESTATUS[0]})
obtengo lo que quería.command_might_fail | grep -v "line_pattern_to_exclude" || exit ${PIPESTATUS[0]}
en caso de que no sea tee sino filtrado grepAsí 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 icommand1 en su stdout, pero esa 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 traeremos la salida de printf en el descriptor de archivo 3 nuevamente al 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 "sale" de la sustitución del comando, efectivamente sigue yendo al descriptor de archivo general del script 1.(
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 utilizo 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
En Ubuntu y Debian, puedes
apt-get install moreutils
. Contiene una utilidad llamadamispipe
que devuelve el estado de salida del primer comando en la tubería.fuente
A diferencia de la respuesta de @ cODAR, esto devuelve el código de salida original del primer comando y no solo 0 para el éxito y 127 para el fracaso. Pero como señaló @Chaoran, puedes llamar
${PIPESTATUS[0]}
. Sin embargo, es importante que todo se ponga entre paréntesis.fuente
Fuera de bash, puedes hacer:
Esto es útil, por ejemplo, en los scripts ninja donde se espera que esté el shell
/bin/sh
.fuente
PIPESTATUS [@] debe copiarse en una matriz inmediatamente después de que regrese el comando de canalización. Cualquier lectura de PIPESTATUS [@] borrará el contenido. Cópielo en otra matriz si planea verificar el estado de todos los comandos de tubería. PS es el mismo valor que el último elemento de "$ {PIPESTATUS [@]}", y leerlo parece destruir "$ {PIPESTATUS [@]}", pero no lo he verificado.
Esto no funcionará si la tubería está en un sub-shell. Para una solución a ese problema,
vea bash pipestatus en el comando backticked?
fuente
La forma más simple de hacer esto en bash simple es usar la sustitución de procesos en lugar de una tubería. Existen varias diferencias, pero probablemente no importen demasiado para su caso de uso:
pipefail
opción y laPIPESTATUS
variable son irrelevantes para la sustitución de procesos.Con la sustitución del proceso, bash solo comienza el proceso y se olvida de él, ni siquiera es visible en él
jobs
.Las diferencias mencionadas a un lado,
consumer < <(producer)
yproducer | consumer
son esencialmente equivalentes.Si desea voltear cuál es el proceso "principal", simplemente voltee los comandos y la dirección de la sustitución
producer > >(consumer)
. En tu caso:Ejemplo:
Como dije, hay diferencias con la expresión de la tubería. Es posible que el proceso nunca deje de ejecutarse, a menos que sea sensible al cierre de la tubería. En particular, puede seguir escribiendo cosas en su stdout, lo que puede ser confuso.
fuente
Solución de concha pura:
Y ahora con el segundo
cat
reemplazado porfalse
:Tenga en cuenta que el primer gato también falla, porque su stdout se cierra. El orden de los comandos fallidos en el registro es correcto en este ejemplo, pero no confíe en él.
Este método permite capturar stdout y stderr para los comandos individuales para que luego pueda volcar eso también en un archivo de registro si se produce un error, o simplemente eliminarlo si no hay error (como la salida de dd).
fuente
Base en la respuesta de @ brian-s-wilson; esta función de ayuda bash:
utilizado así:
1: get_bad_things debe tener éxito, pero no debe producir resultados; pero queremos ver la salida que produce
2: toda la tubería debe tener éxito
fuente
A veces puede ser más simple y claro usar un comando externo, en lugar de profundizar en los detalles de bash. pipeline , desde el execline del lenguaje de scripting de proceso mínimo , sale con el código de retorno del segundo comando *, al igual que lo hace un
sh
pipeline, pero a diferenciash
, permite invertir la dirección de la tubería, para que podamos capturar el código de retorno del productor proceso (el siguiente está todo en lash
línea de comando, pero conexecline
instalado):El uso
pipeline
tiene las mismas diferencias con las tuberías de bash nativas que la sustitución del proceso de bash utilizada en la respuesta # 43972501 .* En realidad
pipeline
no sale en absoluto a menos que haya un error. Se ejecuta en el segundo comando, por lo que es el segundo comando el que hace la devolución.fuente