Comment écrire stderr dans un fichier en utilisant «tee» avec un tuyau?

544

Je sais comment utiliser teepour écrire la sortie ( STDOUT) de aaa.shto bbb.out, tout en l'affichant toujours dans le terminal:

./aaa.sh | tee bbb.out

Comment pourrais-je maintenant également écrire STDERRdans un fichier nommé ccc.out, tout en l'ayant affiché?

jparanich
la source
2
Pour clarifier - voulez-vous que stderr aille aussi bien à l'écran qu'au fichier?
Charles Duffy
Je l'ai fait, je vais modifier mon message pour clarifier cela. Je crois que la solution de lhunath suffira. Merci pour l'aide tous!
jparanich

Réponses:

786

Je suppose que vous voulez toujours voir STDERR et STDOUT sur le terminal. Vous pouvez opter pour la réponse de Josh Kelley, mais je trouve que garder un tailarrière-plan qui génère votre fichier journal est très hackish et maladroit. Remarquez comment vous devez conserver un exra FD et faire le nettoyage par la suite en le tuant et que vous devriez techniquement le faire dans a trap '...' EXIT.

Il y a une meilleure façon de faire, et que vous avez déjà découvert: tee.

Seulement, au lieu de simplement l'utiliser pour votre stdout, ayez un tee pour stdout et un pour stderr. Comment allez-vous y parvenir? Substitution de processus et redirection de fichiers:

command > >(tee -a stdout.log) 2> >(tee -a stderr.log >&2)

Divisons-le et expliquons:

> >(..)

>(...)(substitution de processus) crée un FIFO et permet de l' teeécouter. Ensuite, il utilise >(redirection de fichiers) pour rediriger le STDOUT de commandvers le FIFO que votre premier teeécoute.

Même chose pour le second:

2> >(tee -a stderr.log >&2)

Nous utilisons à nouveau la substitution de processus pour créer un teeprocessus qui lit à partir de STDIN et le sauvegarde dans stderr.log. teerenvoie son entrée sur STDOUT, mais comme son entrée est notre STDERR, nous voulons rediriger teele STDOUT de notre STDERR à nouveau. Ensuite, nous utilisons la redirection de fichiers pour rediriger commandle STDERR de l'entrée FIFO ( teele STDIN).

Voir http://mywiki.wooledge.org/BashGuide/InputAndOutput

La substitution de processus est l'une de ces très belles choses que vous obtenez en prime de choisir bashvotre shell par opposition à sh(POSIX ou Bourne).


Dans sh, vous devez faire les choses manuellement:

out="${TMPDIR:-/tmp}/out.$$" err="${TMPDIR:-/tmp}/err.$$"
mkfifo "$out" "$err"
trap 'rm "$out" "$err"' EXIT
tee -a stdout.log < "$out" &
tee -a stderr.log < "$err" >&2 &
command >"$out" 2>"$err"
lhunath
la source
5
J'ai essayé ceci: $ echo "HANG" > >(tee stdout.log) 2> >(tee stderr.log >&2)qui fonctionne, mais attend l'entrée. Y a-t-il une raison simple à cela?
Justin
1
@SillyFreak Je ne comprends pas ce que vous voulez faire ni quel est le problème que vous rencontrez. test d'écho; exit ne produit aucune sortie sur stdout, donc err restera vide.
lhunath
1
merci pour ce commentaire; J'ai ensuite découvert quelle était mon erreur logique: lorsqu'il est invoqué en tant que shell interactif, bash affiche une invite de commande et renvoie écho à stderr. Cependant, si stderr est redirigé, bash démarre comme non interactif par défaut; comparer /bin/bash 2> erret/bin/bash -i 2> err
Silly Freak
15
Et pour ceux qui "voir c'est croire", un petit test:(echo "Test Out";>&2 echo "Test Err") > >(tee stdout.log) 2> >(tee stderr.log >&2)
Matthew Wilcoxson
2
Ouah . Bonne réponse: "
Divisons-le
674

pourquoi pas simplement:

./aaa.sh 2>&1 | tee -a log

Cela redirige simplement stderràstdout , donc tee échos à la fois au journal et à l' écran. Peut-être que je manque quelque chose, car certaines des autres solutions semblent vraiment compliquées.

Remarque: depuis bash version 4, vous pouvez utiliser |&comme abréviation pour 2>&1 |:

./aaa.sh |& tee -a log
user542833
la source
85
Cela fonctionne très bien si vous voulez que stdout (canal 1) et stderr (canal 2) soient enregistrés dans le même fichier (un seul fichier contenant le mélange de stdout et sterr). L'autre solution, plus compliquée, vous permet de séparer stdout et stderr en 2 fichiers différents (stdout.log et stderr.log, respectivement). Parfois, c'est important, parfois non.
Tyler Rick
19
Les autres solutions sont beaucoup plus compliquées que nécessaire dans de nombreux cas. Celui-ci fonctionne parfaitement pour moi.
dkamins
13
Le problème avec cette méthode est que vous perdez le code exit / status du processus aaa.sh, ce qui peut être important (par exemple lors de l'utilisation dans un makefile). Vous n'avez pas ce problème avec la réponse acceptée.
Stefaan
9
si cela ne vous dérange pas fusionné stdout / stderr ./aaa.sh |& tee aaa.logfonctionne alors (en bash).
jfs
5
@Stefaan Je pense que vous pouvez conserver le statut de sortie si vous ajoutez la chaîne de commandes avec set -o pipefailsuivi de ;ou &&si je ne me trompe pas.
David
59

Cela peut être utile pour les personnes qui trouvent cela via google. Décommentez simplement l'exemple que vous souhaitez essayer. Bien sûr, n'hésitez pas à renommer les fichiers de sortie.

#!/bin/bash

STATUSFILE=x.out
LOGFILE=x.log

### All output to screen
### Do nothing, this is the default


### All Output to one file, nothing to the screen
#exec > ${LOGFILE} 2>&1


### All output to one file and all output to the screen
#exec > >(tee ${LOGFILE}) 2>&1


### All output to one file, STDOUT to the screen
#exec > >(tee -a ${LOGFILE}) 2> >(tee -a ${LOGFILE} >/dev/null)


### All output to one file, STDERR to the screen
### Note you need both of these lines for this to work
#exec 3>&1
#exec > >(tee -a ${LOGFILE} >/dev/null) 2> >(tee -a ${LOGFILE} >&3)


### STDOUT to STATUSFILE, stderr to LOGFILE, nothing to the screen
#exec > ${STATUSFILE} 2>${LOGFILE}


### STDOUT to STATUSFILE, stderr to LOGFILE and all output to the screen
#exec > >(tee ${STATUSFILE}) 2> >(tee ${LOGFILE} >&2)


### STDOUT to STATUSFILE and screen, STDERR to LOGFILE
#exec > >(tee ${STATUSFILE}) 2>${LOGFILE}


### STDOUT to STATUSFILE, STDERR to LOGFILE and screen
#exec > ${STATUSFILE} 2> >(tee ${LOGFILE} >&2)


echo "This is a test"
ls -l sdgshgswogswghthb_this_file_will_not_exist_so_we_get_output_to_stderr_aronkjegralhfaff
ls -l ${0}
Anthony
la source
6
Non, et je suppose que l'exec peut utiliser des explications. exec >signifie, déplacer la cible d'un descripteur de fichier vers une certaine destination. La valeur par défaut est 1, donc, exec > /dev/nulldéplace la sortie de stdout vers / dev / null à partir de maintenant dans cette session. Les descripteurs de fichiers actuels pour cette session peuvent être vus en faisant ls -l /dev/fd/. Essayez! Ensuite, voyez ce qui se passe lorsque vous émettez exec 2>/tmp/stderr.log.En outre, exec 3>&1signifie, créez un nouveau descripteur de fichier avec le numéro 3 et redirigez-le vers la cible du descripteur de fichier 1. Dans l'exemple, la cible était l'écran lorsque la commande a été émise.
drumfire
L'exemple pour montrer stdout et stderr à la fois à l'écran et dans des fichiers séparés est génial !!! Merci beaucoup!
Marcello de Sales
21

Pour rediriger stderr vers un fichier, affichez stdout à l'écran et enregistrez également stdout dans un fichier:

./aaa.sh 2> ccc.out | tee ./bbb.out

EDIT : Pour afficher à la fois stderr et stdout à l'écran et également enregistrer les deux dans un fichier, vous pouvez utiliser la redirection d'E / S de bash :

#!/bin/bash

# Create a new file descriptor 4, pointed at the file
# which will receive stderr.
exec 4<>ccc.out

# Also print the contents of this file to screen.
tail -f ccc.out &

# Run the command; tee stdout as normal, and send stderr
# to our file descriptor 4.
./aaa.sh 2>&4 | tee bbb.out

# Clean up: Close file descriptor 4 and kill tail -f.
exec 4>&-
kill %1
Josh Kelley
la source
1
Je m'attends à ce que l'utilisateur veuille que stderr aille sur sa console en plus du fichier, bien que cela n'ait pas été explicitement spécifié.
Charles Duffy
2
J'aurais dû être plus clair, je voulais aussi stderr à l'écran. J'ai toujours apprécié la solution de Josh Kelley, mais je trouve celle de Lhunath qui correspond davantage à mes besoins. Merci les gars!
jparanich
18

En d'autres termes, vous voulez diriger stdout dans un filtre ( tee bbb.out) et stderr dans un autre filtre ( tee ccc.out). Il n'y a aucun moyen standard de diriger autre chose que stdout dans une autre commande, mais vous pouvez contourner cela en jonglant avec les descripteurs de fichiers.

{ { ./aaa.sh | tee bbb.out; } 2>&1 1>&3 | tee ccc.out; } 3>&1 1>&2

Voir aussi Comment grep flux d'erreur standard (stderr)? et quand utiliseriez-vous un descripteur de fichier supplémentaire?

Dans bash (et ksh et zsh), mais pas dans d'autres shells POSIX tels que dash, vous pouvez utiliser la substitution de processus :

./aaa.sh > >(tee bbb.out) 2> >(tee ccc.out)

Attention, en bash, cette commande revient dès la ./aaa.shfin, même si les teecommandes sont toujours exécutées (ksh et zsh attendent les sous-processus). Cela peut être un problème si vous faites quelque chose comme ./aaa.sh > >(tee bbb.out) 2> >(tee ccc.out); process_logs bbb.out ccc.out. Dans ce cas, utilisez plutôt le jonglage de descripteur de fichier ou ksh / zsh.

Gilles 'SO- arrête d'être méchant'
la source
4
Cela semble être la seule réponse qui permet de conserver les flux stdout / stderr tels quels (par exemple, ne pas les fusionner). Doux!
user1338062
L'approche la plus simple pour sh, utile pour les tâches cron, où la substitution de processus n'est pas disponible.
Roger Dueck
13

Si vous utilisez bash:

# Redirect standard out and standard error separately
% cmd >stdout-redirect 2>stderr-redirect

# Redirect standard error and out together
% cmd >stdout-redirect 2>&1

# Merge standard error with standard out and pipe
% cmd 2>&1 |cmd2

Le crédit (ne répondant pas du haut de ma tête) va ici: http://www.cygwin.com/ml/cygwin/2003-06/msg00772.html

ChristopheD
la source
5

Dans mon cas, un script exécutait une commande lors de la redirection de stdout et stderr vers un fichier, quelque chose comme:

cmd > log 2>&1

Je devais le mettre à jour de telle sorte qu'en cas d'échec, prendre certaines mesures en fonction des messages d'erreur. Je pourrais bien sûr supprimer le dup 2>&1et capturer le stderr du script, mais les messages d'erreur ne seront pas enregistrés dans le fichier journal pour référence. Alors que la réponse acceptée de @lhunath est censée faire la même chose, elle redirige stdoutet stderrvers différents fichiers, ce qui n'est pas ce que je veux, mais cela m'a aidé à trouver la solution exacte dont j'ai besoin:

(cmd 2> >(tee /dev/stderr)) > log

Avec ce qui précède, le journal aura une copie des deux stdoutet stderret je peux capturer stderrde mon script sans avoir à se soucier stdout.

haridsv
la source
4

Les éléments suivants fonctionneront pour KornShell (ksh) où la substitution de processus n'est pas disponible,

# create a combined(stdin and stdout) collector
exec 3 <> combined.log

# stream stderr instead of stdout to tee, while draining all stdout to the collector
./aaa.sh 2>&1 1>&3 | tee -a stderr.log 1>&3

# cleanup collector
exec 3>&-

La vraie astuce ici, est la séquence de 2>&1 1>&3laquelle, dans notre cas, redirige le stderrvers stdoutet redirige le stdoutdescripteur vers 3. À ce stade, le stderretstdout ne sont pas encore combinés.

En effet, le stderr(as stdin) est passé à l' teeendroit où il se connectestderr.log et redirige également vers le descripteur 3.

Et le descripteur l' 3enregistre combined.logtout le temps. Ainsi, le combined.logcontient à la fois stdoutet stderr.

shuva
la source
Nifty! Dommage qui soit sous-estimé car c'est une bonne option. J'ai KSH, btw :)
runlevel0
2

Si vous utilisez zsh , vous pouvez utiliser plusieurs redirections, vous n'avez donc même pas besoin tee:

./cmd 1>&1 2>&2 1>out_file 2>err_file

Ici, vous redirigez simplement chaque flux vers lui - même et le fichier cible.


Exemple complet

% (echo "out"; echo "err">/dev/stderr) 1>&1 2>&2 1>/tmp/out_file 2>/tmp/err_file
out
err
% cat /tmp/out_file
out
% cat /tmp/err_file
err

Notez que cela nécessite la MULTIOSdéfinition de l' option (qui est la valeur par défaut).

MULTIOS

Exécutez tees ou cats implicites lorsque plusieurs redirections sont tentées (voir Redirection ).

Arminius
la source