Come acquisire STDOUT / STDERR ordinato e aggiungere timestamp / prefissi?


25

Ho esplorato quasi tutte le domande simili disponibili , senza risultati.

Vorrei descrivere il problema in dettaglio:

Eseguo alcuni script incustoditi e questi possono produrre output standard e righe di errore standard, voglio catturarli nel loro ordine preciso come visualizzato da un emulatore di terminale e quindi aggiungere un prefisso come "STDERR:" e "STDOUT:" a loro.

Ho provato a usare pipe e persino un approccio basato su epoll su di esse, senza risultati. Penso che la soluzione sia nell'uso pty, anche se non sono un maestro in questo. Ho anche dato una sbirciatina al codice sorgente del VTE di Gnome , ma non è stato molto produttivo.

Idealmente avrei usato Go invece di Bash per ottenere questo risultato, ma non sono stato in grado di farlo. Sembra che i tubi proibiscano automaticamente di mantenere un corretto ordine delle linee a causa del buffering.

Qualcuno è stato in grado di fare qualcosa di simile? O è semplicemente impossibile? Penso che se un emulatore di terminale può farlo, allora non lo è - forse creando un piccolo programma C che gestisce i PTY in modo diverso?

Idealmente, vorrei utilizzare l'input asincrono per leggere questi 2 flussi (STDOUT e STDERR) e quindi ristamparli secondo le mie esigenze, ma l'ordine di input è cruciale!

NOTA: sono a conoscenza di stderred ma non funziona per me con gli script Bash e non posso essere facilmente modificato per aggiungere un prefisso (poiché fondamentalmente avvolge un sacco di syscall).

Aggiornamento: aggiunto sotto due elementi

(i ritardi casuali al secondo possono essere aggiunti nello script di esempio che ho fornito per dimostrare un risultato coerente)

Aggiornamento: la soluzione a questa domanda risolverebbe anche questa altra domanda , come ha sottolineato @Gilles. Tuttavia sono giunto alla conclusione che non è possibile fare ciò che è stato chiesto qua e là. Quando si utilizzano 2>&1entrambi i flussi, questi vengono correttamente uniti a livello di pty / pipe, ma per utilizzare i flussi separatamente e nell'ordine corretto, si dovrebbe effettivamente utilizzare l'approccio di stderred che invoca l'aggancio di syscall e può essere visto come sporco in molti modi.

Non vedo l'ora di aggiornare questa domanda se qualcuno può smentire quanto sopra.


1
Non è quello che vuoi? stackoverflow.com/questions/21564/…
slm

@slm probabilmente non, dal momento che le esigenze OP anteporre diverse stringhe in diversi flussi.
peterph,

Puoi condividere perché l'ordine è così importante? Forse potrebbe esserci un altro modo per aggirare il tuo problema ...
peterph,

@peterph è un prerequisito, se non posso avere un output coerente preferirei inviarlo a / dev / null piuttosto che leggerlo e confonderlo da esso :) 2> & 1 conserva l'ordine per esempio, ma non consente il tipo di personalizzazione che chiedo in questa domanda
Deim0s

Risposte:


12

