web-dev-qa-db-fra.com

Rediriger stderr et stdout dans Bash

Je veux rediriger à la fois stdout et stderr d'un processus vers un fichier unique. Comment je fais ça dans Bash?

625
flybywire

Jetez un oeil ici . Devrait être:

yourcommand &>filename

(redirige à la fois stdout et stderr vers le nom du fichier).

714
dirkgently
do_something 2>&1 | tee -a some_file

Cela va rediriger stderr vers stdout et stdout vers some_fileet l’imprimer sur stdout.

429
Marko

Vous pouvez rediriger stderr vers stdout et le stdout dans un fichier:

some_command >file.log 2>&1 

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

Ce format est préféré au format &> le plus populaire qui ne fonctionne que dans bash. Dans Bourne Shell, cela peut être interprété comme exécutant la commande en arrière-plan. Aussi le format est plus lisible 2 (est STDERR) redirigé vers 1 (STDOUT).

EDIT: modification de l'ordre comme indiqué dans les commentaires

231
f3lix
# 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'"

Maintenant, un simple écho va écrire dans $ LOG_FILE. Utile pour démoniser.

À l'auteur du message original,

Cela dépend de ce que vous devez accomplir. Si vous avez juste besoin de rediriger une commande que vous appelez à partir de votre script, les réponses sont déjà données. Mine concerne la redirection au sein du script en cours qui affecte toutes les commandes/fonctions intégrées (y compris les fourches) après le fragment de code mentionné.


Une autre solution intéressante consiste à rediriger simultanément std-err/out ET vers un enregistreur ou un fichier journal, ce qui implique de scinder "un flux" en deux. Cette fonctionnalité est fournie par la commande 'tee' qui peut écrire/ajouter plusieurs descripteurs de fichier (fichiers, sockets, pipes, etc.) à la fois: 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
}

