Comment capturer STDOUT / STDERR ordonné et ajouter des horodatages / préfixes?

25

J'ai exploré presque toutes les questions similaires disponibles , en vain.

Permettez-moi de décrire le problème en détail:

J'exécute quelques scripts sans assistance et ceux-ci peuvent produire une sortie standard et des lignes d'erreur standard, je veux les capturer dans leur ordre précis affiché par un émulateur de terminal , puis leur ajouter un préfixe comme "STDERR:" et "STDOUT:".

J'ai essayé d'utiliser des tuyaux et même une approche basée sur epoll sur eux, en vain. Je pense que la solution est en usage pty, bien que je ne sois pas maître à cela. J'ai également jeté un œil au code source du VTE de Gnome , mais cela n'a pas été très productif.

Idéalement, j'utiliserais Go au lieu de Bash pour y parvenir, mais je n'y suis pas parvenu. Il semble que les canaux interdisent automatiquement de conserver un ordre de lignes correct en raison de la mise en mémoire tampon.

Quelqu'un a-t-il pu faire quelque chose de similaire? Ou c'est tout simplement impossible? Je pense que si un émulateur de terminal peut le faire, ce n'est pas le cas - peut-être en créant un petit programme C qui gère différemment les PTY?

Idéalement, j'utiliserais une entrée asynchrone pour lire ces 2 flux (STDOUT et STDERR) et les réimprimer ensuite en fonction de mes besoins, mais l'ordre d'entrée est crucial!

REMARQUE: je connais stderred, mais cela ne fonctionne pas pour moi avec les scripts Bash et ne peut pas être facilement modifié pour ajouter un préfixe (car il encapsule essentiellement de nombreux appels système).

Mise à jour: ajouté sous deux points essentiels

(des retards aléatoires d'une seconde peuvent être ajoutés dans l'exemple de script que j'ai fourni pour prouver un résultat cohérent)

Mise à jour: une solution à cette question résoudrait également cette autre question , comme l'a souligné @Gilles. Cependant, je suis arrivé à la conclusion qu'il n'est pas possible de faire ce qui a été demandé ici et là. Lorsque vous utilisez les 2>&1deux flux, ils sont correctement fusionnés au niveau pty / pipe, mais pour utiliser les flux séparément et dans le bon ordre, vous devez en effet utiliser l'approche de stderred qui évoque le raccordement syscall et peut être considérée comme sale de plusieurs manières.

Je serai impatient de mettre à jour cette question si quelqu'un peut réfuter ce qui précède.

Deim0s
la source
1
N'est-ce pas ce que tu veux? stackoverflow.com/questions/21564/…
slm
@slm probablement pas, car OP doit ajouter différentes chaînes à différents flux.
peterph
Pouvez-vous expliquer pourquoi la commande est si importante? Peut-être qu'il pourrait y avoir un autre moyen de contourner votre problème ...
peterph
@peterph c'est un prérequis, si je ne peux pas avoir une sortie cohérente, je préfère l'envoyer à / dev / null que de le lire et de le confondre :) 2> & 1 préserve l'ordre par exemple, mais ne permet pas le type de personnalisation que je pose dans cette question
Deim0s

Réponses:

12

Vous pouvez utiliser des coprocessus. Wrapper simple qui alimente les deux sorties d'une commande donnée à deux sedinstances (l'une pour stderrl'autre pour stdout), qui effectuent le balisage.

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

Notez plusieurs choses:

  1. C'est une incantation magique pour de nombreuses personnes (y compris moi) - pour une raison (voir la réponse liée ci-dessous).

  2. Il n'y a aucune garantie qu'il n'échangera pas occasionnellement quelques lignes - tout dépend de la programmation des coprocessus. En fait, il est presque garanti qu'à un moment donné, ce sera le cas. Cela dit, si vous gardez l'ordre strictement le même, vous devez traiter les données à la fois stderret stdindans le même processus, sinon le planificateur du noyau peut (et va) en faire un gâchis.

    Si je comprends bien le problème, cela signifie que vous devrez demander au shell de rediriger les deux flux vers un seul processus (ce qui peut être fait AFAIK). Le problème commence lorsque ce processus commence à décider sur quoi agir en premier - il devrait interroger les deux sources de données et à un moment donné se mettre dans l'état où il traiterait un flux et les données arriveraient aux deux flux avant de se terminer. Et c'est exactement là où ça tombe en panne. Cela signifie également que l'encapsulation des appels système de sortie comme stderredest probablement le seul moyen d'atteindre le résultat souhaité (et même alors, vous pourriez avoir un problème une fois que quelque chose devient multithread sur un système multiprocesseur).

