Afficher uniquement stderr à l'écran mais écrire à la fois stdout et stderr dans le fichier

24

Comment puis-je utiliser la magie BASH pour y parvenir?
Je veux seulement voir la sortie de stderr à l'écran,
mais je veux que stdout et stderr soient écrits dans un fichier.

Clarification: je veux que stdout et stderr se retrouvent dans le même fichier. Dans l'ordre où ils se produisent.
Malheureusement, aucune des réponses ci-dessous ne le fait.

Andreas
la source
2
Une nouvelle approche du même problème fondamental: comment capturer les STDOUT / STDERR commandés et ajouter des horodatages / préfixes?
Gilles 'SO- arrête d'être méchant'

Réponses:

14

Même sans redirection, ou avec rien mais >logfile 2>&1, vous n'êtes pas assuré de voir la sortie dans l'ordre de génération.

Pour commencer, la sortie standard de l'application sera mise en mémoire tampon de ligne (vers tty) ou mise en mémoire tampon (dans un pipeline) mais stderr est sans tampon, de sorte que les relations entre l'ordre de sortie sont rompues en ce qui concerne le lecteur. Les étapes suivantes de tout pipeline que vous pourriez concocter n'obtiendront pas un accès ordonné de manière déterministe aux deux flux (ce sont conceptuellement des choses qui se déroulent en parallèle, et vous êtes toujours soumis à l'ordonnanceur - si au moment où votre lecteur obtient une tranche que l'auteur a déjà écrit sur les deux tuyaux, vous ne pouvez pas dire lequel est arrivé en premier).

"[L] e ordre qu'ils se produisent" n'est vraiment connu de l'application. L'ordre de sortie sur stdout / stderr est un problème bien connu - classique, peut-être -.

jmb
la source
7

Lorsque vous utilisez la construction: les 1>stdout.log 2>&1deux stderr et stdout redirigés vers le fichier parce que le stdout redirection est mis en place avant la stderr redirection.

Si vous inversez l'ordre, vous pouvez rediriger stdout vers un fichier, puis copier stderr vers stdout pour pouvoir le diriger vers tee.

$ cat test
#!/bin/sh
echo OUT! >&1
echo ERR! >&2

$ ./test 2>&1 1>stdout.log | tee stderr.log
ERR!

$ cat stdout.log
OUT!

$ cat stderr.log
ERR!
mmoya
la source
Cela ne vous permet pas de consigner les deux flux dans le même fichier.
artistoex
1
@artistoex: Vous pouvez simplement utiliser tee -aavec le même fichier utilisé 1>pour additionner les deux sorties dans le même fichier. Quelque chose comme:./test 2>&1 1>out+err.log | tee -a out+err.log
mmoya
Je ne sais pas pourquoi, mais cela ne fonctionne pas avec wget -O - www.google.demoi. (comparer avec la solution ci-dessous)
artistoex
4
L'utilisation tee -ane fonctionnera pas de manière fiable pour avoir la même sortie que celle qui serait sur la console si rien n'était redirigé. La sortie qui passe teepeut être légèrement retardée par rapport à la sortie qui vient directement de la commande et peut donc apparaître plus tard dans le journal.
Gilles 'SO- arrête d'être méchant'
Cela ne fonctionne pas pour moi, out + err.log est vide. Et oui, je veux stderror et standard dans le même fichier
Andreas
7

J'ai pu accomplir cela en plaçant la ligne suivante en haut d'un script bash:

exec 1>>log 2> >(tee -a log >&2)

