Rediriger stderr et stdout dans Bash

679

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

fil volant
la source
2
Je voudrais dire que c'est une question étonnamment utile. Beaucoup de gens ne savent pas comment faire, car ils n'ont pas à le faire fréquemment, et ce n'est pas le comportement le mieux documenté de Bash.
Robert Wm Ruedisueli
2
Parfois, il est utile de voir la sortie (comme d'habitude) ET de la rediriger vers un fichier. Voir la réponse de Marko ci-dessous. (Je dis cela ici parce qu'il est facile de regarder la première réponse acceptée si cela suffit pour résoudre un problème, mais d'autres réponses fournissent souvent des informations utiles.)
jvriesem

Réponses:

763

Jetez un oeil ici . Devrait être:

yourcommand &>filename

(redirige les deux stdoutet stderrvers le nom de fichier).

dirkgently
la source
29
Cette syntaxe est déconseillée selon le wiki Bash Hackers . C'est ça?
Salman von Abbas
20
Selon wiki.bash-hackers.org/scripting/obsolete , il semble être obsolète dans le sens où il ne fait pas partie de POSIX, mais la page de manuel bash ne fait aucune mention de sa suppression de bash dans un avenir proche. La page de manuel spécifie une préférence pour «&>» sur «> &», qui est par ailleurs équivalent.
chepner
13
Je suppose que nous ne devrions pas utiliser &> car il n'est pas dans POSIX, et les shells courants tels que "dash" ne le supportent pas.
Sam Watkins
27
Un conseil supplémentaire: si vous l'utilisez dans un script, assurez-vous qu'il commence par #!/bin/bashplutôt que #!/bin/sh, car dans requiert bash.
Tor Klingberg
8
Ou & >> pour ajouter au lieu de remplacer.
Alexander Gonchiy
449
do_something 2>&1 | tee -a some_file

Cela va rediriger stderr vers stdout et stdout vers some_file et l' imprimer vers stdout.

Marko
la source
16
Sous AIX (ksh), votre solution fonctionne. La réponse acceptée do_something &>filenamene le fait pas. +1.
Retenu le
11
@Daniel, mais cette question concerne spécifiquement bash
John La Rooy
3
J'ai une Ambiguous output redirect.idée pourquoi?
Alexandre Holden Daly
1
J'ai un script ruby ​​(que je ne veux en aucun cas modifier) ​​qui imprime les messages d'erreur en rouge gras. Ce script ruby ​​est ensuite invoqué depuis mon script bash (que je peux modifier). Lorsque j'utilise ce qui précède, il imprime les messages d'erreur en texte brut, moins la mise en forme. Existe-t-il un moyen de conserver le formatage à l'écran et d'obtenir la sortie (à la fois stdout et stderr) dans un fichier?
atlantis
8
Notez que (par défaut), cela a l'effet secondaire qui $?ne fait plus référence à l'état de sortie de do_something, mais à l'état de sortie de tee.
Flimm
255

Vous pouvez rediriger stderr vers stdout et la 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 qu'en bash. Dans Bourne shell, cela pourrait être interprété comme exécutant la commande en arrière-plan. Le format est également plus lisible 2 (est STDERR) redirigé vers 1 (STDOUT).

EDIT: a changé l'ordre comme indiqué dans les commentaires

f3lix
la source
47
Cela redirige stderr vers la sortie standard, pas vers le fichier où va la sortie standard. Mettez '2> & 1' après '> file.log' et ça marche.
1
Quel est l'avantage de cette approche sur some_command &> file.log?
ubermonkey
6
Si vous souhaitez ajouter à un fichier, vous devez le faire de cette façon: echo "foo" 2> & 1 1 >> bar.txt AFAIK il n'y a aucun moyen d'ajouter à l'aide de &>
SlappyTheFish
9
Argh, désolé, faites écho à "foo" 1 >> bar.txt 2> & 1
SlappyTheFish
11
Je pense que l'interprétation selon laquelle 2> & 1 redirige stderr vers stdout est erronée; Je crois qu'il est plus exact de dire qu'il envoie stderr au même endroit que stdout en ce moment. Ainsi, placer 2> & 1 après la première redirection est essentiel.
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'"

Maintenant, l'écho simple écrira dans $ LOG_FILE. Utile pour démoniser.

À l'auteur de l'article d'origine,

Cela dépend de ce que vous devez réaliser. Si vous avez juste besoin de rediriger vers / depuis une commande que vous appelez depuis votre script, les réponses sont déjà données. Le mien concerne la redirection dans le script actuel qui affecte toutes les commandes / commandes intégrées (y compris les fourches) après l'extrait de code mentionné.


Une autre solution intéressante consiste à rediriger à la fois std-err / out ET vers un enregistreur ou un fichier journal à la fois, ce qui implique de diviser "un flux" en deux. Cette fonctionnalité est fournie par la commande 'tee' qui peut écrire / ajouter à plusieurs descripteurs de fichiers (fichiers, sockets, tuyaux, 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, il peut s'agir d'un tuyau, d'une douille ou autre.

  • Créez les FD # 3 et # 4 et pointez respectivement vers le même "emplacement" que # 1 et # 2. Changer FD # 1 n'affecte plus FD # 3. Maintenant, les FD # 3 et # 4 pointent respectivement vers STDOUT et STDERR. Ceux-ci seront utilisés comme de vrais terminaux STDOUT et STDERR.
  • 1>> (...) redirige STDOUT vers la commande parens
  • parens (sous-shell) exécute la lecture «tee» du STDOUT (pipe) de l'exécutif et redirige vers la commande «logger» via un autre tube vers le sous-shell en parens. En même temps, il copie la même entrée dans FD # 3 (terminal)
  • la deuxième partie, très similaire, consiste à faire la même astuce pour STDERR et FDs # 2 et # 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
quizac
la source
1
une seule exception. dans le premier exemple que vous avez écrit: exec 1 <> $ LOG_FILE. cela cause le fichier journal d'origine est toujours écrasé. pour une vraie connexion, le meilleur moyen est: exec 1 >> $ LOG_FILE car le journal est toujours ajouté.
Znik
4
C'est vrai même si cela dépend des intentions. Mon approche consiste à toujours créer un fichier journal unique et horodaté. L'autre est d'ajouter. Les deux façons sont «logotables». Je préfère les fichiers séparés qui nécessitent moins d'analyse mais comme je l'ai dit, tout ce qui fait flotter votre bateau :)
quizac
1
Votre deuxième solution est informative, mais qu'est-ce qui se passe avec tout le code de nettoyage? Cela ne semble pas pertinent, et si c'est le cas, ne fait qu'embrouiller un autre bon exemple. J'aimerais également le voir retravaillé légèrement pour que les FD 1 et 2 ne soient pas redirigés vers l'enregistreur, mais plutôt 3 et 4 pour que tout ce qui appelle ce script puisse manipuler 1 et 2 davantage sous l'hypothèse courante que la sortie standard == 1 et stderr == 2, mais ma brève expérimentation suggère que c'est plus complexe.
JFlo
1
Je l'aime mieux avec le code de nettoyage. Cela pourrait être un peu distrayant de l'exemple de base, mais le supprimer rendrait l'exemple incomplet. Le net est déjà plein d'exemples sans gestion d'erreur, ou du moins une note amicale qu'il a encore besoin d'une centaine de lignes de code pour être utilisé en toute sécurité.
Zoltan K.
1
Je voulais développer le code de nettoyage. C'est une partie du script qui démonifie ergo devient immunisé contre le signal HANG-UP. 'tee' et 'logger' sont des processus engendrés par le même PPID et héritent du trap HUP du script bash principal. Ainsi, une fois que le processus principal meurt, ils deviennent hérités par init [1]. Ils ne deviendront pas des zombies (defunc). Le code de nettoyage garantit que toutes les tâches d'arrière-plan sont supprimées, si le script principal meurt. Elle s'applique également à tout autre processus qui aurait pu être créé et exécuté en arrière-plan.
quizac
41
bash your_script.sh 1>file.log 2>&1

1>file.logdemande au shell d'envoyer STDOUT au fichier file.loget lui 2>&1dit de rediriger STDERR (descripteur de fichier 2) vers STDOUT (descripteur de fichier 1).

Remarque: la commande est importante, comme l'a souligné liw.fi, 2>&1 1>file.logne fonctionne pas.

Guðmundur H
la source
21

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

la source
10
&>>semble fonctionner sur BASH 4:$ echo $BASH_VERSION 4.1.5(1)-release $ (echo to stdout; echo to stderr > /dev/stderr) &>> /dev/null
user272735
15

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


Explication:

Considérez le code suivant qui imprime 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 omettons le «&», cette commande s'imprime stdoutsur stdout, crée un fichier nommé «2» et stderrory écrit .

En expérimentant avec le code ci-dessus, vous pouvez voir par vous-même exactement comment fonctionnent les opérateurs de redirection. Par exemple, en modifiant le fichier, lequel des deux descripteurs 1,2est redirigé vers /dev/nullles deux lignes de code suivantes, supprimez respectivement tout de la stdout et tout de stderror (en imprimant 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 cette page Web sur les tableaux de descripteurs de fichiers . En supposant que vous ayez fait cette lecture, nous pouvons continuer. Notez que Bash traite de gauche à droite; Bash voit donc en >/dev/nullpremier (ce qui est le même que 1>/dev/null) et définit le descripteur de fichier 1 pour pointer vers / dev / null au lieu de la sortie standard. Cela fait, Bash se déplace ensuite vers la droite et voit 2>&1. Cela définit le descripteur de fichier 2 pour pointer vers le même fichier que le descripteur de fichier 1 (et non pour le descripteur de fichier 1 lui-même !!!! (voir cette ressource sur les pointeurspour plus d'informations)) . Étant donné que le descripteur de fichier 1 pointe vers / dev / null et que le descripteur de fichier 2 pointe vers le même fichier que le descripteur de fichier 1, le descripteur de fichier 2 pointe désormais également vers / dev / null. Ainsi, les deux descripteurs de fichiers pointent vers / dev / null, et c'est pourquoi aucune sortie n'est rendue.


Pour tester si vous comprenez vraiment le concept, essayez de deviner la sortie lorsque nous changeons l'ordre de redirection:

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

stderror

Le raisonnement ici est que l'évaluation de gauche à droite, Bash voit 2> & 1, et définit ainsi le descripteur de fichier 2 pour pointer au même endroit que le descripteur de fichier 1, c'est-à-dire stdout. Il définit ensuite le descripteur de fichier 1 (rappelez-vous que> / dev / null = 1> / dev / null) pour pointer vers> / dev / null, supprimant ainsi tout ce qui serait normalement envoyé vers la sortie standard. Ainsi, tout ce qui nous reste est 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 n'est qu'un pointeur vers la sortie standard, la redirection du pointeur 2 vers 1 via 2>&1NE forme PAS une chaîne de pointeurs 2 -> 1 -> stdout. Si c'est le cas, suite à la redirection de 1 vers / dev / null, le code2>&1 >/dev/null donnerait la chaîne de pointeurs 2 -> 1 -> / dev / null, et donc le code ne générerait rien, contrairement à ce que nous avons vu ci-dessus.


Enfin, je note qu'il existe un moyen plus simple de procéder:

De la section 3.6.4 ici , nous voyons que nous pouvons utiliser l'opérateur &>pour rediriger stdout et stderr. Ainsi, pour rediriger à la fois la sortie stderr et stdout de n'importe quelle commande \dev\null(qui supprime la sortie), nous tapons simplement $ command &> /dev/null ou dans le cas de mon exemple:

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

Points clés à retenir:

  • Les descripteurs de fichiers se comportent comme des pointeurs (bien que les descripteurs de fichiers ne soient pas identiques aux pointeurs de fichiers)
  • La redirection d'un descripteur de fichier "a" vers un descripteur de fichier "b" qui pointe vers le fichier "f", fait pointer le descripteur de fichier "a" 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 est important, 2>&1 >/dev/nullc'est! = >/dev/null 2>&1. L'un génère une sortie et l'autre pas!

Enfin, jetez un œil à ces excellentes ressources:

Documentation Bash sur la redirection , explication des tableaux de descripteurs de fichiers , introduction aux pointeurs

Evan Rosica
la source
Les descripteurs de fichiers (0, 1, 2) ne sont que des décalages dans une table. Lorsque 2> & 1 est utilisé, l'effet est l'emplacement FD [2] = dup (1), donc là où FD [1] pointait FD [2] pointe maintenant. Lorsque vous changez FD [1] pour pointer vers / dev / null, FD [1] est modifié mais cela ne change pas le slot FD [2] (qui pointe vers stdout). J'utilise le terme dup () parce que c'est l'appel système qui est utilisé pour dupliquer le descripteur de fichier.
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" )

Il est lié: Ecrire stdOut & stderr dans syslog.

Cela fonctionne presque, mais pas de xinted; (


la source
Je suppose que cela ne fonctionne pas à cause de "/ dev / fd / 3 Autorisation refusée". Le passage à> & 3 peut aider.
quizac
6

Je voulais une solution pour que la sortie de stdout plus stderr soit écrite dans un fichier journal et stderr toujours sur la console. J'ai donc dû dupliquer la sortie stderr via tee.

Voici 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 la sortie standard au fichier journal
  • pipe stderr pour té et l'ajouter également au fichier journal
bebbo
la source
BTW, cela n'a pas fonctionné pour moi (le fichier journal est vide). | tee n'a aucun effet. Au lieu de cela, je l'ai fait fonctionner en utilisant stackoverflow.com/questions/692000/…
Yaroslav Bulatov
4

Pour la situation, lorsque 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 STDOUT et STDERR séparément (de STDERR de "sort -c" ou de STDERR à "sort -h").

dmitry_podyachev
la source
1

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

reim
la source
1

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"
Fernando Fabreti
la source
lorsque j'utilise vos fonctions et qu'il tente de restaurer les sorties standard, j'obtiens un écho: erreur d'écriture: mauvais numéro de fichier, la redirection fonctionne parfaitement ... la restauration ne semble pas
thom schumacher
pour que votre script fonctionne, j'ai dû commenter ces lignes et j'ai changé l'ordre: #exec 1> & - #closes FD 1 (logfile) #exec 2> & - #closes FD 2 (logfile); exec 1> & 3 #restore stdout exec 2> & 4 #restore stderr
thom schumacher
Désolé d'entendre ça. Je ne reçois aucune erreur lors de l'exécution dans CentOS 7, bash 4.2.46. J'ai annoté la référence où j'ai obtenu ces commandes. C'est: Ref: logan.tw/posts/2016/02/20/open-and-close-files-in-bash
Fernando Fabreti
J'exécute ces commandes sur AIX, c'est probablement pourquoi. J'ai ajouté un post pour le correctif que j'ai fait.
thom schumacher
1

@ fernando-fabreti

Ajoutant à ce que vous avez fait, j'ai légèrement changé les fonctions et supprimé la 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"
thom schumacher
la source
1

Dans les situations où vous envisagez d'utiliser des choses comme exec 2>&1je trouve plus facile à lire si possible la réécriture de code en utilisant des fonctions bash comme ceci:

function myfunc(){
  [...]
}

myfunc &>mylog.log
user2761220
la source
0

Pour tcsh, je dois utiliser la commande suivante:

command >& file

Si utilisé command &> file, il donnera l'erreur "Invalid null command".

einstein6
la source