Wie erfasst man bestellte STDOUT / STDERR und fügt Zeitstempel / Präfixe hinzu?

25

Ich habe fast alle verfügbaren ähnlichen Fragen ohne Erfolg untersucht.

Lassen Sie mich das Problem im Detail beschreiben:

Ich führe einige unbeaufsichtigte Skripte aus und diese können Standardausgaben und Standardfehlerzeilen erzeugen. Ich möchte sie in der genauen Reihenfolge erfassen, die von einem Terminalemulator angezeigt wird, und ihnen dann ein Präfix wie "STDERR:" und "STDOUT:" hinzufügen.

Ich habe versucht, Pfeifen und sogar epollbasierte Ansätze zu verwenden, ohne Erfolg. Ich denke, die Lösung wird in kleinen Mengen verwendet, obwohl ich kein Meister darin bin. Ich habe auch einen Blick in den Quellcode von Gnomes VTE geworfen , aber das war nicht sehr produktiv.

Idealerweise würde ich Go anstelle von Bash verwenden, um dies zu erreichen, aber ich war nicht in der Lage, dies zu tun. Scheint, als ob Pipes aufgrund der Pufferung automatisch die Einhaltung einer korrekten Zeilenreihenfolge verbieten.

Hat jemand etwas Ähnliches tun können? Oder ist es einfach unmöglich? Ich denke, wenn ein Terminal-Emulator das kann, dann nicht - vielleicht indem Sie ein kleines C-Programm erstellen, das die PTYs anders behandelt?

Idealerweise würde ich asynchrone Eingaben verwenden, um diese 2 Streams (STDOUT und STDERR) zu lesen und sie dann nach Bedarf erneut auszudrucken, aber die Reihenfolge der Eingaben ist entscheidend!

ANMERKUNG: Ich kenne stderred, aber es funktioniert nicht mit Bash-Skripten und kann nicht einfach bearbeitet werden, um ein Präfix hinzuzufügen (da es im Grunde viele Syscalls umschließt).

Update: unten zwei Gists hinzugefügt

(Zufällige Verzögerungen von weniger als einer Sekunde können in dem von mir bereitgestellten Beispielskript hinzugefügt werden, um ein konsistentes Ergebnis zu beweisen.)

Update: Die Lösung dieser Frage würde auch diese andere Frage lösen , wie @Gilles hervorhob. Ich bin jedoch zu dem Schluss gekommen, dass es nicht möglich ist, das zu tun, was hier und da gefragt wurde. Bei der Verwendung 2>&1beiden Ströme sind korrekt am pty / Rohr Ebene verschmolzen, sondern die Ströme getrennt und in der richtigen Reihenfolge sollte man den Ansatz in der Tat zu verwenden , verwendet stderred dass involes syscall Einhaken und wie zu sehen sind schmutzig in vielerlei Hinsicht.

Ich werde diese Frage gerne aktualisieren, wenn jemand das oben Gesagte widerlegen kann.

Deim0s
quelle
1
Ist das nicht was du willst? stackoverflow.com/questions/21564/…
slm
@slm wahrscheinlich nicht, da OP verschiedenen Streams unterschiedliche Strings voranstellen muss .
Peterph
Können Sie mitteilen, warum die Bestellung so wichtig ist? Vielleicht könnte es einen anderen Weg geben, um Ihr Problem zu
umgehen
@peterph ist eine Grundvoraussetzung, wenn ich keine konsistente Ausgabe haben kann, sende ich sie lieber an / dev / null, als sie zu lesen und mich verwirren zu lassen der Anpassung, die ich in dieser Frage stelle
Deim0s

Antworten:

12

Sie könnten Koprozesse verwenden. Einfacher Wrapper, der beide Ausgaben eines bestimmten Befehls an zwei sedInstanzen (eine für stderrdie andere für stdout) weiterleitet, die das Tagging ausführen.

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

Beachten Sie mehrere Dinge:

  1. Es ist eine magische Beschwörung für viele Menschen (einschließlich mich) - aus einem Grund (siehe die verknüpfte Antwort unten).

  2. Es gibt keine Garantie, dass nicht gelegentlich ein paar Leitungen vertauscht werden - alles hängt von der Planung der Coprozesse ab. Eigentlich ist fast garantiert, dass es irgendwann wird. Das heißt, wenn Sie die Reihenfolge streng gleich halten, müssen Sie die Daten von beiden stderrund stdinin demselben Prozess verarbeiten, sonst kann (und wird) der Kernel-Scheduler ein Durcheinander daraus machen.

    Wenn ich das Problem richtig verstehe, bedeutet dies, dass Sie die Shell anweisen müssen, beide Streams auf einen Prozess umzuleiten (dies kann mit AFAIK durchgeführt werden). Die Schwierigkeit beginnt, wenn dieser Prozess zu entscheiden beginnt, was zuerst zu tun ist - er müsste beide Datenquellen abfragen und irgendwann in den Zustand versetzt werden, in dem er einen Datenstrom verarbeiten würde und Daten in beiden Datenströmen ankommen, bevor er beendet wird. Und genau dort bricht es zusammen. Dies bedeutet auch, dass das Umbrechen der Ausgabesyscalls stderredwahrscheinlich der einzige Weg ist, um das gewünschte Ergebnis zu erzielen (und selbst dann kann es zu Problemen kommen, wenn auf einem Multiprozessorsystem ein Multithreading auftritt).

