Mostrar solo stderr en la pantalla pero escribir tanto stdout como stderr en el archivo

24

¿Cómo puedo usar la magia BASH para lograr esto?
Solo quiero ver la salida de stderr en la pantalla,
pero quiero que tanto stdout como stderr se escriban en un archivo.

Aclaración: Quiero que stdout y stderr terminen en el mismo archivo. En el orden en que suceden.
Lamentablemente, ninguna de las respuestas a continuación hace esto.

Andreas
fuente
2
Un nuevo paso al mismo problema fundamental: ¿Cómo capturar STDOUT / STDERR ordenado y agregar marcas de tiempo / prefijos?
Gilles 'SO- deja de ser malvado'

Respuestas:

14

Incluso sin ninguna redirección, o con nada más >logfile 2>&1, no se garantiza que vea la salida en orden de generación.

Para empezar, el stdout de la aplicación tendrá un buffer de línea (a tty) o un buffer (a una tubería) pero stderr no tiene buffer, por lo que las relaciones entre el orden de salida se rompen en lo que respecta al lector. Las etapas posteriores en cualquier canalización que pueda inventar no obtendrán un acceso ordenado de manera determinista a las dos corrientes (son conceptualmente cosas que suceden en paralelo, y siempre está sujeto al planificador, si para el momento en que su lector obtiene una porción, el escritor ya tiene escrito en ambas tuberías, no se puede saber cuál vino primero).

"[L] a orden de que sucedan" solo es realmente conocido por la aplicación. El pedido de salida a través de stdout / stderr es un problema bien conocido, quizás clásico.

jmb
fuente
7

Cuando utiliza la construcción: 1>stdout.log 2>&1tanto stderr como stdout se redirigen al archivo porque la redirección stdout se configura antes de la redirección stderr .

Si invierte el orden, puede obtener stdout redirigido a un archivo y luego copiar stderr a stdout para que pueda canalizarlo tee.

$ cat test
#!/bin/sh
echo OUT! >&1
echo ERR! >&2

$ ./test 2>&1 1>stdout.log | tee stderr.log
ERR!

$ cat stdout.log
OUT!

$ cat stderr.log
ERR!
mmoya
fuente
Eso no le permite registrar las dos secuencias en el mismo archivo.
artistoex
1
@artistoex: solo puede usar tee -acon el mismo archivo usado 1>para sumar ambas salidas en el mismo archivo. Algo así como:./test 2>&1 1>out+err.log | tee -a out+err.log
mmoya
No tengo idea de por qué, pero esto no funciona wget -O - www.google.depara mí. (compárese con la solución a continuación)
artistoex
44
El uso tee -ano funcionará de manera confiable para tener el mismo resultado que estaría en la consola si no se redirige nada. La salida que se realiza teepuede retrasarse ligeramente en comparación con la salida que proviene directamente del comando, por lo que puede aparecer más adelante en el registro.
Gilles 'SO- deja de ser malvado'
Esto no funciona para mí, out + err.log está vacío. Y sí, quiero stderror y estándar en el mismo archivo
Andreas
7

He podido lograr esto colocando la siguiente línea en la parte superior de un script bash:

exec 1>>log 2> >(tee -a log >&2)

