¿Cómo capturar STDOUT / STDERR ordenado y agregar marcas de tiempo / prefijos?

25

He explorado casi todas las preguntas similares disponibles , sin resultado.

Permítanme describir el problema en detalle:

Ejecuto algunos scripts desatendidos y estos pueden producir resultados estándar y líneas de error estándar, quiero capturarlos en su orden preciso como se muestra en un emulador de terminal y luego agregarles un prefijo como "STDERR:" y "STDOUT:".

He intentado usar tuberías e incluso un enfoque basado en epoll en ellas, sin éxito. Creo que la solución está en uso pty, aunque no soy un maestro en eso. También he echado un vistazo al código fuente del VTE de Gnome , pero eso no ha sido muy productivo.

Lo ideal sería utilizar Go en lugar de Bash para lograr esto, pero no he podido. Parece que las tuberías prohíben automáticamente mantener un orden de líneas correcto debido al almacenamiento en búfer.

¿Alguien ha podido hacer algo similar? ¿O es simplemente imposible? Creo que si un emulador de terminal puede hacerlo, entonces no es así, ¿tal vez creando un pequeño programa en C que maneje los PTY de manera diferente?

Idealmente, usaría una entrada asíncrona para leer estos 2 flujos (STDOUT y STDERR) y luego volver a imprimirlos en segundo lugar según mis necesidades, ¡pero el orden de entrada es crucial!

NOTA: Soy consciente de stderred pero no funciona para mí con los scripts de Bash y no se puede editar fácilmente para agregar un prefijo (ya que básicamente envuelve muchas llamadas de sistema).

Actualización: agregado debajo de dos aspectos esenciales

(se pueden agregar retrasos aleatorios de menos de un segundo en el script de muestra que proporcioné para probar un resultado consistente)

Actualización: la solución a esta pregunta también resolvería esta otra pregunta , como señaló @Gilles. Sin embargo, he llegado a la conclusión de que no es posible hacer lo que se pide aquí y allá. Cuando se usan 2>&1ambas transmisiones se fusionan correctamente en el nivel pty / pipe, pero para usar las transmisiones por separado y en el orden correcto, se debe utilizar el enfoque de stderred que invoca el enganche de syscall y puede verse como sucio de muchas maneras.

Estaré ansioso por actualizar esta pregunta si alguien puede corregir la información anterior.

Deim0s
fuente
1
¿No es esto lo que quieres? stackoverflow.com/questions/21564/…
slm
@slm probablemente no, ya que OP necesita anteponer cadenas diferentes a secuencias diferentes.
Peter
¿Puedes compartir por qué el pedido es tan importante? Tal vez podría haber alguna otra manera alrededor de su problema ...
peterph
@peterph es un requisito previo, si no puedo tener resultados consistentes, prefiero enviarlo a / dev / null que leerlo y confundirme con él :) 2> & 1 conserva el orden, por ejemplo, pero no permite el tipo de personalización que hago en esta pregunta
Deim0s

Respuestas:

12

Puede usar coprocesos. Contenedor simple que alimenta ambas salidas de un comando dado a dos sedinstancias (una para stderrla otra stdout), que realizan el etiquetado.

#!/bin/bash
exec 3>&1
coproc SEDo ( sed "s/^/STDOUT: /" >&3 )
exec 4>&2-
coproc SEDe ( sed "s/^/STDERR: /" >&4 )
eval $@ 2>&${SEDe[1]} 1>&${SEDo[1]}
eval exec "${SEDo[1]}>&-"
eval exec "${SEDe[1]}>&-"

Tenga en cuenta varias cosas:

  1. Es un encantamiento mágico para muchas personas (incluyéndome a mí), por una razón (vea la respuesta vinculada a continuación).

  2. No hay garantía de que ocasionalmente intercambie un par de líneas, todo depende de la programación de los coprocesos. En realidad, está casi garantizado que en algún momento lo hará. Dicho esto, si mantiene el orden estrictamente igual, debe procesar los datos de ambos stderry stdinen el mismo proceso, de lo contrario, el programador del núcleo puede (y lo hará) un desastre.

    Si entiendo el problema correctamente, significa que necesitaría indicarle al shell que redirija ambas secuencias a un solo proceso (que se puede hacer AFAIK). El problema comienza cuando ese proceso comienza a decidir sobre qué actuar primero: tendría que sondear ambas fuentes de datos y, en algún momento, llegar al estado en el que procesaría una secuencia y los datos llegarían a ambas secuencias antes de que finalice. Y eso es exactamente donde se descompone. También significa que envolver las llamadas al sistema de salida como stderredes probablemente la única forma de lograr el resultado deseado (e incluso entonces podría tener un problema una vez que algo se multiprocese en un sistema multiprocesador).

