¿Cómo escribo stderr en un archivo mientras uso "tee" con una tubería?

544

Sé cómo usar teepara escribir la salida ( STDOUT) de aaa.shto bbb.out, mientras lo sigo mostrando en el terminal:

./aaa.sh | tee bbb.out

¿Cómo escribiría ahora también STDERRen un archivo llamado ccc.out, sin dejar de mostrarlo?

jparanich
fuente
2
Para aclarar, ¿quieres que stderr vaya tanto a la pantalla como al archivo?
Charles Duffy
Lo hice, editaré mi publicación para aclarar eso. Creo que la solución de lhunath será suficiente. ¡Gracias por toda la ayuda!
jparanich

Respuestas:

786

Supongo que todavía quieres ver STDERR y STDOUT en la terminal. Podrías buscar la respuesta de Josh Kelley, pero me parece que mantener un tailfondo en el fondo que da como resultado tu archivo de registro es muy halagador y torpe. Observe cómo necesita mantener un exra FD y hacer la limpieza después de matarlo y técnicamente debería hacerlo en a trap '...' EXIT.

Hay una mejor manera de hacer esto, y que ya ha descubierto que: tee.

Solo que, en lugar de usarlo solo para su stdout, tenga una camiseta para stdout y una para stderr. ¿Cómo vas a lograr esto? Sustitución de procesos y redirección de archivos:

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

Vamos a dividirlo y explicarlo:

> >(..)

>(...)(sustitución de proceso) crea un FIFO y lo teeescuchamos. Luego, utiliza >(redirección de archivos) para redirigir el STDOUT commandal FIFO en el que teeestá escuchando el primero .

Lo mismo para el segundo:

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

Usamos la sustitución de procesos nuevamente para hacer un teeproceso que lea desde STDIN y lo descargue stderr.log. teedevuelve su entrada a STDOUT, pero como su entrada es nuestro STDERR, queremos redirigir teeel STDOUT a nuestro STDERR nuevamente. Luego usamos la redirección de archivos para redirigir commandel STDERR de la entrada de FIFO (tee STDIN de ).

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

La sustitución de procesos es una de esas cosas realmente encantadoras que obtienes como un bono de elegir bashcomo tu shell en lugar de sh(POSIX o Bourne).


En sh, tendrías que hacer las cosas manualmente:

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
fuente
55
Intenté esto: $ echo "HANG" > >(tee stdout.log) 2> >(tee stderr.log >&2)que funciona, pero espera la entrada. ¿Hay una razón simple por la que esto sucede?
Justin
1
@SillyFreak No entiendo lo que quieres hacer o cuál es el problema que tienes. prueba de eco; exit no produce ningún resultado en stdout, por lo que err permanecerá vacío.
lhunath
1
gracias por ese comentario; Luego descubrí cuál era mi error lógico: cuando se invoca como un shell interactivo, bash imprime un símbolo del sistema y hace eco de la salida a stderr. Sin embargo, si se redirige stderr, bash comienza como no interactivo de manera predeterminada; comparar /bin/bash 2> erry/bin/bash -i 2> err
Silly Freak
15
Y para aquellos que "ver para creer", una prueba rápida:(echo "Test Out";>&2 echo "Test Err") > >(tee stdout.log) 2> >(tee stderr.log >&2)
Matthew Wilcoxson
2
Wow . Buena respuesta: "Vamos a dividirlo y explicarlo" +1
jalanb
674

por qué no simplemente:

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

Esto simplemente redirige stderrastdout , por lo que tee hace eco tanto en el registro como en la pantalla. Tal vez me estoy perdiendo algo, porque algunas de las otras soluciones parecen realmente complicadas.

Nota: desde la versión 4 de bash puede usar |&como abreviatura para 2>&1 |:

./aaa.sh |& tee -a log
usuario542833
fuente
85
Eso funciona bien si desea que stdout (canal 1) y stderr (canal 2) se registren en el mismo archivo (un solo archivo que contiene la mezcla de stdout y sterr). La otra solución más complicada le permite separar stdout y stderr en 2 archivos diferentes (stdout.log y stderr.log, respectivamente). A veces eso es importante, a veces no lo es.
Tyler Rick
19
Las otras soluciones son mucho más complicadas de lo necesario en muchos casos. Este funciona perfectamente para mí.
dkamins
13
El problema con este método es que pierde el código de salida / estado del proceso aaa.sh, que puede ser importante (por ejemplo, cuando se usa en un archivo MAKE). No tiene este problema con la respuesta aceptada.
Stefaan
99
si no te importa fusionar stdout / stderr, entonces ./aaa.sh |& tee aaa.logfunciona (en bash)
jfs
55
@ Stefaan Creo que puede conservar el estado de salida si antepone la cadena de comandos set -o pipefailseguido de ;o &&si no me equivoco.
David
59

Esto puede ser útil para las personas que lo encuentran a través de google. Simplemente descomente el ejemplo que desea probar. Por supuesto, no dude en cambiar el nombre de los archivos de salida.