Esto redirigirá stdout al archivo log( 1>>log), luego tee stderr al archivo log( 2> >(tee -a log) y lo dirigirá de nuevo a stderr ( >&2). De esta manera, obtengo un solo archivo log, que muestra tanto stdout como stderr en orden, y stderr también es se muestra en la pantalla como de costumbre.

El problema es que solo parece funcionar cuando anexo el archivo. Si no agrego, parece que las dos redirecciones se golpean entre sí, y solo obtengo la última salida.

bjg222
fuente
¿Alguna forma de hacer que esto "modifique" las líneas STDERR para incluir una cadena personalizada para que pueda manipularla fácilmente?
IMTheNachoMan
1

Desea duplicar la secuencia de error para que aparezca tanto en la consola como en el archivo de registro. La herramienta para eso es tee, y todo lo que necesita hacer es aplicarlo a la secuencia de error. Desafortunadamente, no hay una construcción de shell estándar para canalizar el flujo de error de un comando en otro comando, por lo que se requiere una pequeña reorganización del descriptor de archivo.

{ { echo out; echo err 1>&2; } 2>&1 >&3 | tee /dev/tty; } >log 3>&1
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^   ^^^^^^^^^^^^    ^^^^^^^^^
  command produces output      stdout3                    log
  command produces error       stderr1   dup to terminal  log
Gilles 'SO- deja de ser malvado'
fuente
Esto tampoco me funciona del todo, primero obtengo líneas estándar y luego todas las líneas estándar en el archivo 'log'. ejemplo: {{echo out; echo err 1> & 2; echo out2; echo err2 1> & 2; } 2> y 1> y 3 | tee / dev / tty; }> log 3> & 1
Andreas
1
@Andreas: Oh, duh, también teeintroduce un retraso aquí. No creo que haya una solución de shell pura. De hecho, me pregunto si hay una solución sin modificar la aplicación de alguna manera.
Gilles 'SO- deja de ser malvado'
1

Para escribir stderr en la pantalla y escribir AMBOS stderr y stdout en un archivo, Y que las líneas para stderr y stdout salgan en la misma secuencia que lo harían si ambas se escribieran en la pantalla:

Resulta ser un problema difícil, especialmente la parte de tener "la misma secuencia" que esperaría si simplemente los escribiera en la pantalla. En términos simples: escriba cada uno en su propio archivo, haga un poco de magia de proceso en segundo plano para marcar cada línea (en cada archivo) con la hora exacta en que se produjo la línea, y luego: "seguir - seguir" el archivo stderr en la pantalla , pero para ver AMBOS "stderr" y "stdout" juntos, en secuencia, clasifique los dos archivos (con marcas de tiempo exacto en cada línea) juntos.

Código:

# Set the location of output and the "first name" of the log file(s)
pth=$HOME
ffn=my_log_filename_with_no_extension
date >>$pth/$ffn.out
date >>$pth/$ffn.err
# Start background processes to handle 2 files, by rewriting each one line-by-line as each line is added, putting a label at front of line
tail -f $pth/$ffn.out | perl -nle 'use Time::HiRes qw(time);print substr(time."0000",0,16)."|1|".$_' >>$pth/$ffn.out.txt &
tail -f $pth/$ffn.err | perl -nle 'use Time::HiRes qw(time);print substr(time."0000",0,16)."|2|".$_' >>$pth/$ffn.err.txt &
sleep 1
# Remember the process id of each of 2 background processes
export idout=`ps -ef | grep "tail -f $pth/$ffn.out" | grep -v 'grep' | perl -pe 's/\s+/\t/g' | cut -f2`
export iderr=`ps -ef | grep "tail -f $pth/$ffn.err" | grep -v 'grep' | perl -pe 's/\s+/\t/g' | cut -f2`
# Run the command, sending stdout to one file, and stderr to a 2nd file
bash mycommand.sh 1>>$pth/$ffn.out 2>>$pth/$ffn.err
# Remember the exit code of the command
myexit=$?
# Kill the two background processes
ps -ef | perl -lne 'print if m/^\S+\s+$ENV{"idout"}/'
echo kill $idout
kill $idout
ps -ef | perl -lne 'print if m/^\S+\s+$ENV{"iderr"}/'
echo kill $iderr
kill $iderr
date
echo "Exit code: $myexit for '$listname', list item# '$ix', bookcode '$bookcode'"

Sí, esto parece elaborado, y da como resultado 4 archivos de salida (2 de los cuales puede eliminar). Parece que este es un problema difícil de resolver, por lo que tomó varios mecanismos.

Al final, para ver los resultados de AMBOS stdout y stderr en la secuencia que esperarías, ejecuta esto:

cat $pth/$ffn.out.txt $pth/$ffn.err.txt | sort

La única razón por la que la secuencia es al menos muy cercana a lo que hubiera sido que stdout y stderr simplemente hubieran ido a la pantalla es: Cada línea está marcada con una marca de tiempo hasta el milisegundo.

Para ver stderr en la pantalla a medida que avanza el proceso, use esto:

tail -f $pth/$ffn.out

Espero que ayude a alguien, que llegó aquí mucho después de que se hizo la pregunta original.

dana forsberg
fuente
+1 por el gran esfuerzo, tuve una idea similar (marcas de tiempo de ambas transmisiones a través de perl) pero estaba luchando por implementarla. Investigaré si funciona para mí (es decir, si realmente mantiene el orden de las salidas, ya que otras soluciones aquí cambian un poco las cosas debido a la asincronía entre el etiquetado de stderr y stdout)
Olivier Dulac
0

Deje fser el comando que desea ejecutar, entonces esto

( exec 3>/tmp/log; f 2>&1 1>&3 |tee >(cat)>&3 )

debería darte lo que deseas. Por ejemplo, wget -O - www.google.dese vería así:

( exec 3>/tmp/googlelog; wget -O - www.google.de 2>&1 1>&3 |tee >(cat)>&3 )
artistoex
fuente
1
Esto casi funciona para mí, pero en el archivo de registro primero obtengo todas las líneas estándar y luego todas las líneas estándar. Quiero que se intercalen en el orden natural a medida que suceden.
Andreas
0

Dado lo que he leído aquí, la única solución que puedo ver sería anteponer cada línea, ya sea STOUT o STERR con un intervalo de tiempo / marca de tiempo. date +% s llegaría a segundos, pero eso es lento para mantener el orden. Además, el registro de alguna manera tendría que ser ordenado. Más trabajo del que soy capaz.

viejo cansado
fuente
0

He luchado con este requisito exacto, y al final no pude encontrar una solución simple. Lo que terminé haciendo en su lugar fue esto:

TMPFILE=/tmp/$$.log
myCommand > $TMPFILE 2>&1
[ $? != 0 ] && cat $TMPFILE
date >> myCommand.log
cat $TMPFILE >> myCommand.log
rm -f $TMPFILE

Agrega stdout y stderr, en el orden intercalado adecuado, al archivo de registro. Y si se produce algún error (según lo determine el estado de salida del comando), envía toda la salida (stdout y stderr) a stdout, nuevamente en el orden correcto intercalado. Encontré que esto es un compromiso razonable para mis necesidades. Si desea un archivo de registro de una sola ejecución en lugar de un archivo de varias ejecuciones en crecimiento, es aún más simple:

myCommand > myCommand.log 2>&1
[ $? != 0 ] && cat myCommand.log
Keith Pomakis
fuente
0

stderr a comando sustituido tee -a, y stdout anexando al mismo archivo:

./script.sh 2> >(tee -a outputfile) >>outputfile

y nota: también asegúrese de ordenar correctamente (pero sin stderr-show) hay un comando 'unbuffer' de las herramientas 'esperar', que simula tty y mantiene stdout / err en orden como se mostraría en la terminal:

unbuffer ./script.sh > outputfile
Asain Kujovic
fuente