En cuanto a los coprocesos, asegúrese de leer la excelente respuesta de Stéphane en ¿Cómo utiliza el comando coproc en Bash? para una visión profunda.

Peterph
fuente
Gracias @peterph por su respuesta, sin embargo, estoy buscando específicamente formas de preservar el pedido. Nota: Creo que su intérprete debería ser bash debido a la sustitución del proceso que utiliza (obtengo ./test1.sh: 3: ./test1.sh: Syntax error: "(" unexpectedcopiando / pegando su script)
Deim0s
Muy probablemente, lo encontré bashcon /bin/sh(no estoy seguro de por qué lo tenía allí).
Peter
He actualizado un poco la pregunta, con respecto a dónde podría ocurrir la confusión de la transmisión.
Peter
1
eval $@es bastante buggy Úselo "$@"si desea ejecutar sus argumentos como una línea de comando exacta: agregar una capa de evalinterpretación arroja un montón de elementos difíciles de predecir (y potencialmente maliciosos, si está pasando nombres de archivos u otro contenido que no controla como argumentos), y al no citar incluso más (divide los nombres con espacios en varias palabras, expande los globos incluso si se citaron previamente como literales, etc.).
Charles Duffy
1
Además, en bash lo suficientemente moderno como para tener coprocesos, no es necesario eval cerrar los descriptores de archivo nombrados en una variable. exec {SEDo[1]}>&-funcionará tal cual (sí, la falta de un $antes del {deliberado).
Charles Duffy
5

Método 1. Usando descriptores de archivo y awk

¿Qué pasa con algo como esto usando las soluciones de este SO Q&A titulado: ¿Hay una utilidad Unix para anteponer marcas de tiempo a las líneas de texto? y este SO Q&A titulado: canalizar STDOUT y STDERR a dos procesos diferentes en el script de shell .

El enfoque

Paso 1, creamos 2 funciones en Bash que realizarán el mensaje de marca de tiempo cuando se llame:

$ msgOut () {  awk '{ print strftime("STDOUT: %Y-%m-%d %H:%M:%S"), $0; fflush(); }'; }
$ msgErr () {  awk '{ print strftime("STDERR: %Y-%m-%d %H:%M:%S"), $0; fflush(); }'; }

Paso 2 usaría las funciones anteriores para obtener el mensaje deseado:

$ { { { ...command/script... } 2>&3; } 2>&3 | msgErr; } 3>&1 1>&2 | msgOut

Ejemplo

Aquí he inventado un ejemplo que escribirá aen STDOUT, duerme durante 10 segundos y luego escribe la salida en STDERR. Cuando ponemos esta secuencia de comandos en nuestra construcción anterior, recibimos mensajes como usted especificó.

$ { { echo a; sleep 10; echo >&2 b; } 2>&3 | \
    msgErr; } 3>&1 1>&2 | msgOut
STDERR: 2014-09-26 09:22:12 a
STDOUT: 2014-09-26 09:22:22 b

Método 2. Usando anotar-salida

Hay una herramienta llamada annotate-outputque es parte del devscriptspaquete que hará lo que quieras. Su única restricción es que debe ejecutar los scripts por usted.

Ejemplo

Si ponemos nuestra secuencia de comandos de ejemplo anterior en un script llamado mycmds.bashasí:

$ cat mycmds.bash 
#!/bin/bash

echo a
sleep 10
echo >&2 b

Entonces podemos ejecutarlo así:

$ annotate-output ./mycmds.bash 
09:48:00 I: Started ./mycmds.bash
09:48:00 O: a
09:48:10 E: b
09:48:10 I: Finished with exitcode 0

El formato de salida se puede controlar para la parte de marca de tiempo, pero no más allá de eso. Pero es una salida similar a la que está buscando, por lo que puede cumplir con los requisitos.

slm
fuente
1
desafortunadamente, esto tampoco resuelve el problema de posiblemente intercambiar algunas líneas.
Peter
exactamente. Creo que la respuesta a esta pregunta mía "no es posible". El evento con stderredusted no puede determinar fácilmente los límites de las líneas (intentarlo sería hackear). Quería ver si alguien podría ayudarme con este problema, pero aparentemente todos quieren renunciar a la única restricción ( orden ) que es la base de la pregunta
Deim0s
El paso 2 del Método 1 requiere otro {en la parte delantera para funcionar correctamente.
Austin Hanson