EDIT: Je vois que j'ai déraillé et j'ai fini par répondre à une question différente de celle posée. La réponse à la vraie question se trouve au bas de la réponse de Paul Tomblin. (Si vous souhaitez améliorer cette solution pour rediriger stdout et stderr séparément pour une raison quelconque, vous pouvez utiliser la technique que je décris ici.)
Je voulais une réponse qui préserve la distinction entre stdout et stderr. Malheureusement, toutes les réponses données jusqu'à présent qui préservent cette distinction sont sujettes à la race: elles risquent que les programmes voient des contributions incomplètes, comme je l'ai souligné dans mes commentaires.
Je pense que j'ai finalement trouvé une réponse qui préserve la distinction, n'est pas sujette à la race et n'est pas non plus terriblement délicate.
Premier bloc de construction: pour permuter stdout et stderr:
my_command 3>&1 1>&2 2>&3-
Deuxième bloc de construction: si nous voulions filtrer (par exemple tee) uniquement stderr, nous pourrions le faire en échangeant stdout et stderr, filtrer, puis inverser:
{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-
Maintenant, le reste est simple: nous pouvons ajouter un filtre stdout, soit au début:
{ { my_command | stdout_filter;} 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-
ou à la fin:
{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3- | stdout_filter
Pour me convaincre que les deux commandes ci-dessus fonctionnent, j'ai utilisé ce qui suit:
alias my_command='{ echo "to stdout"; echo "to stderr" >&2;}'
alias stdout_filter='{ sleep 1; sed -u "s/^/teed stdout: /" | tee stdout.txt;}'
alias stderr_filter='{ sleep 2; sed -u "s/^/teed stderr: /" | tee stderr.txt;}'
La sortie est:
...(1 second pause)...
teed stdout: to stdout
...(another 1 second pause)...
teed stderr: to stderr
et mon invite revient immédiatement après le " teed stderr: to stderr
", comme prévu.
Note de bas de page sur zsh :
La solution ci-dessus fonctionne dans bash (et peut-être dans d'autres shells, je ne suis pas sûr), mais cela ne fonctionne pas dans zsh. Il y a deux raisons pour lesquelles il échoue dans zsh:
- la syntaxe
2>&3-
n'est pas comprise par zsh; qui doit être réécrit comme2>&3 3>&-
- dans zsh (contrairement à d'autres shells), si vous redirigez un descripteur de fichier qui est déjà ouvert, dans certains cas (je ne comprends pas complètement comment il décide), il effectue à la place un comportement de type tee intégré. Pour éviter cela, vous devez fermer chaque fd avant de le rediriger.
Ainsi, par exemple, ma deuxième solution doit être réécrite pour zsh comme {my_command 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stderr_filter;} 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stdout_filter
(qui fonctionne aussi dans bash, mais est terriblement verbeuse).
D'un autre côté, vous pouvez profiter du mystérieux départ implicite intégré de zsh pour obtenir une solution beaucoup plus courte pour zsh, qui ne fonctionne pas du tout:
my_command >&1 >stdout.txt 2>&2 2>stderr.txt
(Je n'aurais pas deviné d'après les documents que j'ai trouvé que le >&1
et 2>&2
sont la chose qui déclenche le départ implicite de zsh; je l'ai découvert par essais et erreurs.)