Redirigir stderr y stdout en Bash

679

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

flybywire
fuente
2
Me gustaría decir que esta es una pregunta sorprendentemente útil. Muchas personas no saben cómo hacer esto, ya que no tienen que hacerlo con frecuencia, y no es el mejor comportamiento documentado de Bash.
Robert Wm Ruedisueli
2
A veces es útil ver el resultado (como de costumbre) Y redirigirlo a un archivo. Vea la respuesta de Marko a continuación. (Lo digo aquí porque es fácil de ver justo en la primera respuesta aceptada si eso es suficiente para resolver un problema, pero otras respuestas a menudo proporcionan información útil.)
jvriesem

Respuestas:

763

Echa un vistazo aquí . Debiera ser:

yourcommand &>filename

(redirecciona a ambos stdouty stderral nombre del archivo).

Dirkgently
fuente
29
Esta sintaxis está en desuso según la Wiki Bash Hackers . ¿Lo es?
Salman von Abbas
20
De acuerdo con wiki.bash-hackers.org/scripting/obsolete , parece obsoleto en el sentido de que no forma parte de POSIX, pero la página del manual de bash no menciona que se eliminará de bash en un futuro próximo. La página del manual especifica una preferencia para '&>' sobre '> &', que de otro modo es equivalente.
chepner
13
Supongo que no deberíamos usar &> ya que no está en POSIX, y los shells comunes como "dash" no lo admiten.
Sam Watkins el
27
Una sugerencia adicional: si usa esto en un script, asegúrese de que comience con #!/bin/bashmás que con #!/bin/sh, ya que requiere bash.
Tor Klingberg
8
O & >> para agregar en lugar de sobrescribir.
Alexander Gonchiy
449
do_something 2>&1 | tee -a some_file

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

Marko
fuente
16
En AIX (ksh) su solución funciona. La respuesta aceptada do_something &>filenameno. +1.
Retenido el
11
@Daniel, pero esta pregunta es específicamente sobre bash
John La Rooy
3
Tengo Ambiguous output redirect.alguna idea de por qué?
Alexandre Holden Daly
1
Tengo un script ruby ​​(que no quiero modificar de ninguna manera) que imprime los mensajes de error en negrita roja. Este script de ruby ​​se invoca desde mi script de bash (que puedo modificar). Cuando uso lo anterior, imprime los mensajes de error en texto plano, menos el formato. ¿Hay alguna forma de conservar el formato en pantalla y obtener la salida (tanto stdout como stderr) en un archivo también?
atlantis
8
Tenga en cuenta que (por defecto) esto tiene el efecto secundario que $?ya no se refiere al estado de salida de do_something, sino al estado de salida de tee.
Flimm
255

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 se podría interpretar como ejecutar el comando en segundo plano. Además, el formato es más legible 2 (es STDERR) redirigido a 1 (STDOUT).

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

f3lix
fuente
47
Esto redirige stderr al stdout original, no al archivo donde va stdout. Pon '2> & 1' después de '> file.log' y funciona.
1
¿Cuál es la ventaja de este enfoque sobre some_command &> file.log?
ubermonkey
66
Si desea agregar a un archivo, debe hacerlo de esta manera: echo "foo" 2> & 1 1 >> bar.txt AFAIK no hay manera de agregar usando &>
SlappyTheFish
99
Argh, lo siento, echo "foo" 1 >> bar.txt 2> & 1
SlappyTheFish
11
Creo que la interpretación de que 2> & 1 redirige stderr a stdout es incorrecta; Creo que es más exacto decir que envía stderr al mismo lugar al que está yendo stdout en este momento. Por lo tanto, colocar 2> & 1 después de la primera redirección es esencial.
jdg
201
# 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 redirigir dentro / fuera de un comando que llama desde su script, las respuestas ya están dadas. El mío se trata de redirigir dentro del script actual que afecta a todos los comandos / incorporados (incluye bifurcaciones) después del fragmento de código mencionado.