Donc, depuis le début. Supposons que nous ayons un terminal connecté à/dev/stdout (FD # 1) et à/dev/stderr (FD # 2). En pratique, cela pourrait être un tuyau, une prise ou autre chose.

  • Créez les FD n ° 3 et n ° 4 et pointez sur le même "emplacement" que les n ° 1 et n ° 2, respectivement. Changer le FD n ° 1 n'affecte pas le FD n ° 3 à partir de maintenant. Maintenant, les FD n ° 3 et n ° 4 désignent respectivement STDOUT et STDERR. Ceux-ci seront utilisés comme réels terminaux STDOUT et STDERR.
  • 1>> (...) redirige STDOUT vers une commande entre parenthèses
  • parens (sous-shell) exécute la lecture de "tee" à partir de STDOUT (pipe) de l'exec et redirige vers la commande 'logger' via un autre tuyau vers le sous-shell dans les parens. Simultanément, il copie la même entrée sur le FD n ° 3 (terminal)
  • la deuxième partie, très similaire, concerne le même truc pour STDERR et FD n ° 2 et n ° 4.

Le résultat de l'exécution d'un script ayant la ligne ci-dessus et en plus celle-ci:

echo "Will end up in STDOUT(terminal) and /var/log/messages"

...est comme suit:

$ ./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 vous voulez voir une image plus claire, ajoutez ces 2 lignes au script:

ls -l /proc/self/fd/
ps xf
185
quizac
bash your_script.sh 1>file.log 2>&1

1>file.log demande au shell d'envoyer STDOUT au fichier file.log, et 2>&1 lui dit de rediriger STDERR (descripteur de fichier 2) vers STDOUT (descripteur de fichier 1).

Remarque: L'ordre est important, comme l'a souligné liw.fi, 2>&1 1>file.log ne fonctionne pas.

37
Guðmundur H

Curieusement, cela fonctionne:

yourcommand &> filename

Mais cela donne une erreur de syntaxe:

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

Vous devez utiliser:

yourcommand 1>> filename 2>&1
21
Mark

Réponse courte: Command >filename 2>&1 ou Command &>filename


Explication:

Considérez le code suivant qui affiche le mot "stdout" sur stdout et le mot "stderror" sur stderror.

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

Notez que l'opérateur '&' indique à bash que 2 est un descripteur de fichier (qui pointe vers le stderr) et non un nom de fichier. Si nous omettions le '&', cette commande afficherait stdout sur la sortie standard, créerait un fichier nommé "2" et y écrirait stderror.

En testant le code ci-dessus, vous pouvez voir par vous-même exactement comment fonctionnent les opérateurs de redirection. Par exemple, en changeant quel fichier lequel des deux descripteurs 1,2 est redirigé vers /dev/null, les deux lignes de code suivantes suppriment tout ce qui se trouve dans la sortie standard et tout ce qui provient de stderror (imprimer ce qui reste).

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

Maintenant, nous pouvons expliquer pourquoi la solution pourquoi le code suivant ne produit aucune sortie:

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

Pour vraiment comprendre cela, je vous recommande fortement de lire ceci page Web sur les tableaux de descripteurs de fichiers . En supposant que vous ayez fait cette lecture, nous pouvons procéder. Notez que Bash traite de gauche à droite; Bash voit donc d'abord >/dev/null (qui est identique à 1>/dev/null) et définit le descripteur de fichier 1 pour qu'il pointe vers/dev/null au lieu de la sortie standard. Ceci fait, Bash se déplace alors vers la droite et voit 2>&1. Ceci définit le descripteur de fichier 2 pour qu'il pointe vers le même fichier que le descripteur de fichier 1 (et non sur le descripteur de fichier 1 lui-même !!!! (voir - cette ressource sur les pointeurs pour plus d’informations)). Comme le descripteur de fichier 1 pointe sur/dev/null et que le descripteur de fichier 2 pointe sur le même fichier que le descripteur de fichier 1, le descripteur de fichier 2 pointe également sur/dev/null. Ainsi, les deux descripteurs de fichier pointent vers/dev/null, raison pour laquelle aucune sortie n'est rendue.


Pour tester si vous comprenez vraiment le concept, essayez de deviner le résultat lorsque nous inversons l'ordre de redirection:

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

stderror

Le raisonnement est le suivant: en évaluant de gauche à droite, Bash voit 2> & 1, et définit ainsi le descripteur de fichier 2 pour qu'il pointe au même endroit que le descripteur de fichier 1, c.-à-d. Stdout. Il définit ensuite le descripteur de fichier 1 (rappelez-vous que>/dev/null = 1>/dev/null) pour qu'il pointe vers>/dev/null, supprimant ainsi tout ce qui serait normalement envoyé à la sortie standard. Ainsi, il ne nous reste que ce qui n’a pas été envoyé à stdout dans le sous-shell (le code entre parenthèses) - c’est-à-dire "stderror". La chose intéressante à noter est que même si 1 est juste un pointeur sur la sortie standard, la redirection du pointeur 2 vers 1 via 2>&1 ne forme PAS une chaîne de pointeurs 2 -> 1 -> stdout. Si c'est le cas, à la suite de la redirection de 1 vers/dev/null, le code 2>&1 >/dev/null donnerait à la chaîne de pointeur 2 -> 1 ->/dev/null, et le code ne générerait rien, contrairement à ce que nous avons vu ci-dessus.


Enfin, je noterais qu’il existe un moyen plus simple de procéder:

À partir de la section 3.6.4 ici , nous voyons que nous pouvons utiliser l'opérateur &> pour rediriger stdout et stderr. Ainsi, pour rediriger les sorties stderr et stdout de toute commande vers \dev\null (ce qui supprime la sortie), il suffit de taper $ command &> /dev/null ou dans le cas de mon exemple:

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

Points clés à retenir:

  • Les descripteurs de fichier se comportent comme des pointeurs (bien que les descripteurs de fichier ne soient pas identiques aux pointeurs de fichier)
  • En redirigeant un descripteur de fichier "a" vers un descripteur de fichier "b" pointant vers le fichier "f", le descripteur de fichier "a" pointe au même endroit que le descripteur de fichier b - fichier "f". Il ne forme pas une chaîne de pointeurs a -> b -> f
  • En raison de ce qui précède, l'ordre compte, 2>&1 >/dev/null est! = >/dev/null 2>&1. L'un génère une sortie et l'autre pas!

Enfin, jetez un coup d’œil à ces excellentes ressources:

Documentation Bash sur la redirection , Explication des tables de descripteur de fichier , Introduction aux pointeurs

13
Evan Rosica
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" )

C'est lié: Ecrire stdOut & stderr dans syslog.

Cela fonctionne presque, mais pas de xinted; (

11
log-control

Je voulais une solution pour que la sortie de stdout plus stderr soit écrite dans un fichier journal et stderr toujours sur la console. Il me fallait donc dupliquer la sortie stderr via un tee.

C'est la solution que j'ai trouvée:

command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
  • Premier échange stderr et stdout
  • puis ajoutez le stdout au fichier journal
  • pipe stderr to tee et l'ajoute également au fichier journal
6
bebbo

Pour la situation, quand "la tuyauterie" est nécessaire, vous pouvez utiliser:

&

Par exemple:

echo -ne "15\n100\n"|sort -c |& tee >sort_result.txt

ou

TIMEFORMAT=%R;for i in `seq 1 20` ; do time kubectl get pods |grep node >>js.log  ; done |& sort -h

Ces solutions basées sur bash peuvent diriger séparément STDOUT et STDERR (de STDERR à "sort -c" ou de STDERR à "sort -h").

4
dmitry_podyachev

Dans des situations où vous envisagez d'utiliser des éléments tels que exec 2>&1, je trouve plus facile à lire si possible en réécrivant le code à l'aide de fonctions bash telles que celle-ci:

function myfunc(){
  [...]
}

myfunc &>mylog.log
1
user2761220

Manière "la plus facile" (uniquement pour bash4): ls * 2>&- 1>&-.

1
reim

Les fonctions suivantes peuvent être utilisées pour automatiser le processus de basculement des sorties entre stdout/stderr et un fichier journal.

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

Exemple d'utilisation à l'intérieur du 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"
1
Fernando Fabreti

Pour tcsh, je dois utiliser la commande suivante:

command >& file

Si vous utilisez command &> file, vous obtiendrez l'erreur "Invalid null command".

0
einstein6

@ fernando-fabreti

En ajoutant à ce que vous avez fait j'ai légèrement modifié les fonctions et supprimé le & - fermeture et cela a fonctionné pour moi.

    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"
0
thom schumacher