EDITAR: Veo que me descarrilé y terminé respondiendo una pregunta diferente a la que me hicieron. La respuesta a la pregunta real está al final de la respuesta de Paul Tomblin. (Si desea mejorar esa solución para redirigir stdout y stderr por separado por alguna razón, puede usar la técnica que describo aquí).
Quería una respuesta que conservara la distinción entre stdout y stderr. Desafortunadamente, todas las respuestas dadas hasta ahora que preservan esa distinción son propensas a la raza: corren el riesgo de que los programas vean una entrada incompleta, como señalé en los comentarios.
Creo que finalmente encontré una respuesta que conserva la distinción, que no es propensa a la raza y tampoco es terriblemente complicada.
Primer bloque de construcción: para intercambiar stdout y stderr:
my_command 3>&1 1>&2 2>&3-
Segundo bloque de construcción: si quisiéramos filtrar (por ejemplo, tee) solo stderr, podríamos lograrlo cambiando stdout y stderr, filtrando y luego volviendo a intercambiar:
{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-
Ahora el resto es fácil: podemos agregar un filtro de salida estándar, ya sea al principio:
{ { my_command | stdout_filter;} 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-
o al final:
{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3- | stdout_filter
Para convencerme de que los dos comandos anteriores funcionan, utilicé lo siguiente:
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 salida es:
...(1 second pause)...
teed stdout: to stdout
...(another 1 second pause)...
teed stderr: to stderr
y mi mensaje vuelve inmediatamente después de " teed stderr: to stderr
", como se esperaba.
Nota al pie sobre zsh :
La solución anterior funciona en bash (y tal vez en otros shells, no estoy seguro), pero no funciona en zsh. Hay dos razones por las que falla en zsh:
2>&3-
zsh no entiende la sintaxis ; que tiene que ser reescrito como2>&3 3>&-
- en zsh (a diferencia de otros shells), si redirige un descriptor de archivo que ya está abierto, en algunos casos (no entiendo completamente cómo decide), en su lugar, realiza un comportamiento integrado en forma de tee. Para evitar esto, debe cerrar cada fd antes de redirigirlo.
Entonces, por ejemplo, mi segunda solución debe reescribirse para zsh como {my_command 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stderr_filter;} 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stdout_filter
(que también funciona en bash, pero es muy detallado).
Por otro lado, puede aprovechar el misterioso tee implícito incorporado de zsh para obtener una solución mucho más corta para zsh, que no ejecuta tee en absoluto:
my_command >&1 >stdout.txt 2>&2 2>stderr.txt
(No habría adivinado por los documentos que encontré que >&1
y 2>&2
son lo que desencadena el tee implícito de zsh; lo descubrí por prueba y error).