when - Redirigir stderr y stdout en Bash
hello world bash (14)
@ fernando-fabreti
Agregando a lo que hiciste, cambié ligeramente las funciones y eliminé el cierre y funcionó para mí.
function saveStandardOutputs {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
exec 3>&1
exec 4>&2
trap restoreStandardOutputs EXIT
else
echo "[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"
exit 1;
fi
}
# Params: $1 => logfile to write to
function redirectOutputsToLogfile {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
LOGFILE=$1
if [ -z "$LOGFILE" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]"
fi
if [ ! -f $LOGFILE ]; then
touch $LOGFILE
fi
if [ ! -f $LOGFILE ]; then
echo "[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]"
exit 1
fi
saveStandardOutputs
exec 1>>${LOGFILE}
exec 2>&1
OUTPUTS_REDIRECTED="true"
else
echo "[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"
exit 1;
fi
}
function restoreStandardOutputs {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
exec 1>&3 #restore stdout
exec 2>&4 #restore stderr
OUTPUTS_REDIRECTED="false"
fi
}
LOGFILE_NAME="tmp/one.log"
OUTPUTS_REDIRECTED="false"
echo "this goes to stdout"
redirectOutputsToLogfile $LOGFILE_NAME
echo "this goes to logfile"
echo "${LOGFILE_NAME}"
restoreStandardOutputs
echo "After restore this goes to stdout"
Quiero redireccionar tanto stdout como stderr de un proceso a un solo archivo. ¿Cómo hago eso en Bash?
Curiosamente, esto funciona:
yourcommand &> filename
Pero esto da un error de sintaxis:
yourcommand &>> filename
syntax error near unexpected token `>''
Tienes que usar:
yourcommand 1>> filename 2>&1
Echa un vistazo here . Debiera ser:
yourcommand &>filename
(Redirige tanto stdout
como stderr
al nombre de archivo).
Forma "más fácil" (solo bash4): ls * 2>&- 1>&-
.
Las siguientes funciones se pueden usar para automatizar el proceso de alternar salidas entre stdout / stderr y un archivo de registro.
#!/bin/bash
#set -x
# global vars
OUTPUTS_REDIRECTED="false"
LOGFILE=/dev/stdout
# "private" function used by redirect_outputs_to_logfile()
function save_standard_outputs {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"
exit 1;
fi
exec 3>&1
exec 4>&2
trap restore_standard_outputs EXIT
}
# Params: $1 => logfile to write to
function redirect_outputs_to_logfile {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"
exit 1;
fi
LOGFILE=$1
if [ -z "$LOGFILE" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]"
fi
if [ ! -f $LOGFILE ]; then
touch $LOGFILE
fi
if [ ! -f $LOGFILE ]; then
echo "[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]"
exit 1
fi
save_standard_outputs
exec 1>>${LOGFILE%.log}.log
exec 2>&1
OUTPUTS_REDIRECTED="true"
}
# "private" function used by save_standard_outputs()
function restore_standard_outputs {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot restore standard outputs because they have NOT been redirected"
exit 1;
fi
exec 1>&- #closes FD 1 (logfile)
exec 2>&- #closes FD 2 (logfile)
exec 2>&4 #restore stderr
exec 1>&3 #restore stdout
OUTPUTS_REDIRECTED="false"
}
Ejemplo de uso dentro del script:
echo "this goes to stdout"
redirect_outputs_to_logfile /tmp/one.log
echo "this goes to logfile"
restore_standard_outputs
echo "this goes to stdout"
Para la situación, cuando es necesaria la "tubería" puede usar:
| &
Por ejemplo:
echo -ne "15/n100/n"|sort -c |& tee >sort_result.txt
o
TIMEFORMAT=%R;for i in `seq 1 20` ; do time kubectl get pods |grep node >>js.log ; done |& sort -h
Estas soluciones basadas en bash pueden canalizar STDOUT y STDERR por separado (desde STDERR de "sort -c" o desde STDERR hasta "sort -h").
Para tcsh, tengo que usar el siguiente comando:
command >& file
Si usa el command &> file
, aparecerá el error "Comando nulo no válido".
Puede redireccionar stderr a stdout y el stdout a un archivo:
some_command >file.log 2>&1
Consulte here
Este formato es preferible al formato más popular &> que solo funciona en bash. En el shell Bourne se podría interpretar que se ejecuta el comando en segundo plano. También el formato es más legible 2 (es STDERR) redirigido a 1 (STDOUT).
EDITAR: se modificó el orden como se señala en los comentarios.
Quería una solución para tener el resultado de stdout plus stderr escrito en un archivo de registro y stderr aún en la consola. Así que necesitaba duplicar la salida stderr vía tee.
Esta es la solución que encontré:
command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
- Primer intercambio de stderr y stdout.
- a continuación, agregue la salida estándar al archivo de registro
- tubo stderr a la T y anexarlo también al archivo de registro
Respuesta corta: Command >filename 2>&1
o Command &>filename
Explicación:
Considere el siguiente código que imprime la palabra "stdout" a stdout y la palabra "stderror" a stderror.
$ (echo "stdout"; echo "stderror" >&2)
stdout
stderror
Tenga en cuenta que el operador ''&'' le dice a bash que 2 es un descriptor de archivo (que apunta al stderr) y no un nombre de archivo. Si stderror
el ''&'', este comando imprimirá stdout
a stdout, creará un archivo llamado "2" y escribirá stderror
allí.
Al experimentar con el código anterior, puede ver por sí mismo exactamente cómo funcionan los operadores de redirección. Por ejemplo, al cambiar qué archivo de cuál de los dos descriptores 1,2
se redirige a /dev/null
las siguientes dos líneas de código eliminan todo del stdout, y todo del stderror respectivamente (imprimiendo lo que queda).
$ (echo "stdout"; echo "stderror" >&2) 1>/dev/null
stderror
$ (echo "stdout"; echo "stderror" >&2) 2>/dev/null
stdout
Ahora, podemos explicar por qué la solución por la que el siguiente código no produce resultados:
(echo "stdout"; echo "stderror" >&2) >/dev/null 2>&1
Para comprender realmente esto, le recomiendo que lea esta página web en las tablas de descriptores de archivos . Suponiendo que haya hecho esa lectura, podemos proceder. Tenga en cuenta que Bash procesa de izquierda a derecha; por lo tanto, Bash ve >/dev/null
primero (que es lo mismo que 1>/dev/null
), y configura el descriptor de archivo 1 para que apunte a / dev / null en lugar de la salida estándar. Una vez hecho esto, Bash se mueve hacia la derecha y ve 2>&1
. Esto configura el descriptor de archivo 2 para que apunte al mismo archivo que el descriptor de archivo 1 (y no al descriptor de archivo 1 en sí mismo (consulte este recurso en los punteros para obtener más información)). Como el descriptor de archivo 1 apunta a / dev / null, y el descriptor de archivo 2 apunta al mismo archivo que el descriptor de archivo 1, el descriptor de archivo 2 ahora también apunta a / dev / null. Por lo tanto, ambos descriptores de archivo apuntan a / dev / null, y esta es la razón por la que no se procesa ninguna salida.
Para comprobar si realmente entiende el concepto, intente adivinar la salida cuando cambiemos el orden de redirección:
(echo "stdout"; echo "stderror" >&2) 2>&1 >/dev/null
Stderror
El razonamiento aquí es que al evaluar de izquierda a derecha, Bash ve 2> & 1, y así establece el descriptor de archivo 2 para que apunte al mismo lugar que el descriptor de archivo 1, es decir, stdout. Luego establece el descriptor de archivo 1 (recuerde que> / dev / null = 1> / dev / null) para apuntar a> / dev / null, eliminando así todo lo que normalmente se enviaría a la salida estándar. Por lo tanto, todo lo que nos queda es lo que no se envió a la salida estándar en la subshell (el código entre paréntesis), es decir, "stderror". Lo interesante a tener en cuenta es que aunque 1 es solo un puntero a la salida estándar, redirigir el puntero 2 a 1 a través de 2>&1
NO forma una cadena de punteros 2 -> 1 -> stdout. Si lo hiciera, como resultado de redirigir 1 a / dev / null, el código 2>&1 >/dev/null
daría la cadena de punteros 2 -> 1 -> / dev / null, y así el código no generaría nada, En contraste con lo que vimos arriba.
Finalmente, observaría que hay una forma más sencilla de hacer esto:
Desde la sección 3.6.4 here , vemos que podemos usar el operador &>
para redireccionar tanto stdout como stderr. Por lo tanto, para redirigir la salida stderr y stdout de cualquier comando a /dev/null
(que elimina la salida), simplemente escribimos $ command &> /dev/null
o en el caso de mi ejemplo:
$ (echo "stdout"; echo "stderror" >&2) &>/dev/null
Puntos clave:
- Los descriptores de archivo se comportan como punteros (aunque los descriptores de archivo no son lo mismo que los punteros de archivo)
- La redirección de un descriptor de archivo "a" a un descriptor de archivo "b" que apunta al archivo "f", hace que el descriptor de archivo "a" apunte al mismo lugar que el descriptor de archivo b - archivo "f". NO forma una cadena de punteros a -> b -> f
- Debido a lo anterior, el orden importa,
2>&1 >/dev/null
is! =>/dev/null 2>&1
. ¡Uno genera salida y el otro no!
Por último, eche un vistazo a estos grandes recursos:
here , una explicación de las tablas de descriptores de archivos , Introducción a los punteros
# Close STDOUT file descriptor
exec 1<&-
# Close STDERR FD
exec 2<&-
# Open STDOUT as $LOG_FILE file for read and write.
exec 1<>$LOG_FILE
# Redirect STDERR to STDOUT
exec 2>&1
echo "This line will appear in $LOG_FILE, not ''on screen''"
Ahora, el eco simple se escribirá en $ LOG_FILE. Útil para la demonización.
Al autor del post original,
Depende de lo que necesites lograr. Si solo necesita redirigir dentro / fuera de un comando que llama desde su script, las respuestas ya están dadas. El mío es acerca de la redirección dentro del script actual que afecta a todos los comandos / incorporados (incluye las bifurcaciones) después del fragmento de código mencionado.
Otra solución interesante consiste en redirigir a std-err / out AND a logger o log file a la vez, lo que implica dividir "un flujo" en dos. Esta función es proporcionada por el comando ''tee'' que puede escribir / adjuntar a varios descriptores de archivos (archivos, sockets, tuberías, etc.) a la vez: tee FILE1 FILE2 ...> (cmd1)> (cmd2) ...
exec 3>&1 4>&2 1> >(tee >(logger -i -t ''my_script_tag'') >&3) 2> >(tee >(logger -i -t ''my_script_tag'') >&4)
trap ''cleanup'' INT QUIT TERM EXIT
get_pids_of_ppid() {
local ppid="$1"
RETVAL=''''
local pids=`ps x -o pid,ppid | awk "//$2 == //"$ppid//" { print //$1 }"`
RETVAL="$pids"
}
# Needed to kill processes running in background
cleanup() {
local current_pid element
local pids=( "$$" )
running_pids=("${pids[@]}")
while :; do
current_pid="${running_pids[0]}"
[ -z "$current_pid" ] && break
running_pids=("${running_pids[@]:1}")
get_pids_of_ppid $current_pid
local new_pids="$RETVAL"
[ -z "$new_pids" ] && continue
for element in $new_pids; do
running_pids+=("$element")
pids=("$element" "${pids[@]}")
done
done
kill ${pids[@]} 2>/dev/null
}
Así, desde el principio. Supongamos que tenemos el terminal conectado a / dev / stdout (FD # 1) y / dev / stderr (FD # 2). En la práctica, podría ser una tubería, un zócalo o lo que sea.
- Cree los FD # 3 y # 4 y apunte a la misma "ubicación" como # 1 y # 2 respectivamente. Cambiar FD # 1 no afecta a FD # 3 a partir de ahora. Ahora, FDs # 3 y # 4 apuntan a STDOUT y STDERR respectivamente. Estos serán utilizados como terminal real STDOUT y STDERR.
- 1>> (...) redirige STDOUT al comando en parens
- parens (sub-shell) ejecuta la lectura ''tee'' de STDOUT (pipe) de exec y redirige al comando ''logger'' a través de otro pipe a sub-shell en parens. Al mismo tiempo, copia la misma entrada a FD # 3 (terminal)
- La segunda parte, muy similar, trata sobre hacer el mismo truco para STDERR y FDs # 2 y # 4.
El resultado de ejecutar un script con la línea anterior y, además, este:
echo "Will end up in STDOUT(terminal) and /var/log/messages"
...es como sigue:
$ ./my_script
Will end up in STDOUT(terminal) and /var/log/messages
$ tail -n1 /var/log/messages
Sep 23 15:54:03 wks056 my_script_tag[11644]: Will end up in STDOUT(terminal) and /var/log/messages
Si desea ver una imagen más clara, agregue estas 2 líneas al script:
ls -l /proc/self/fd/
ps xf
LOG_FACILITY="local7.notice"
LOG_TOPIC="my-prog-name"
LOG_TOPIC_OUT="$LOG_TOPIC-out[$$]"
LOG_TOPIC_ERR="$LOG_TOPIC-err[$$]"
exec 3>&1 > >(tee -a /dev/fd/3 | logger -p "$LOG_FACILITY" -t "$LOG_TOPIC_OUT" )
exec 2> >(logger -p "$LOG_FACILITY" -t "$LOG_TOPIC_ERR" )
Está relacionado: Escribir stdOut y stderr en syslog.
Casi funciona, pero no de xinted;
bash your_script.sh 1>file.log 2>&1
1>file.log
indica al shell que envíe STDOUT al archivo file.log
, y 2>&1
le indica que redirija STDERR (descriptor de archivo 2) a STDOUT (descriptor de archivo 1).
Nota: el orden importa como liw.fi señaló, 2>&1 1>file.log
no funciona.
do_something 2>&1 | tee -a some_file
Esto va a redirigir stderr a stdout y stdout a some_file
e imprimirlo a stdout.