2010-12-21 14 views
9

Para fines de prueba, me gustaría guardar stdout y stderr por separado para su inspección por código subsiguiente. Por ejemplo, una ejecución de prueba con una entrada errónea debería dar como resultado salida a stderr, pero nada en stdout, mientras que una ejecución de prueba con la entrada correcta debería dar como resultado salida a stdout, pero nada a stderr. El guardado debe ser sincrónico, para evitar condiciones de carrera con las pruebas (por lo que no puedo usar process substitution).Guardar stdout, stderr y stdout + stderr sincrónicamente

Para poder depurar la prueba después del hecho, I también necesito ver stdout y stderr en la secuencia de salida. Así que tengo que guardarlos en el mismo archivo/variable/lo que sea o enviarlos al terminal al mismo tiempo que los guardo por separado.

Para probar qué error ocurrió, I también necesita el código de salida del comando.

Por razones de eficiencia y precisión, por supuesto no puedo ejecutar cada prueba dos veces.

¿Es posible, por ejemplo, redirigir stdout a stdout.log, stderr a stderr.log, y ambos a output.log en el mismo comando? O para usar un comando síncronotee por separado para stdout y stderr? ¿O para guardar una copia de stdout y stderr para separar las variables?

actualización: Parece que la solución de Tim casi obras (modificado para salida en el terminal en lugar de entrar a all.log):

$ set -o pipefail 
$ { 
    { 
     echo foo | tee stdout.log 2>&3 3>&- 
    } 2>&1 >&4 4>&- | tee stderr.log 2>&3 3>&- 
} 3>&2 4>&1 
foo 
$ cat stdout.log 
foo 
$ cat stderr.log 
$ { 
    { 
     echo foo >&2 | tee stdout.log 2>&3 3>&- 
    } 2>&1 >&4 4>&- | tee stderr.log 2>&3 3>&- 
} 3>&2 4>&1 
foo 
$ cat stdout.log 
$ cat stderr.log 
foo 
$ bar=$({ 
    { 
     echo foo | tee stdout.log 2>&3 3>&- 
    } 2>&1 >&4 4>&- | tee stderr.log 2>&3 3>&- 
} 3>&2 4>&1) 
$ echo "$bar" 
foo 
$ cat stdout.log 
foo 
$ cat stderr.log 
$ bar=$({ 
    { 
     echo foo >&2 | tee stdout.log 2>&3 3>&- 
    } 2>&1 >&4 4>&- | tee stderr.log 2>&3 3>&- 
} 3>&2 4>&1) 
$ cat stdout.log 
$ cat stderr.log 
foo 
$ echo "$bar" 
foo 

Esto parece funcionar a excepción de la última iteración, donde el valor de bar se establece en los contenidos de stderr. ¿Alguna sugerencia para que todos estos funcionen?

Respuesta

11

Consulte BashFAQ/106. No es fácil tener tu torta y comértela también.

Desde esa página:

embargo, algunas personas no aceptan bien la pérdida de separación entre stdout y stderr, o la desincronización de las líneas. Son puristas, por lo que piden la forma más difícil de todas: quiero registrar stdout y stderr en un solo archivo, PERO también quiero que mantengan sus destinos originales y separados.

Con el fin de hacer esto, primero tenemos que hacer algunas notas:

  • Si no van a ser dos stdout y stderr corrientes separadas, entonces algún proceso tiene que escribir cada uno de ellos.
  • No hay forma de escribir un proceso en un script de shell que lea desde dos FD separados siempre que uno de ellos tenga entrada disponible, porque el shell no tiene encuesta (2) o seleccione (2).
  • Por lo tanto, necesitaremos dos procesos de escritura independientes.
  • La única forma de evitar que la salida de dos escritores diferentes se destruya entre sí es asegurarse de que ambos abran su salida en modo de adición. Un FD que se abre en modo de adición tiene la propiedad garantizada de que cada vez que se escriben datos en él, saltará al final primero.

Así:

# Bash 
> mylog 
exec > >(tee -a mylog) 2> >(tee -a mylog >&2) 

echo A >&2 
cat file 
echo B >&2 

Esto asegura que el archivo de registro es correcta. Esto no garantiza que los escritores terminan antes de la siguiente línea de comandos:

~$ ./foo 
A 
hi mom 
B 
~$ cat mylog 
A 
hi mom 
B 
~$ ./foo 
A 
hi mom 
~$ B 

También, ver BashFAQ/002 y BashFAQ/047.

+1

Suponiendo que estamos hablando cadenas basadas en línea, podemos tubo de ambas corrientes a través, por ejemplo, 'sed' para agregar marcadores al comienzo de las líneas? Entonces, podríamos decir desde el archivo de registro qué línea vino de qué corriente. – Raphael

+1

@Raphael: Debería poder preceder a 'tee' dentro de cada sustitución de proceso, por ejemplo:' exec>> (sed 's/^/de STDOUT: /' | tee-a mylog) 2>> (sed 's/^/desde STDERR:/'| tee -a mylog> & 2) '(no probado). Esto provocaría que la salida del terminal también tenga esos prefijos, por cierto. –

+0

¡Genial! ¿Eso no invalida parte de la cita que das? Ahora tenemos un archivo de salida compartido sincronizado y aún sabemos cuál fue cuál o me falta algo. – Raphael

2

Esto suena como una tarea para multitarea.

Para probar para la salida síncrona o escrituras en disco sin utilizar sustitución de proceso es posible que desee jugar con trucos de redirección de Bash y camiseta (1) similar al siguiente:

echos() { echo "hello on stdout"; echo "hello on stderr" 1>&2; return 0; } 

    { 
    { 
    echos 3>&- | 
     tee stdout.log 2>&3 3>&- 
    } 2>&1 >&4 4>&- | 
     tee stderr.log 2>&3 3>&- 
} 3>&2 4>&1 2>&1 | tee /dev/stderr > all.log 

open -e stdout.log stderr.log all.log # Mac OS X 

Para más información ver: http://codesnippets.joyent.com/posts/show/8769

para obtener el código de salida del comando es posible que desee echa un vistazo a pipefail o PIPESTATUS respectivamente (además de la última salida de código variable "$?"):

help set | grep -i pipefail 
man bash | less -p pipefail 
man bash | less -p PIPESTATUS 
+0

PIPESTATUS no parece funcionar: si el comando echos devuelve 1, sigue siendo solo dos ceros. – l0b0

+0

'set -o pipefail' funciona bien. – l0b0

1

Para la última iteración, donde el valor de la barra se establece en el contenido de stderr, intente:

# restore original stdout & stderr at the end by adding: 1>&2 2>&1 
bar="$(
{ 
    { 
     echo foo >&2 | tee stdout.log 2>&3 3>&- 
    } 2>&1 >&4 4>&- | tee stderr.log 2>&3 3>&- 
} 3>&2 4>&1 1>&2 2>&1 
)" 

echo "$bar" 
Cuestiones relacionadas