È possibile utilizzare i coprocessi. Semplice wrapper che fornisce entrambi gli output di un determinato comando a due sedistanze (una per stderrl'altra per stdout), che eseguono il tagging.

#!/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]}>&-"

Nota diverse cose:

  1. È un incantesimo magico per molte persone (incluso me) - per un motivo (vedere la risposta collegata di seguito).

  2. Non vi è alcuna garanzia che non cambi di tanto in tanto un paio di righe - tutto dipende dalla pianificazione dei coprocessi. In realtà, è quasi garantito che ad un certo punto lo farà. Detto questo, se si mantiene l'ordine esattamente lo stesso, è necessario elaborare i dati da entrambi stderre stdinnello stesso processo, altrimenti lo scheduler del kernel può (e lo farà) rovinare.

    Se capisco correttamente il problema, significa che dovresti istruire la shell a reindirizzare entrambi i flussi su un processo (che può essere fatto AFAIK). Il problema inizia quando quel processo inizia a decidere su cosa agire per primo: dovrebbe eseguire il polling di entrambe le origini dati e ad un certo punto entrare nello stato in cui elaborerà un flusso e i dati arriveranno ad entrambi i flussi prima che finisca. Ed è esattamente dove si rompe. Significa anche che avvolgere le syscalls di output come stderredè probabilmente l'unico modo per ottenere il risultato desiderato (e anche in questo caso potresti avere un problema quando qualcosa diventa multithread su un sistema multiprocessore).

Per quanto riguarda i coprocessi, assicurati di leggere l'eccellente risposta di Stéphane in Come usi il comando coproc in Bash? per approfondimenti.


Grazie @peterph per la tua risposta, tuttavia sto cercando specificamente modi per preservare l'ordine. Nota: penso che il tuo interprete dovrebbe essere bash a causa della sostituzione del processo che usi (ottengo ./test1.sh: 3: ./test1.sh: Syntax error: "(" unexpectedcopiando / incollando il tuo script)
Deim0s

Molto probabilmente, l'ho eseguito bashcon /bin/sh(non so perché l'ho avuto lì).
peterph,

Ho aggiornato un po 'la domanda, riguardo a dove potrebbe verificarsi il mix-up dello stream.
peterph,

1
eval $@è abbastanza difettoso. Usa "$@"se vuoi eseguire i tuoi argomenti come una riga di comando esatta - l'aggiunta di un livello di evalinterpretazione genera un mucchio di difficili da prevedere (e potenzialmente dannosi, se stai passando nomi di file o altri contenuti che non controlli come argomenti) e non riuscire a citare anche il moreso (suddivide i nomi con spazi in più parole, espande i globi anche se precedentemente citati come letterali, ecc.).
Charles Duffy,

1
Inoltre, in bash abbastanza moderno da avere coprocessi, non è necessario eval chiudere i descrittori di file denominati in una variabile. exec {SEDo[1]}>&-funzionerà così com'è (sì, la mancanza di una $prima {era deliberata).
Charles Duffy,

5

Metodo n. 1. Utilizzando descrittori di file e awk

Che ne dici di qualcosa del genere che usa le soluzioni di questo Q&A SO intitolato: Esiste un'utilità Unix per anteporre timestamp a righe di testo? e questo D&R SO intitolato: pipe STDOUT e STDERR a due diversi processi in shell script? .

L'approccio

Passaggio 1, creiamo 2 funzioni in Bash che eseguiranno il messaggio timestamp quando chiamato:

$ 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(); }'; }

Passo 2 useresti le funzioni sopra come in questo modo per ottenere la messaggistica desiderata:

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

Esempio

Qui ho inventato un esempio che scriverà asu STDOUT, dormirà per 10 secondi e quindi scriverà l'output su STDERR. Quando inseriamo questa sequenza di comandi nel nostro costrutto sopra, riceviamo messaggi come specificato.

$ { { 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

Metodo n. 2. Utilizzo di annotate-output

C'è uno strumento chiamato annotate-outputche fa parte del devscriptspacchetto che farà quello che vuoi. L'unica limitazione è che deve eseguire gli script per te.

Esempio

Se inseriamo la nostra sequenza di comandi dell'esempio sopra in uno script chiamato mycmds.bashcosì:

$ cat mycmds.bash 
#!/bin/bash

echo a
sleep 10
echo >&2 b

Possiamo quindi eseguirlo in questo modo:

$ 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

Il formato dell'output può essere controllato per la parte timestamp ma non oltre. Ma è un output simile a quello che stai cercando, quindi potrebbe adattarsi al conto.


1
sfortunatamente anche questo non risolve il problema di scambiare alcune righe.
peterph,

Esattamente. Penso che la risposta a questa mia domanda sia "impossibile". L'evento con stderredte non è in grado di determinare facilmente i confini delle linee (provarlo sarebbe un trucco). Volevo vedere se qualcuno potesse aiutarmi con questo problema, ma a quanto pare tutti vogliono rinunciare al singolo vincolo ( ordine ) che è la base della domanda
Deim0s

Il passaggio 2 del metodo 1 richiede un altro {in primo piano per funzionare correttamente.
Austin Hanson,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.