En ce qui concerne les coprocessions, assurez-vous de lire l'excellente réponse de Stéphane dans Comment utilisez-vous la commande coproc dans Bash? pour un aperçu en profondeur.

peterph
la source
Merci @peterph pour votre réponse, mais je cherche spécifiquement des moyens de conserver la commande. Remarque: Je pense que votre interprète devrait être bash en raison de la substitution de processus que vous utilisez (j'obtiens ./test1.sh: 3: ./test1.sh: Syntax error: "(" unexpecteden
copiant
Très probablement, je l'ai couru bashavec /bin/sh(je ne sais pas pourquoi je l'avais là-bas).
peterph
J'ai un peu mis à jour la question, concernant l'endroit où la confusion de flux pourrait se produire.
peterph
1
eval $@est assez bogué. À utiliser "$@"si vous souhaitez exécuter vos arguments en tant que ligne de commande exacte - l'ajout d'une couche d' evalinterprétation génère un tas de données difficiles à prévoir (et potentiellement malveillantes, si vous transmettez des noms de fichiers ou d'autres contenus que vous ne contrôlez pas comme arguments), et en omettant de citer même le moreso (divise les noms avec des espaces en plusieurs mots, développe les globes même s'ils ont été précédemment cités pour être littéraux, etc.).
Charles Duffy
1
De plus, dans bash suffisamment moderne pour avoir des coprocessus, vous n'avez pas besoin eval de fermer les descripteurs de fichiers nommés dans une variable. exec {SEDo[1]}>&-fonctionnera tel quel (oui, l'absence d'un $avant le {était délibéré).
Charles Duffy
5

Méthode n ° 1. Utilisation de descripteurs de fichiers et awk

Qu'en est-il de quelque chose comme ça en utilisant les solutions de ce SO Q&A intitulé: Existe - t-il un utilitaire Unix pour ajouter des horodatages aux lignes de texte? et ce SO Q&A intitulé: pipe STDOUT et STDERR à deux processus différents dans un script shell? .

L'approche

Étape 1, nous créons 2 fonctions dans Bash qui exécuteront le message d'horodatage lors de l'appel:

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

Étape 2, vous utiliseriez les fonctions ci-dessus comme pour obtenir la messagerie souhaitée:

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

Exemple

Ici, j'ai concocté un exemple qui va écrire adans STDOUT, dort pendant 10 secondes, puis écrit la sortie dans STDERR. Lorsque nous mettons cette séquence de commandes dans notre construction ci-dessus, nous obtenons des messages comme vous l'avez spécifié.

$ { { 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éthode n ° 2. Utilisation d'annoter-sortie

Il existe un outil appelé annotate-outputfaisant partie du devscriptspackage qui fera ce que vous voulez. Sa seule restriction est qu'il doit exécuter les scripts pour vous.

Exemple

Si nous mettons notre exemple de séquence de commandes ci-dessus dans un script appelé mycmds.bashainsi:

$ cat mycmds.bash 
#!/bin/bash

echo a
sleep 10
echo >&2 b

Nous pouvons ensuite l'exécuter comme ceci:

$ 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

Le format de la sortie peut être contrôlé pour la partie d'horodatage mais pas au-delà. Mais c'est une sortie similaire à ce que vous recherchez, donc cela peut correspondre à la facture.

slm
la source
1
malheureusement, cela ne résout pas non plus le problème de permutation possible de certaines lignes.
peterph
exactement. Je pense que la réponse à ma question est "impossible". Un événement avec stderredvous ne peut pas facilement déterminer les limites des lignes (essayer serait donc hackish). Je voulais voir si quelqu'un pouvait m'aider avec ce problème mais apparemment tout le monde veut renoncer à la seule contrainte ( ordre ) qui est la base de la question
Deim0s
L'étape 2 de la méthode 1 nécessite un autre {à l'avant pour fonctionner correctement.
Austin Hanson