Redirige stderr y stdout en Bash

Quiero redireccionar stdout y stderr de un proceso a un solo archivo. ¿Cómo hago eso en Bash?

Echa un vistazo aquí . Debiera ser:

yourcommand &>filename 

(redirige tanto stdout como stderr al nombre del archivo).

 do_something 2>&1 | tee -a some_file 

Esto va a redirigir stderr a stdout y stdout a some_file e imprimirlo a stdout.

Puede redirigir stderr a stdout y stdout a un archivo:

 some_command >file.log 2>&1 

Ver http://tldp.org/LDP/abs/html/io-redirection.html

Se prefiere este formato que el formato &> más popular que solo funciona en bash. En Bourne shell podría interpretarse como ejecutar el comando en segundo plano. También el formato es más legible 2 (es STDERR) redirigido a 1 (STDOUT).

EDITAR: cambió el orden como se señala en los comentarios

 # 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, echo simple escribirá en $ LOG_FILE. Útil para demonizar.

Para el autor de la publicación original,

Depende de lo que necesites lograr. Si solo necesita redireccionar hacia adentro / hacia afuera de un comando al que llama desde su script, las respuestas ya están dadas. El mío se trata de redirigir dentro de la secuencia de comandos actual que afecta a todos los comandos / built-ins (incluye tenedores) después del fragmento de código mencionado.


Otra buena solución es redirigir a STD-err / out Y al registrador o al archivo de registro de una vez, lo que implica dividir “una secuencia” en dos. Esta funcionalidad es provista por el comando ‘tee’ que puede escribir / agregar a varios descriptores de archivos (archivos, tomas de stream, 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 } 

Entonces, desde el principio. Supongamos que tenemos un terminal conectado a / dev / stdout (FD # 1) y / dev / stderr (FD # 2). En la práctica, podría ser una tubería, zócalo o lo que sea.

  • Crea FDs # 3 y # 4 y apunta a la misma “ubicación” como # 1 y # 2 respectivamente. Cambiar FD # 1 no afecta FD # 3 a partir de ahora. Ahora, los FDs # 3 y # 4 apuntan a STDOUT y STDERR respectivamente. Estos se usarán como terminal real STDOUT y STDERR.
  • 1>> (…) redirige STDOUT al comando en parens
  • parens (sub-shell) ejecuta la lectura ‘tee’ del STDOUT (canal) del ejecutivo y redirecciona al comando ‘logger’ a través de otro conducto al subconjunto 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 guión:

 ls -l /proc/self/fd/ ps xf 
 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 es importante como lo señaló liw.fi, 2>&1 1>file.log no funciona.

Curiosamente, esto funciona:

 yourcommand &> filename 

Pero esto da un error de syntax:

 yourcommand &>> filename syntax error near unexpected token `>' 

Tienes que usar:

 yourcommand 1>> filename 2>&1 

Respuesta corta: Command >filename 2>&1 o Command &>filename


Explicación:

Considere el siguiente código que imprime la palabra “stdout” en stdout y la palabra “stderror” en 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 dejamos fuera el ‘&’, este comando imprimiría stdout a stdout, y crearía un archivo llamado “2” y escribiría stderror allí.

Al experimentar con el código anterior, puede ver exactamente cómo funcionan los operadores de redirección. Por ejemplo, cambiando el archivo que de los dos descriptores 1,2 , se redirige a /dev/null las dos líneas siguientes de código eliminan todo del stdout, y todo desde 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 cual el siguiente código no produce ningún resultado:

 (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 hayas hecho esa lectura, podemos proceder. Tenga en cuenta que Bash procesa de izquierda a derecha; por lo tanto, Bash ve primero >/dev/null (que es lo mismo que 1>/dev/null ), y establece el descriptor de archivo 1 para que apunte a / dev / null en lugar de stdout. Habiendo hecho esto, Bash luego 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 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 ningún resultado.


Para probar si realmente entiende el concepto, trate de adivinar el resultado cuando cambiemos el orden de redireccionamiento:

 (echo "stdout"; echo "stderror" >&2) 2>&1 >/dev/null 

stderror

El razonamiento aquí es que al evaluar de izquierda a derecha, Bash ve 2> y 1, y por lo tanto establece el descriptor de archivo 2 para que apunte al mismo lugar que el descriptor de archivo 1, es decir, stdout. A continuación, establece el descriptor de archivo 1 (recuerde que> / dev / null = 1> / dev / null) para que apunte a> / dev / null, eliminando así todo lo que normalmente se enviaría a la salida estándar. Por lo tanto, todo lo que queda es lo que no se envió a stdout en la subcadena (el código entre paréntesis), es decir, “stderror”. Lo interesante a tener en cuenta es que aunque 1 es solo un puntero al stdout, 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 le daría a la cadena del apuntador 2 -> 1 -> / dev / null, y así el código no generaría nada, en contraste con lo que vimos arriba.


Finalmente, señalaría que hay una manera más simple de hacer esto:

De la sección 3.6.4 aquí , vemos que podemos usar el operador &> para redirigir 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)
  • Al redirigir un descriptor de archivo “a” a un descriptor de archivo “b” que apunta al archivo “f”, el descriptor de archivo “a” apunta 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 es importante, 2>&1 >/dev/null is! = >/dev/null 2>&1 . ¡Uno genera salida y el otro no!

Por último, eche un vistazo a estos excelentes recursos:

Bash Documentation on Redirection , Explicación de tablas de descriptores de archivos , Introducción a los punteros

 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; (

Quería una solución para tener la salida de stdout plus stderr escrita en un archivo de registro y stderr todavía en la consola. Así que necesitaba duplicar la salida de stderr a través de tee.

Esta es la solución que encontré:

 command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile 
  • Primer swder stderr y stdout
  • a continuación, agregue el stdout al archivo de registro
  • pipe stderr a tee y añádalo también al archivo de registro

La manera “más fácil” (solo bash4): ls * 2>&- 1>&- .

Para tcsh, tengo que usar el siguiente comando:

 command >& file 

Si usa command &> file , dará el error “Invalid null command”.

Las siguientes funciones se pueden usar para automatizar el proceso de alternar salidas beetwen 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 restre_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 restre_standard_outputs { if [ "$OUTPUTS_REDIRECTED" == "false" ]; then echo "[ERROR]: ${FUNCNAME[0]}: Cannot restre 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 #restre stderr exec 1>&3 #restre 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" restre_standard_outputs echo "this goes to stdout"