Cela redirigera stdout vers le fichier log( 1>>log), puis tee stderr vers le fichier log( 2> >(tee -a log) et le redirigera vers stderr ( >&2). De cette façon, j'obtiens un seul fichier log, qui montre à la fois stdout et stderr dans l'ordre, et stderr est également affiché à l'écran comme d'habitude.

Le hic, c'est que cela ne semble fonctionner que lorsque j'ajoute au fichier. Si je n'ajoute pas, il semble que les deux redirections s'entrechoquent, et je n'obtiens que la dernière sortie.

bjg222
la source
Est-il possible d'obtenir ceci pour "modifier" les lignes STDERR pour inclure une chaîne personnalisée afin que vous puissiez facilement la grep?
IMTheNachoMan
1

Vous souhaitez dupliquer le flux d'erreurs afin qu'il apparaisse à la fois sur la console et dans le fichier journal. L'outil pour cela est tee, et tout ce que vous devez faire est de l'appliquer au flux d'erreurs. Malheureusement, il n'y a pas de construction shell standard pour diriger le flux d'erreurs d'une commande dans une autre commande, donc un petit réarrangement des descripteurs de fichiers est nécessaire.

{ { echo out; echo err 1>&2; } 2>&1 >&3 | tee /dev/tty; } >log 3>&1
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^   ^^^^^^^^^^^^    ^^^^^^^^^
  command produces output      stdout3                    log
  command produces error       stderr1   dup to terminal  log
Gilles 'SO- arrête d'être méchant'
la source
Cela aussi, ne fonctionne pas tout à fait pour moi, j'obtiens d'abord les lignes stdout, puis toutes les lignes stderr dans le fichier «log». exemple: {{echo out; écho err 1> & 2; écho out2; écho err2 1> & 2; } 2> & 1> & 3 | tee / dev / tty; }> log 3> & 1
Andreas
1
@Andreas: Oh, duh, teeintroduit un délai ici aussi. Je ne pense pas qu'il existe une solution pure shell. En fait, je me demande s'il existe une solution sans modifier l'application d'une manière ou d'une autre.
Gilles 'SO- arrête d'être méchant'
1

Pour écrire stderr sur écran et écrire les deux stderr et stdout dans un fichier - ET faire en sorte que les lignes pour stderr et stdout sortent dans le même ordre que si elles étaient écrites sur l'écran:

Se révèle être un problème difficile, en particulier la partie concernant la "même séquence" à laquelle vous vous attendez si vous les écriviez simplement à l'écran. En termes simples: écrivez chacun dans son propre fichier, faites un peu de magie en arrière-plan pour marquer chaque ligne (dans chaque fichier) avec l'heure exacte à laquelle la ligne a été produite, puis: "tail --follow" le fichier stderr à l'écran , mais pour voir les deux "stderr" et "stdout" ensemble - dans l'ordre - trier les deux fichiers (avec des marquages ​​d'heure exacte sur chaque ligne) ensemble.

Code:

# Set the location of output and the "first name" of the log file(s)
pth=$HOME
ffn=my_log_filename_with_no_extension
date >>$pth/$ffn.out
date >>$pth/$ffn.err
# Start background processes to handle 2 files, by rewriting each one line-by-line as each line is added, putting a label at front of line
tail -f $pth/$ffn.out | perl -nle 'use Time::HiRes qw(time);print substr(time."0000",0,16)."|1|".$_' >>$pth/$ffn.out.txt &
tail -f $pth/$ffn.err | perl -nle 'use Time::HiRes qw(time);print substr(time."0000",0,16)."|2|".$_' >>$pth/$ffn.err.txt &
sleep 1
# Remember the process id of each of 2 background processes
export idout=`ps -ef | grep "tail -f $pth/$ffn.out" | grep -v 'grep' | perl -pe 's/\s+/\t/g' | cut -f2`
export iderr=`ps -ef | grep "tail -f $pth/$ffn.err" | grep -v 'grep' | perl -pe 's/\s+/\t/g' | cut -f2`
# Run the command, sending stdout to one file, and stderr to a 2nd file
bash mycommand.sh 1>>$pth/$ffn.out 2>>$pth/$ffn.err
# Remember the exit code of the command
myexit=$?
# Kill the two background processes
ps -ef | perl -lne 'print if m/^\S+\s+$ENV{"idout"}/'
echo kill $idout
kill $idout
ps -ef | perl -lne 'print if m/^\S+\s+$ENV{"iderr"}/'
echo kill $iderr
kill $iderr
date
echo "Exit code: $myexit for '$listname', list item# '$ix', bookcode '$bookcode'"

Oui, cela semble élaboré et il en résulte 4 fichiers de sortie (dont 2 que vous pouvez supprimer). Il semble que ce soit un problème difficile à résoudre, il a donc fallu plusieurs mécanismes.

En fin de compte, pour voir les résultats des deux stdout et stderr dans la séquence que vous attendez, exécutez ceci:

cat $pth/$ffn.out.txt $pth/$ffn.err.txt | sort

La seule raison pour laquelle la séquence est au moins très proche de ce qu'elle aurait été si stdout et stderr étaient simplement passés à l'écran est: chaque ligne est marquée avec un horodatage jusqu'à la milliseconde.

Pour voir stderr à l'écran au cours du processus, utilisez ceci:

tail -f $pth/$ffn.out

J'espère que cela aide quelqu'un qui est arrivé ici longtemps après que la question initiale a été posée.

dana forsberg
la source
+1 pour l'effort, j'ai eu une idée similaire (horodatage des deux flux via perl) mais j'ai eu du mal à l'implémenter. J'examinerai si cela fonctionne pour moi (c'est-à-dire s'il garde vraiment l'ordre des sorties, car d'autres solutions ici changent un peu les choses en raison de l'asynchronicité entre le marquage de stderr et stdout)
Olivier Dulac
0

Soit fla commande que vous aimeriez exécuter, alors ceci

( exec 3>/tmp/log; f 2>&1 1>&3 |tee >(cat)>&3 )

devrait vous donner ce que vous souhaitez. Par exemple, wget -O - www.google.decela ressemblerait à ceci:

( exec 3>/tmp/googlelog; wget -O - www.google.de 2>&1 1>&3 |tee >(cat)>&3 )
artistoex
la source
1
Cela fonctionne presque pour moi, mais dans le fichier journal, j'obtiens d'abord toutes les lignes stdout, puis toutes les lignes stderror. Je veux qu'ils soient entrelacés dans l'ordre naturel au fur et à mesure qu'ils se produisent.
Andreas
0

Compte tenu de ce que j'ai lu ici, la seule solution que je puisse voir serait d'ajouter à chaque ligne STOUT ou STERR un timelice / timestamp. date +% s arriverait en secondes, mais cela ralentirait pour maintenir l'ordre. De plus, le journal devrait en quelque sorte être trié. Plus de travail que je n'en suis capable.

vieillard fatigué
la source
0

J'ai eu du mal avec cette exigence exacte, et finalement je n'ai pas trouvé de solution simple. J'ai fini par faire ceci:

TMPFILE=/tmp/$$.log
myCommand > $TMPFILE 2>&1
[ $? != 0 ] && cat $TMPFILE
date >> myCommand.log
cat $TMPFILE >> myCommand.log
rm -f $TMPFILE

Il ajoute stdout et stderr, dans l'ordre entrelacé approprié, au fichier journal. Et si des erreurs se sont produites (comme déterminé par l'état de sortie de la commande), il envoie la sortie entière (stdout et stderr) à stdout, toujours dans un ordre entrelacé approprié. J'ai trouvé que c'était un compromis raisonnable pour mes besoins. Si vous voulez un fichier journal à exécution unique plutôt qu'un fichier multi-exécution croissant, c'est encore plus simple:

myCommand > myCommand.log 2>&1
[ $? != 0 ] && cat myCommand.log
Keith Pomakis
la source
0

stderr à la commande tee -a, et stdout s'ajoutant au même fichier:

./script.sh 2> >(tee -a outputfile) >>outputfile

et notez: aussi assurez-vous que l'ordre est correct (mais sans stderr-show), il y a une commande 'unbuffer' des outils 'expect', qui simule tty et maintient stdout / err dans l'ordre comme il le montrerait sur le terminal:

unbuffer ./script.sh > outputfile
Asain Kujovic
la source