2014-04-23 20 views
6

ich an einer Pipeline arbeite, die einige Verzweigungspunkte hat, die anschließend merge-- sie in etwa so aussehen:Wie kann ich STDOUT von mehreren Prozessen trennen und wieder verbinden?

  command2 
     /  \ 
command1   command4 
     \  /
     command3 

Jeder Befehl an STDOUT schreibt und nimmt Eingaben über STDIN. STDOUT von command1 muss an command2 und command3 übergeben werden, die sequenziell ausgeführt werden, und ihre Ausgabe muss effektiv verkettet und an command4 übergeben werden. Ich dachte zunächst, dass so etwas wie dies funktionieren würde:

$ command1 | (command2; command3) | command4 

Das allerdings nicht funktioniert, da nur STDOUT von command2 geben 4 zu befehlen, und wenn ich entfernen Command4 es offensichtlich ist, dass command3 nicht die geführt wird passender Stream von command1 - mit anderen Worten, es ist so, als ob command2 den Stream erschöpft oder verbraucht. Ich bekomme das gleiche Ergebnis mit {command2; Befehl3; } auch in der Mitte. Also dachte ich, ich sollte 'tee' with process substitution verwenden, und versucht, dies:

$ command1 | tee >(command2) | command3 | command4 

Aber überraschend, dass auch nicht funktioniert - es scheint, dass der Ausgang des command1 und die Ausgabe von command2 in command3 geleitet werden, die führt zu Fehlern und nur die Ausgabe von Befehl3 wird in Befehl4 weitergeleitet. Ich fand, dass die folgende bekommt den entsprechenden Eingang und Ausgang zu und von command2 und command3:

$ command1 | tee >(command2) >(command3) | command4 

jedoch strömt dies die Ausgabe von command1 auch Command4, was zu Problemen wie command2 führt und command3 eine anderes produziert Spezifikation als Befehl1. Die Lösung kam ich habe auf scheint hacky, aber es funktioniert:

$ command1 | tee >(command2) >(command3) > /dev/null | command4 

Das command1 vorbei seine Ausgabe an Command4 unterdrückt, während STDOUT von command2 und command3 zu sammeln. Es funktioniert, aber ich habe das Gefühl, dass mir eine offensichtlichere Lösung fehlt. Bin ich? Ich habe Dutzende von Threads gelesen und habe keine Lösung für dieses Problem gefunden, das in meinem Anwendungsfall funktioniert, noch habe ich eine genaue Ausarbeitung des Problems des Teilens und erneuten Verbindens von Streams gesehen (obwohl ich nicht der erste sein kann) damit umzugehen). Sollte ich nur Named Pipes verwenden? Ich habe versucht, aber ich hatte Schwierigkeiten damit, das zu erreichen, also ist das vielleicht eine andere Geschichte für einen anderen Thread. Ich verwende bash in RHEL5.8.

+0

Sieht so aus, als ob Ihre Frage eine Lösung hat - was funktioniert - fragen Sie nach einer anderen Lösung? Normalerweise wird diese Art der Aufteilung nicht häufig in Shell-Skripten angezeigt, aber häufig in speziellen Tools wie Hadoop-MapReduce - ich glaube nicht, dass Sie etwas besseres als Bash-Pipeline finden werden. – Soren

+0

@Soren - Ja, ich frage mich, ob es eine bessere Lösung gibt. Ich habe keinen Schlaf verloren, da meine Lösung zu funktionieren scheint, aber ich erwarte, dass es eine Lösung gibt, die stdout nicht nach/dev/null umleitet und ich bin neugierig, wo ich mich geirrt habe, da es informativ sein könnte für mich (oder andere), während ich mich weiterentwickele. –

Antwort

4

Sie können mit Dateideskriptoren wie diesem umgehen;

((date | tee >(wc >&3) | wc) 3>&1) | wc 

oder

((command1 | tee >(command2 >&3) | command3) 3>&1) | command4 

erklären To, das heißt tee >(wc >&3) ausgeben wird die Originaldaten auf stdout und die innere wc ausgeben wird das Ergebnis auf FD 3. Die Außen 3> & 1) werden dann füge die FD3-Ausgabe wieder in STDOUT ein, so dass die Ausgabe von beiden wc an den Tailing-Befehl gesendet wird.

JEDOCH gibt es nichts in dieser Pipeline (oder die in Ihrer eigenen Lösung), die garantiert, dass die Ausgabe nicht gemangelt wird.Das sind unvollständige Zeilen von Befehl2, die nicht mit Befehlszeilen vermischt werden3 - wenn das ein Problem ist, müssen Sie eines von zwei Dingen tun;

  1. Ihr eigenes tee Programm schreiben, die intern popen verwendet und jede Zeile zurück, bevor das Senden komplette Linien für Command4 nach stdout lesen
  2. Schreiben Sie die Ausgabe von command2 und command3 in eine Datei zu lesen und verwenden cat die Daten zu fusionieren als Eingabe für Befehl4
+0

Danke - das ist genau das, was ich suche. Ich schätze auch den Hinweis bezüglich der möglichen Interkalation der Ausgangsströme. Ich frage mich, ob es eine elegantere Lösung gibt, als "T-Stück" neu zu schreiben oder eine Datei zu verwenden. Vielleicht könnte ich 'wait' verwenden, um den Befehl3 zu halten, bis der Befehl2 beendet ist? –

+0

Diese Lösung scheint auf bash/ksh/zsh zu funktionieren. Weiß jemand, wie man es mit/bin/static-sh (d. H. Busybox) arbeiten lässt? –

0

Siehe auch https://unix.stackexchange.com/questions/28503/how-can-i-send-stdout-to-multiple-commands. Unter allen Antworten fand ich this answer besonders passend für meine Bedürfnisse.

erweitern Antwort ein wenig @ Soren,

$ ((date | tee >(wc >&3) | wc) 3>&1) | cat -n 
    1   1  6  29 
    2   1  6  29 

Sie ohne T-Stück,

aber eine Umgebungsvariable tun
$ (z=$(date); (echo "$z"| wc); (echo "$z"| wc)) | cat -n 
    1   1  6  29 
    2   1  6  29 

In meinem Fall angewendet ich diese Technik und hat einen sehr komplexen Skript, das unter busybox läuft.