Comment rediriger stdout vers un fichier et stdout + stderr vers un autre?

32

Comment puis-je atteindre

cmd >> file1 2>&1 1>>file2

C'est-à-dire que stdout et stderr doivent être redirigés vers un fichier (fichier1) et que seul stdout (fichier2) doit être redirigé vers un autre (les deux en mode ajout)?

Swarna Gowri
la source

Réponses:

41

Le problème est que lorsque vous redirigez votre sortie, elle n’est plus disponible pour la prochaine redirection. Vous pouvez diriger teedans un sous-shell pour conserver la sortie pour la deuxième redirection:

( cmd | tee -a file2 ) >> file1 2>&1

ou si vous aimez voir la sortie dans le terminal:

( cmd | tee -a file2 ) 2>&1 | tee -a file1

Pour éviter d'ajouter le stderr du premier teeà file1, vous devez rediriger le stderr de votre commande vers un descripteur de fichier (par exemple 3), puis l'ajouter à nouveau à stdout:

( 2>&3 cmd | tee -a file2 ) >> file1 3>&1
# or
( 2>&3 cmd | tee -a file2 ) 3>&1 | tee -a file1

(merci @ fra-san)

pLumo
la source
16

Avec zsh:

cmd >& out+err.log > out.log

En mode annexe:

cmd >>& out+err.log >> out.log

Dans zshet si l' mult_iosoption n'a pas été désactivée, lorsqu'un descripteur de fichier (ici 1) est redirigé à plusieurs reprises pour écriture, le shell implémente une fonction intégrée teepermettant de dupliquer la sortie vers toutes les cibles.

Stéphane Chazelas
la source
Je ne peux pas comprendre quoi out+erret outdire ici. Noms de fichiers? Des flux à rediriger?
gronostaj
@gronostaj Pensez que la commande se lit comme suitcmd >& file1 > file2
Isaac
Cette solution préservera l'ordre dans lequel la sortie a été générée. Pour stocker réellement stdout et stderr (dans cet ordre), vous devez adopter une approche différente.
Isaac
1
@ Isaac, l'ordre ne sera pas nécessairement conservé, car la sortie stdout sera acheminée par un canal (à un processus qui le transmet à chaque fichier), tandis que la sortie stderr ira directement au fichier. Dans tous les cas, il ne semble pas que l'OP ait demandé à la sortie stderr de suivre celle-ci.
Stéphane Chazelas
3

Vous pouvez: tag stdout (en utilisant un sed UNBUFFERED, c.-à-d. sed -u ...), Stderr aussi aller sur stdout (non étiqueté, car il n’a pas suivi ce balisage sed), et ainsi pouvoir différencier le 2 dans le fichier journal résultant.

Ce qui suit: est lent (il peut être sérieusement optimisé, en utilisant par exemple un script Perl au lieu de tout le temps ...; do ...; done, par exemple, ce qui engendrera des sous-shell et des commandes à chaque ligne!), Bizarre (il me semble que j'ai besoin des 2 {} étapes dans un renommer stdout puis dans l'autre ajouter le "raté à travers" stderr), etc. Mais c'est: une " preuve de concept " qui essaiera de garder la sortie commande le plus de stdout & stderr autant que possible:

#basic principle (some un-necessary "{}" to visually help see the layers):
# { { complex command ;} | sed -e "s/^/TAGstdout/" ;} 2>&1 | read_stdin_and_redispatch

#exemple:
# complex command = a (slowed) ls of several things (some existing, others not)
#  to see if the order of stdout&stderr is kept

#preparation, not needed for the "proof of concept", but needed for our specific exemple setup:
\rm out.file out_AND_err.file unknown unknown2 
touch existing existing2 existing3

#and the (slow, too many execs, etc) "proof of concept":
uniquetag="_stdout_" # change this to something unique, that will NOT appear in all the commands outputs... 
                     # avoid regexp characters ("+" "?" "*" etc) to make it easy to remove with another sed later on.

{
   { for f in existing unknown existing2 unknown2 existing3 ; do ls -l "$f" ; sleep 1; done ;
   } | sed -u -e "s/^/${uniquetag}/" ;
} 2>&1 | while IFS="" read -r line ; do
    case "$line" in
       ${uniquetag}*) printf "%s\n" "$line" | tee -a out_AND_err.file | sed -e "s/^${uniquetag}//" >> out.file ;; 
        *)            printf "%s\n" "$line"       >> out_AND_err.file ;;   
    esac; 
done;