In Bezug auf Coprocesses lesen Sie unbedingt die ausgezeichnete Antwort von Stéphane in Wie verwenden Sie den Befehl coproc in Bash? für einen tiefen Einblick.

peterph
quelle
Vielen Dank an @peterph für Ihre Antwort. Ich suche jedoch speziell nach Möglichkeiten, um die Bestellung aufrechtzuerhalten. Hinweis: Ich denke, Ihr Interpreter sollte wegen der von Ihnen verwendeten Prozessersetzung bash sein (ich erhalte ./test1.sh: 3: ./test1.sh: Syntax error: "(" unexpecteddurch Kopieren / Einfügen Ihres Skripts)
Deim0s
Sehr wahrscheinlich lief ich es bashmit /bin/sh(nicht sicher, warum ich es dort hatte).
Peterph
Ich habe die Frage, wo die Stream-Verwechslung passieren könnte, ein wenig aktualisiert.
Peterph
1
eval $@ist ziemlich fehlerhaft. Verwenden "$@"Sie diese Option, wenn Sie Ihre Argumente als exakte Befehlszeile ausführen möchten. Durch Hinzufügen einer evalInterpretationsebene wird eine Reihe von schwer vorhersehbaren (und möglicherweise böswilligen) Elementen hinzugefügt , wenn Sie Dateinamen oder andere Inhalte übergeben, die Sie nicht als steuern Argumente), Verhaltensweisen und nicht einmal mehr zitieren (Namen mit Leerzeichen in mehrere Wörter aufteilen, Globs erweitern, selbst wenn sie zuvor als wörtlich zitiert wurden, usw.).
Charles Duffy
1
Auch in modernen genug zu haben-Koprozesse bash, die Sie nicht brauchen eval , in einer Variablen zu schließen Filedeskriptoren benannt. exec {SEDo[1]}>&-wird so funktionieren wie sie ist (ja, das Fehlen einer $vor dem {war absichtlich).
Charles Duffy
5

Methode 1. Verwenden von Dateideskriptoren und awk

Wie wäre es mit so etwas, wenn Sie die Lösungen aus diesem SO Q & A mit dem Titel: Gibt es ein Unix-Dienstprogramm, mit dem Zeitstempel vor Textzeilen eingefügt werden können? und diese SO Q & A mit dem Titel: Pipe STDOUT und STDERR zu zwei verschiedenen Prozessen im Shell-Skript? .

Die Vorgehensweise

In Schritt 1 erstellen wir zwei Funktionen in Bash, die die Zeitstempelnachricht ausführen, wenn sie aufgerufen werden:

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

In Schritt 2 würden Sie die oben genannten Funktionen wie folgt verwenden, um die gewünschte Nachricht zu erhalten:

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

Beispiel

Hier habe ich ein Beispiel zusammengestellt, das ain STDOUT schreibt, 10 Sekunden ruht und dann die Ausgabe in STDERR schreibt. Wenn wir diese Befehlssequenz in unser obiges Konstrukt einfügen, erhalten wir die von Ihnen angegebene Nachricht.

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

Methode # 2. Mit Annotate-Ausgabe

Es gibt ein Tool namens annotate-output, das Teil derdevscripts Pakets ist und das tut, was Sie wollen. Es ist nur eine Einschränkung, dass es die Skripte für Sie ausführen muss.

Beispiel

Wenn wir unsere obige Beispielbefehlssequenz in ein Skript mit folgendem Namen mycmds.basheinfügen:

$ cat mycmds.bash 
#!/bin/bash

echo a
sleep 10
echo >&2 b

Wir können es dann so ausführen:

$ 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

Das Ausgabeformat kann für den Timestamp-Teil gesteuert werden, jedoch nicht darüber hinaus. Aber es ist ähnlich wie das, wonach Sie suchen, sodass es genau Ihren Vorstellungen entspricht.

slm
quelle
1
leider löst dies auch nicht das problem des möglichen austauschs einiger leitungen.
Peterph
genau. Ich denke, die Antwort auf meine Frage ist "nicht möglich". Ein Ereignis mit stderredIhnen kann die Grenzen von Linien nicht leicht bestimmen (dies zu versuchen wäre hackisch). Ich wollte sehen, ob mir jemand bei diesem Problem helfen kann, aber anscheinend möchte jeder die einzige Einschränkung ( Reihenfolge ) aufgeben, die der Frage zugrunde liegt
Deim0s
Für Schritt 2 von Methode 1 ist ein weiteres {an der Vorderseite erforderlich, um ordnungsgemäß zu funktionieren.
Austin Hanson