bash: redirige(y agrega) stdout y stderr al archivo y terminal y obtiene el estado de salida correcto
tee (4)
¿Quizás podría poner el valor de salida de PIPESTATUS en $?
command 2>&1 | tee -a file.txt ; ( exit ${PIPESTATUS} )
Para redirigir (y anexar) stdout y stderr a un archivo, y al mismo tiempo mostrarlo en la terminal, hago esto:
command 2>&1 | tee -a file.txt
Sin embargo, ¿hay alguna otra forma de hacer esto para que obtenga un valor preciso para el estado de salida?
Es decir, si pruebo $?
, Quiero ver el estado de salida del command
, no el estado de tee
de tee
.
Sé que puedo usar ${PIPESTATUS[0]}
aquí en lugar de $?
, pero estoy buscando otra solución que no implique tener que verificar PIPESTATUS
.
Hay una forma arcana de POSIX de hacer esto:
exec 4>&1; R=$({ { command1; echo $? >&3 ; } | { command2 >&4; } } 3>&1); exec 4>&-
Establecerá la variable R
en el valor de retorno de command1
, y la salida de pipe de command1
en command2
, cuyo resultado se redirige a la salida del shell primario.
Otra posibilidad, con algunos sabores bash
, es activar la opción pipefail
:
pipefail
Si se establece, el valor de retorno de una tubería es el valor del último comando (más a la derecha) para salir con un estado distinto de cero, o cero si todos los comandos en la tubería salen exitosamente. Esta opción está deshabilitada por defecto.
set -o pipefail
...
command 2>&1 | tee -a file.txt || echo "Command (or tee?) failed with status $?"
Habiendo dicho esto, la única manera de lograr la funcionalidad de PIPESTATUS de forma portátil (por ejemplo, también podría funcionar con POSIX sh
) es un poco complicada, es decir, requiere un archivo temporal para propagar un estado de salida de tubería al proceso de shell primario:
{ command 2>&1 ; echo $? >"/tmp/~pipestatus.$$" ; } | tee -a file.txt
if [ "`cat /"/tmp/~pipestatus.$$/"`" -ne 0 ] ; then
...
fi
o, encapsulando para reutilizar:
log2file() {
LOGFILE="$1" ; shift
{ "$@" 2>&1 ; echo $? >"/tmp/~pipestatus.$$" ; } | tee -a "$LOGFILE"
MYPIPESTATUS="`cat /"/tmp/~pipestatus.$$/"`"
rm -f "/tmp/~pipestatus.$$"
return $MYPIPESTATUS
}
log2file file.txt command param1 "param 2" || echo "Command failed with status $?"
o, más genéricamente quizás:
save_pipe_status() {
STATUS_ID="$1" ; shift
"$@"
echo $? >"/tmp/~pipestatus.$$.$STATUS_ID"
}
get_pipe_status() {
STATUS_ID="$1" ; shift
return `cat "/tmp/~pipestatus.$$.$STATUS_ID"`
}
save_pipe_status my_command_id ./command param1 "param 2" | tee -a file.txt
get_pipe_status my_command_id || echo "Command failed with status $?"
...
rm -f "/tmp/~pipestatus.$$."* # do this in a trap handler, too, to be really clean
Use la sustitución del proceso:
command > >( tee -a "$logfile" ) 2>&1
tee se ejecuta en una subshell así que $? mantiene el estado de salida del comando .