# see the results:
grep "^" out.file out_AND_err.file
Olivier Dulac
la source
C'est vraiment difficile à comprendre. (1) Pourquoi utilisez-vous un cas d’utilisation aussi compliqué ( ls unknown) pour imprimer quelque chose sur stderr? >&2 echo "error"serait bien. (2) teepeut ajouter plusieurs fichiers à la fois. (3) Pourquoi pas juste catau lieu de grep "^"? (4) votre script échouera au début de la sortie de stderr _stdout_. (5) Pourquoi?
pLumo
@RoVo: les 2 premières lignes commentées montrent l'algorithme, plus simple que l'exemple de preuve de concept. 1): ceci ls loopproduira à la fois sur stdout et stderr, mélangé (alternativement), dans un ordre contrôlé, afin que nous puissions vérifier que nous avons conservé cette commande stderr / stdout malgré le marquage de stdout 2): gnu tail, peut-être, mais pas queue régulière (ex, sur aix.). 3): grep "^" affiche également les deux noms de fichiers. 4): cela peut être changé par la variable. 5): l'exemple compliqué fonctionne sur d'anciens oses (ex, old aix) où je l'ai testé (pas de perl disponible).
Olivier Dulac
(1) Un écho multiple sur stderr et stout serait acceptable, mais bon, pas important, il serait simplement plus facile à lire. (3) d'accord, (4) bien sûr, mais il échouera s'il commence par le contenu de la variable. (5) je vois.
pLumo
@RoVo Je suis d'accord avec votre 1). pour 4), la variable pourrait être aussi complexe que nécessaire pour faire disparaître le pb (ex: uniquetag="banaNa11F453355B28E1158D4E516A2D3EDF96B3450406...)
Olivier Dulac
1
Bien sûr, ce n'est pas très probable, mais de toute façon, cela pourrait poser un problème de sécurité plus tard. Et ensuite, vous voudrez peut-être supprimer cette chaîne avant d’imprimer dans un fichier ;-)
pLumo
2

Si l'ordre de sortie doit être: stdout then stderr ; il n'y a pas de solution avec la redirection uniquement.
Le stderr doit être stocké dans un fichier temporel

cmd 2>>file-err | tee -a file1 >>file2
cat file-err >> file1
rm file-err

La description:

La seule façon de rediriger une sortie (un fd comme stdout ou stderr) vers deux fichiers est de la reproduire. La commande teeest l'outil approprié pour reproduire le contenu d'un descripteur de fichier. Donc, une idée initiale pour avoir une sortie sur deux fichiers serait d'utiliser:

... |  tee file1 file2

Cela reproduit le stdin de tee dans les deux fichiers (1 & 2), en laissant la sortie de tee toujours non utilisée. Mais nous devons ajouter (utiliser -a) et n’avoir besoin que d’une copie. Cela résout les deux problèmes:

... | tee -a file1 >>file2

Pour fournir teestdout (celui à répéter), nous devons utiliser stderr directement en dehors de la commande. Une façon, si l’ordre n’est pas important (l’ordre de la sortie sera (très probablement) conservé tel qu’il sera généré, celui qui sera généré en premier sera stocké en premier). Non plus:

  1. cmd 2>>file1 | tee -a file2 >>file1
  2. cmd 2>>file1 > >( tee -a file2 >>file1 )
  3. ( cmd | tee -a file2 ) >> file1 2>&1

L'option 2 ne fonctionne que dans certains coquillages. L'option 3 utilise un sous-shell supplémentaire (plus lent) mais n'utilise les noms de fichiers qu'une seule fois.

Mais si stdout doit être premier (quelle que soit la sortie de l'ordre générée), nous devons stocker stderr pour l'ajouter au fichier à la fin (première solution postée).

Isaac
la source
1
Ou stocker en mémoire comme le spongefait:(cmd | tee -a out >> out+err) 2>&1 | sponge >> out+err
Stéphane Chazelas
1

Dans l'intérêt de la diversité:

Si votre système prend en charge /dev/stderr, alors

(cmd | tee -a /dev/stderr) 2>> file1 >> file2

marchera. La sortie standard de cmd est envoyée à la fois à la sortie standard et à la sortie standard du pipeline. L'erreur type des cmddérivations tee et sort du stderr du pipeline.

Alors

  • la sortie standard du pipeline n'est que la sortie standard de cmd, et
  • le stderr du pipeline est le stdout et le stderr du cmd, mélangés.

Il suffit ensuite d'envoyer ces flux aux fichiers appropriés.

Comme avec presque toutes les approches comme celle-ci (y compris la réponse de Stéphane ), les file1lignes peuvent être en panne.

Scott
la source