Otra solución interesante es redirigir a std-err / out Y al registrador o al archivo de registro a la vez, lo que implica dividir "una secuencia" en dos. Esta funcionalidad es proporcionada por el comando 'tee' que puede escribir / agregar a varios descriptores de archivos (archivos, tomas, 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 una terminal conectada a / dev / stdout (FD # 1) y / dev / stderr (FD # 2). En la práctica, podría ser una tubería, un enchufe o lo que sea.

  • Cree los FD # 3 y # 4 y apunte a la misma "ubicación" que # 1 y # 2 respectivamente. Cambiar FD # 1 no afecta a FD # 3 de ahora en adelante. Ahora, los FD # 3 y # 4 apuntan a STDOUT y STDERR respectivamente. Estos se utilizarán como terminal real STDOUT y STDERR.
  • 1>> (...) redirige STDOUT al comando en parens
  • parens (sub-shell) ejecuta la lectura 'tee' del STDOUT (tubería) del ejecutivo y lo redirige al comando 'logger' a través de otra tubería a la sub-shell en parens. Al mismo tiempo, copia la misma entrada a FD # 3 (terminal)
  • La segunda parte, muy similar, se trata de 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, esta:

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
quizac
fuente
1
Solo una excepción. en el primer ejemplo que escribió: exec 1 <> $ LOG_FILE. causa que el archivo de registro original siempre esté escrito de antemano. para el inicio de sesión real, la mejor manera es: exec 1 >> $ LOG_FILE porque siempre se agrega el registro.
Znik
44
Eso es cierto, aunque depende de las intenciones. Mi enfoque es crear siempre un archivo de registro único y con marca de tiempo. El otro es anexar. Ambas formas son 'logrotateable'. Prefiero archivos separados que requieren menos análisis, pero como dije, lo que sea que haga que tu bote flote :)
Quizac
1
Su segunda solución es informativa, pero ¿qué pasa con todo el código de limpieza? No parece relevante, y si es así, solo confunde un buen ejemplo. También me gustaría ver que se modifique ligeramente para que los FD 1 y 2 no se redirijan al registrador, sino que 3 y 4 lo hagan para que cualquier cosa que llame a este script pueda manipular 1 y 2 más bajo el supuesto común de que stdout == 1 y stderr == 2, pero mi breve experimentación sugiere que es más complejo.
JFlo
1
Me gusta más con el código de limpieza. Podría ser un poco de distracción del ejemplo central, pero despojarlo haría que el ejemplo sea incompleto. La red ya está llena de ejemplos sin manejo de errores, o al menos una nota amistosa de que todavía necesita alrededor de cien líneas de código para que sea segura de usar.
Zoltan K.
1
Quería dar más detalles sobre el código de limpieza. Es una parte del script que daemoniza que ergo se vuelve inmune a la señal HANG-UP. 'tee' y 'logger' son procesos generados por el mismo PPID y heredan la trampa HUP del script bash principal. Entonces, una vez que el proceso principal muere, son heredados por init [1]. No se convertirán en zombies (defunc). El código de limpieza asegura que se eliminen todas las tareas en segundo plano, si el script principal muere. También se aplica a cualquier otro proceso que podría haberse creado y ejecutarse en segundo plano.
Quizac
41
bash your_script.sh 1>file.log 2>&1

1>file.logindica al shell que envíe STDOUT al archivo file.logy 2>&1le dice que redirija STDERR (descriptor de archivo 2) a STDOUT (descriptor de archivo 1).

Nota: El orden importa, como señaló liw.fi, 2>&1 1>file.logno funciona.

Guðmundur H
fuente
21

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

fuente
10
&>>parece funcionar en BASH 4:$ echo $BASH_VERSION 4.1.5(1)-release $ (echo to stdout; echo to stderr > /dev/stderr) &>> /dev/null
user272735
15

Respuesta corta: Command >filename 2>&1oCommand &>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 omitimos '&', este comando se imprimirá stdouten stdout y 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, cuál de los dos descriptores 1,2, se redirige a /dev/nulllas siguientes dos líneas de código, elimine todo de stdout y todo de 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 qué el siguiente código no produce salida:

(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 continuar. Tenga en cuenta que Bash procesa de izquierda a derecha; por lo tanto, Bash ve >/dev/nullprimero (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. 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 !!!! (vea este recurso en punterospara 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 cual no se procesa ningún resultado.


Para probar si realmente comprende el concepto, intente 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> & 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. 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 aquello que no se envió a stdout en la subshell (el código entre paréntesis), es decir, "stderror". Lo interesante a tener en cuenta es que a pesar de que 1 es solo un puntero al stdout, la redirección del puntero 2 a 1 a través de 2>&1NO forma una cadena de punteros 2 -> 1 -> stdout. Si lo hizo, como resultado de redirigir 1 a / dev / null, el código2>&1 >/dev/null daría a la cadena de puntero 2 -> 1 -> / dev / null, y así el código no generaría nada, en contraste con lo que vimos anteriormente.


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

Conclusiones clave:

  • Los descriptores de archivo se comportan como punteros (aunque los descriptores de archivo no son lo mismo que los punteros de archivo)
  • Redirigir 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/nulles! = >/dev/null 2>&1. ¡Uno genera salida y el otro no!

Finalmente, eche un vistazo a estos excelentes recursos:

Documentación de Bash sobre redireccionamiento , una explicación de las tablas de descriptores de archivos , introducción a punteros

Evan Rosica
fuente
Los descriptores de archivo (0, 1, 2) son solo desplazamientos en una tabla. Cuando se usa 2> & 1, el efecto es la ranura FD [2] = dup (1), por lo que donde FD [1] apuntaba FD [2] ahora apunta. Cuando cambia FD [1] para apuntar a / dev / null, entonces FD [1] cambia pero no cambia la ranura FD [2] (que apunta a stdout). Uso el término dup () porque esa es la llamada al sistema que se usa para duplicar el descriptor de archivo.
PatS
11
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 & stderr en syslog.

Casi funciona, pero no de xinted; (


fuente
Supongo que no funciona debido a "/ dev / fd / 3 Permiso denegado". Cambiar a> & 3 puede ayudar.
Quizac
6

Quería una solución para tener el resultado de stdout plus stderr escrito en un archivo de registro y stderr todavía en la consola. Así que necesitaba duplicar la salida 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
  • Primero intercambie stderr y stdout
  • luego agregue el stdout al archivo de registro
  • pipe stderr a tee y anexarlo también al archivo de registro
bebbo
fuente
Por cierto, esto no funcionó para mí (el archivo de registro está vacío). | tee no tiene ningún efecto. En cambio, lo hice funcionar usando stackoverflow.com/questions/692000/…
Yaroslav Bulatov el
4

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 (de STDERR de "sort -c" o de STDERR a "sort -h").

dmitry_podyachev
fuente
1

Camino "más fácil" (bash4 solamente): ls * 2>&- 1>&-.

reim
fuente
1

Las siguientes funciones se pueden utilizar 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"
Fernando Fabreti
fuente
cuando uso sus funciones e intenta restaurar las salidas estándar, obtengo eco: error de escritura: número de archivo incorrecto, la redirección funciona perfectamente ... la restauración no parece hacerlo
thom schumacher
para que tu script funcione, tuve que comentar estas líneas y cambié el orden: #exec 1> & - #closes FD 1 (logfile) #exec 2> & - #closes FD 2 (logfile); exec 1> & 3 #restore stdout exec 2> & 4 #restore stderr
thom schumacher
Lamento escuchar eso. No recibo ningún error al ejecutar CentOS 7, bash 4.2.46. He anotado la referencia donde obtuve esos comandos. Es: Ref: logan.tw/posts/2016/02/20/open-and-close-files-in-bash
Fernando Fabreti
Estoy ejecutando estos comandos en AIX, probablemente por eso. Agregué una publicación para la solución que hice.
Thom Schumacher
1

@ fernando-fabreti

Además de 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"
Thom Schumacher
fuente
1

En situaciones en las que considera usar cosas como, exec 2>&1me parece más fácil de leer si es posible reescribir el código usando funciones bash como esta:

function myfunc(){
  [...]
}

myfunc &>mylog.log
usuario2761220
fuente
0

Para tcsh, tengo que usar el siguiente comando:

command >& file

Si se usa command &> file, dará el error "Comando nulo inválido".

einstein6
fuente