Réorienter le stderr de toutes les commandes suivantes en utilisant exec

43

J'ai un fichier bash dont j'ai besoin pour rediriger toutes les sorties vers un seul fichier, le journal de débogage ainsi que vers le terminal. Je dois rediriger stdout et stderr vers le débogage et le consigner pour toutes les commandes du script.

Je ne veux pas ajouter 2>&1 | tee -a $DEBUGpour chaque commande dans le fichier. Je pourrais vivre avec | tee -a $DEBUG.

Je me souviens qu'il y avait un moyen de le faire avec quelque chose comme exec 2>&1.

Actuellement, j'utilise quelque chose comme ce qui suit:

#!/bin/bash
DEBUGLOG=/tmp/debug
exec 2>&1
somecommand | tee -a $DEBUGLOG
somecommand2 | tee -a $DEBUGLOG
somecommand3 | tee -a $DEBUGLOG

mais ça ne marche pas. Est-ce que quelqu'un a une solution / peut expliquer la cause?

Avi
la source
1
Dans certains shells, |&fonctionne comme un raccourci 2>&1 |, c’est au moins un peu plus pratique.
Kevin

Réponses:

39

En ce qui concerne une solution pour rediriger beaucoup de commandes à la fois:

#!/bin/bash
{
    somecommand 
    somecommand2
    somecommand3
} 2>&1 | tee -a $DEBUGLOG

Pourquoi votre solution d'origine ne fonctionne-t-elle pas: exec 2> & 1 redirige la sortie d'erreur standard vers la sortie standard de votre shell, qui, si vous exécutez votre script à partir de la console, sera votre console. la redirection de canal sur les commandes ne fera que rediriger la sortie standart de la commande.

Du point de vue de somecommand, sa sortie standard va dans un tuyau connecté à teeet l'erreur standard va dans le même fichier / pseudofile que l'erreur standard du shell, que vous redirigez vers la sortie standard du shell, qui sera le console si vous exécutez votre programme à partir de la console.

La seule vraie façon de l'expliquer est de voir ce qui se passe réellement:

L'environnement d'origine de votre shell peut ressembler à ceci si vous l'exécutez à partir du terminal:

stdin -> /dev/pts/42
stdout -> /dev/pts/42
stderr -> /dev/pts/42

Après avoir redirigé l'erreur standard vers la sortie standard ( exec 2>&1), vous ne changez rien en gros. Mais si vous redirigez la sortie standard du script vers un fichier, vous obtiendrez un environnement comme celui-ci:

stdin -> /dev/pts/42
stdout -> /your/file
stderr -> /dev/pts/42

Ensuite, rediriger l'erreur standard du shell dans la sortie standard se terminerait ainsi:

stdin -> /dev/pts/42
stdout -> /your/file
stderr -> /your/file

L'exécution d'une commande héritera de cet environnement. Si vous exécutez une commande et que vous la dirigez vers tee, l'environnement de la commande serait le suivant:

stdin -> /dev/pts/42
stdout -> pipe:[4242]
stderr -> /your/file

Donc l'erreur standard de votre commande va toujours dans ce que le shell utilise comme erreur standard.

Vous pouvez réellement voir l’environnement d’une commande en regardant dans /proc/[pid]/fd: utiliser ls -lpour lister également le contenu du lien symbolique. Le 0fichier ici est une entrée standard, 1une sortie standard et 2une erreur standard. Si la commande ouvre plus de fichiers (et la plupart des programmes le font), vous les verrez également. Un programme peut également choisir de rediriger ou de fermer ses entrées / sorties standard et de les réutiliser 0, 1et 2.

BatchyX
la source
41

Vous pouvez utiliser exec comme ceci en haut de votre script:

exec > >(tee "$HOME/somefile.log") 2>&1

Par exemple:

#!/bin/bash -

exec > >(tee "$HOME/somefile.log") 2>&1

echo "$HOME"
echo hi
date
date +%F
echo bye 1>&2

Me donne la sortie au fichier $HOME/somefile.loget au terminal comme ceci:

/home/saml
hi
Sun Jan 20 13:54:17 EST 2013
2013-01-20
bye
slm
la source
2
Notez que cela utilise des bashismes - cela peut ne pas fonctionner dans d'autres coques (par exemple, dash). Mais depuis la question spécifiée bash, +1.
Richard Hansen
8
@RichardHansen, la substitution de processus est une fonctionnalité introduite par ksh, et non pas bash. Elle est également prise en charge par zsh, je ne l'appellerais donc pas un bashisme .
Stéphane Chazelas
6
@ StephaneChazelas: Vous faites valoir un bon argument. Je voulais juste souligner que la syntaxe n'est pas prise en charge par le standard POSIX et ne fonctionnera donc pas de manière universelle dans les /bin/shscripts (de nombreuses personnes utilisent à tort la syntaxe bash dans les /bin/shscripts).
Richard Hansen
Pour moi cela donne /dev/fd/62: Operation not supporteddes indices?
Eun
1
Existe-t-il un moyen de ne pas rediriger stderr sauf vers le fichier journal? Si le script original est myscriptet que je cours ./myscript > /dev/null, je devrais quand même voir d' byeoù vient echo bye >&2.
Martin Jambon
0

Écrivez stderr et stdout dans un fichier, affichez stderr à l'écran (sur stdout)

exec 2> >(tee -a -i "$HOME/somefile.log")
exec >> "$HOME/somefile.log"

Utile pour crons, afin que vous puissiez recevoir des erreurs (et seulement des erreurs) par courrier

Lluís
la source