Pregunta

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

¿Fue útil?

Solución

Eche un vistazo aquí . Debería ser:

yourcommand &>filename

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

Otros consejos

do_something 2>&1 | tee -a some_file

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

Puede redirigir stderr a stdout y stdout en un archivo:

some_command >file.log 2>&1 

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

Se prefiere este formato que el & amp; > más popular formato 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

# 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 el 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 tanto a std-err / out como al registrador o 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 " como # 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 real terminal STDOUT y STDERR.
  • 1 > > (...) redirige STDOUT al comando en parens
  • parens (sub-shell) ejecuta la lectura 'tee' de STDOUT (tubería) del ejecutivo y redirige al comando 'logger' a través de otra tubería a la sub-shell en parens. Al mismo tiempo, copia la misma entrada en 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
bash your_script.sh 1>file.log 2>&1

1 > file.log ordena al shell que envíe STDOUT al archivo file.log , y 2 > & amp; 1 le dice para redirigir STDERR (descriptor de archivo 2) a STDOUT (descriptor de archivo 1).

Nota: el orden importa, como señaló liw.fi, 2 > & amp; 1 1 > file.log no funciona.

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

Respuesta corta: Comando > nombre de archivo 2 > & amp; 1 o Comando & amp; > nombre de archivo


Explicación:

Considere el siguiente código que imprime la palabra '' stdout '' stdout y la palabra "stderror" a stderror.

$ (echo "stdout"; echo "stderror" >&2)
stdout
stderror

Tenga en cuenta que '& amp;' 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 '& amp;', este comando imprimirá stdout en stdout y creará un archivo llamado " 2 " y escriba 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 / null , las siguientes dos líneas 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 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 esto página web en 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 / null primero (que es lo mismo que 1 > / dev / null ) y establece el descriptor de archivo 1 para que apunte a / dev / nulo en lugar de stdout. Una vez hecho esto, Bash se mueve hacia la derecha y ve 2 > & amp; 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 punteros para más información)). Dado que 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 muestra 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 > & amp; 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 lo 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 a la salida estándar, la redirección del puntero 2 a 1 a través de 2 > & amp; 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 > & amp; 1 > / dev / null le 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 arriba.


Finalmente, notarí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 & amp; > 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 & amp; > / 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 los mismos que los punteros de archivo)
  • Redirigiendo un fi
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 & amp; stderr a syslog.

Casi funciona, pero no de xinted; (

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

Para la situación, cuando '' tuberías '' es necesario puedes 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 de STDERR a '' sort -h '').

" Más fácil " way (solo bash4): ls * 2 > & amp; - 1 > & amp; - .

Las siguientes funciones se pueden utilizar para automatizar el proceso de alternar salidas entre stdout / stderr y un archivo de registro. Ejemplo de uso dentro del script:

#!/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"
    }

Para tcsh, tengo que usar el siguiente comando:

command >& file

Si usa el comando & amp; > archivo , dará " Comando nulo inválido " error.

@ fernando-fabreti

Agregando a lo que hiciste, cambié ligeramente las funciones y eliminé el cierre & amp; - 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"

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

function myfunc(){
  [...]
}

myfunc &>mylog.log
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top