#!/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}
Antonio
fuente
66
No, y supongo que el ejecutivo puede usar algunas explicaciones. exec >significa, mover el objetivo de un descriptor de archivo a un determinado destino. El valor predeterminado es 1, por lo tanto, exec > /dev/nullmueve la salida de stdout a / dev / null de ahora en adelante en esta sesión. Los descriptores de archivo actuales para esta sesión se pueden ver haciendo ls -l /dev/fd/. ¡Intentalo! Luego, vea qué sucede cuando emite. exec 2>/tmp/stderr.log.Además, exec 3>&1significa crear un nuevo descriptor de archivo con el número 3 y redirigirlo al objetivo del descriptor de archivo 1. En el ejemplo, el objetivo era la pantalla cuando se emitió el comando.
drumfire
¡El ejemplo para mostrar stdout y stderr tanto en pantalla como en archivos separados es increíble! ¡Muchas gracias!
Marcello de Sales
21

Para redirigir stderr a un archivo, muestre stdout a la pantalla y también guarde stdout en un archivo:

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

EDITAR : para mostrar stderr y stdout en la pantalla y también guardar ambos en un archivo, puede usar la redirección de 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
fuente
1
Espero que el usuario quiera que stderr vaya a su consola además del archivo, aunque no se especificó explícitamente.
Charles Duffy el
2
Debería haber sido más claro, también quería stderr en la pantalla. Todavía disfruté la solución de Josh Kelley, pero encuentro la de lhunath para satisfacer más mis necesidades. ¡Gracias chicos!
jparanich
18

En otras palabras, desea canalizar stdout en un filtro ( tee bbb.out) y stderr en otro filtro ( tee ccc.out). No hay una forma estándar de canalizar otra cosa que stdout en otro comando, pero puede solucionarlo haciendo malabares con los descriptores de archivo.

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

Ver también ¿Cómo grep flujo de error estándar (stderr)? y Cuándo usarías un descriptor de archivo adicional?

En bash (y ksh y zsh), pero no en otros shells POSIX como el guión, puede usar la sustitución del proceso :

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

Tenga en cuenta que en bash, este comando vuelve tan pronto como ./aaa.shtermina, incluso si los teecomandos aún se ejecutan (ksh y zsh esperan los subprocesos). Esto puede ser un problema si haces algo como eso ./aaa.sh > >(tee bbb.out) 2> >(tee ccc.out); process_logs bbb.out ccc.out. En ese caso, utilice el malabarismo del descriptor de archivo o ksh / zsh en su lugar.

Gilles 'SO- deja de ser malvado'
fuente
44
Esta parece ser la única respuesta que permite mantener las secuencias stdout / stderr tal cual (por ejemplo, no fusionarlas). ¡Dulce!
user1338062
El enfoque más simple para sh, útil para trabajos cron, donde la sustitución de procesos no está disponible.
Roger Dueck
13

Si usa 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

El crédito (que no responde desde la parte superior de mi cabeza) va aquí: http://www.cygwin.com/ml/cygwin/2003-06/msg00772.html

ChristopheD
fuente
5

En mi caso, un script ejecutaba un comando mientras redireccionaba stdout y stderr a un archivo, algo así como:

cmd > log 2>&1

Necesitaba actualizarlo de tal manera que cuando haya una falla, tome algunas acciones basadas en los mensajes de error. Por supuesto, podría eliminar el dup 2>&1y capturar el stderr del script, pero luego los mensajes de error no entrarán en el archivo de registro como referencia. Mientras que la respuesta aceptada de @lhunath se supone que debe hacer lo mismo, redirecciona stdouty stderra diferentes archivos, que no es lo que quiero, pero me ayudó para llegar a la solución exacta que necesita:

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

Con lo anterior, el registro tendrá una copia de ambos stdouty stderrpuedo capturarlo stderrdesde mi script sin tener que preocuparme stdout.

haridsv
fuente
4

Lo siguiente funcionará para KornShell (ksh) donde la sustitución del proceso no está 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>&-

El verdadero truco aquí, es la secuencia de lo 2>&1 1>&3que en nuestro caso redirige el descriptor stderra stdouty redirige stdoutal 3. En este punto, stderry stdoutaún no se han combinado.

En efecto, el stderr(as stdin) se pasa a teedonde se registra stderr.logy también redirige al descriptor 3.

Y el descriptor lo 3está registrando combined.logtodo el tiempo. Entonces el combined.logcontiene ambos stdouty stderr.

shuva
fuente
¡Hábil! Lástima que esté subestimado porque es una buena opción. Tengo KSH, por cierto :)
runlevel0
2

Si está usando zsh , puede usar múltiples redirecciones, por lo que ni siquiera necesita tee:

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

Aquí simplemente está redirigiendo cada transmisión a sí mismo y al archivo de destino.


Ejemplo completo

% (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

Tenga en cuenta que esto requiere MULTIOSque se establezca la opción (que es la predeterminada).

MULTIOS

Realice so tees implícitas catcuando se intentan redireccionamientos múltiples (consulte Redirección ).

Arminio
fuente