Wie kann ich die stdout eines Prozesses auf mehrere Prozesse unter Verwendung von (vorzugsweise unbenannte) Rohre in Unix (oder Windows) senden?

StackOverflow https://stackoverflow.com/questions/60942

Frage

Ich mag die stdout von Prozess proc1 zu zwei Prozessen proc2 und proc3 umleiten:

         proc2 -> stdout
       /
 proc1
       \ 
         proc3 -> stdout

Ich habe versucht,

 proc1 | (proc2 & proc3)

, aber es scheint nicht zu funktionieren, das heißt.

 echo 123 | (tr 1 a & tr 1 b)

schreibt

 b23

stdout statt

 a23
 b23
War es hilfreich?

Lösung

Anmerkung der Redaktion :
 - >(…) ist eine Prozess Substitution das ist eine Nicht-Standard-Shell-Funktion von einige POSIX-kompatible Shells. bash, ksh, zsh
 - Diese Antwort sendet versehentlich den Ausgabeprozess Auswechselung Ausgang durch die Pipeline zu : echo 123 | tee >(tr 1 a) | tr 1 b
.  - Ausgabe aus dem Prozess Ersetzungen wird unvorhersehbar verschachtelt und, außer in zsh kann die Pipeline beenden, bevor die Befehle innerhalb >(…) tun

.

In Unix (oder auf einem Mac), verwenden Sie den tee Befehl :

$ echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/null
b23
a23

In der Regel würden Sie tee verwenden Ausgabe auf mehrere Dateien zu umleiten, aber unter Verwendung von> (...) Sie können an einem anderen Prozess umleiten. Also, in der Regel

$ proc1 | tee >(proc2) ... >(procN-1) >(procN) >/dev/null

wird tun, was Sie wollen.

Unter Windows, ich glaube nicht, das eingebaute in der Schale ein Äquivalent hat. Microsofts Windows Powershell obwohl ein tee Befehl hat.

Andere Tipps

Wie dF sagte bash ermöglicht es das >(…) Konstrukt läuft einen Befehl anstelle einem Dateinamen zu verwenden. (Es gibt auch die <(…) konstruieren, um das ersetzen Ausgabe von einem anderen Befehl anstelle eines Dateinamens, aber das ist jetzt irrelevant, ich erwähne es nur der Vollständigkeit halber).

Wenn Sie nicht bash haben oder mit einer älteren Version von bash auf einem System ausgeführt wird, können Sie manuell tun, was Bash tut, durch die Verwendung von FIFO-Dateien zu machen.

Die generische Art und Weise zu erreichen, was Sie wollen, ist:

  • entscheiden, wie viele Prozesse sollten die Ausgabe Ihres Befehl erhalten, und so viele FIFOs schaffen, vorzugsweise auf einem globalen temporären Ordner:
    subprocesses="a b c d"
    mypid=$$
    for i in $subprocesses # this way we are compatible with all sh-derived shells  
    do
        mkfifo /tmp/pipe.$mypid.$i
    done
  • starten alle Teilprozesse warten Eingabe von den FIFOs:
    for i in $subprocesses
    do
        tr 1 $i </tmp/pipe.$mypid.$i & # background!
    done
  • führen Sie Ihren Befehl an den FIFOs Abschlägen:
    proc1 | tee $(for i in $subprocesses; do echo /tmp/pipe.$mypid.$i; done)
  • schließlich, entfernen Sie die FIFOs:
    for i in $subprocesses; do rm /tmp/pipe.$mypid.$i; done

Hinweis: Aus Kompatibilitätsgründen, ich würde die $(…) mit einfachen Anführungszeichen tun, aber ich konnte es nicht tun, um diese Antwort zu schreiben (die Backquote wird in SO verwendet). Normalerweise ist die $(…) alt genug, um auch in den alten Versionen von KSH zu arbeiten, aber wenn dies nicht der Fall, schließen Sie den Teil in einfachen Anführungszeichen.

Unix (bash, ksh, zsh)

dF. Antwort enthält die Samen eine Antwort basierend auf tee und < em> Ausgang Prozess Ersetzungen
(>(...)), dass kann oder nicht Arbeit, je nach Ihren Anforderungen:

Beachten Sie, dass Prozess Substitutionen sind eine Nicht-Standard Funktion, die (meist)  POSIX-Funktionen-only shells wie dash (die als /bin/sh auf Ubuntu wirkt,  zum Beispiel), tut nicht Unterstützung. Shell-Skripte Targeting /bin/sh sollte nicht auf sie verlassen.

echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/null

Fallen dieses Ansatzes sind:

  • unvorhersehbar, asynchrones Ausgangsverhalten : die Ausgangsströme von den Befehlen innerhalb der Ausgabeprozess Substitutionen >(...) Verschachtelungslänge in unvorhersehbarer Weise

  • .
  • In bash und ksh (im Gegensatz zu zsh gegen - aber siehe Ausnahme unten):

    • Ausgang ankommen nach der Befehl beendet ist.
    • nachfolgenden Befehle beginnen kann Ausführen vor die Befehle in den Prozess Ersetzungen fertig - bash und ksh tun nicht warten Sie auf den Ausgabeprozess Substitution -spawned Prozesse zu beenden, zumindest in der Standardeinstellung.
    • jmb setzt es auch in einem Kommentar zu dF Antwort:.
  

beachten Sie, dass die Befehle innerhalb >(...) gestartet werden, von der ursprünglichen Schale distanzieren, und Sie können nicht so leicht bestimmen, wann sie fertig ist; die tee wird beendet, nachdem alles zu schreiben, aber die substituierten Prozesse werden noch die Daten aus verschiedenen Puffern im Kernel und Datei-I / O, und was auch immer Zeit durch ihren internen Umgang mit Daten verbrauchen genommen wird. Sie können Rennbedingungen auftreten, wenn Ihre äußere Hülle geht dann weiter durch die Teilprozesse erzeugt auf etwas verlassen können.

  • zsh ist die einzige Schale, die hat standardmäßig Warten auf die Prozesse in den Ausgabeprozess Substitutionen laufen zu beenden , < em> außer , wenn es stderr , die zu einem (2> >(...)) umgeleitet wird.

  • ksh (mindestens ab Version 93u+) ermöglicht die Verwendung von Argument losen wait für den Ausgabeprozess substitutions gelaicht Prozesse zu warten, zu beenden.
    Beachten Sie, dass in einer interaktiven Sitzung, die auch beim Warten auf einem anstehenden Hintergrundjobs führen könnte, aber.

  • bash v4.4+ für die wartet zuletzt gestartet Ausgabeprozess Substitution mit wait $!, aber Argumente weniger wait tut nicht Arbeit, so dass diese nicht geeignet für einen Befehl mit mehrere Ausgabeprozess Ersetzungen.

  • Doch bash und ksh sein können gezwungen warten , indem Sie den Befehl kochend | cat , aber beachten Sie, dass dies macht die Befehl ausgeführt in einem Subshell . Caveats :

    • ksh (Stand ksh 93u+) unterstützt nicht das Senden Stderr an einen Ausgabeprozess Substitution (2> >(...)); ein solcher Versuch ist stillschweigend ignoriert .

    • Während zsh ist (loben) synchron standardmäßig mit dem (viel häufiger) stdout Ausgabeprozess Substitutionen, auch kann die | cat Technik sie nicht machen synchron mit stderr Ausgabe process Ersetzungen (2> >(...)).

  • Doch auch wenn Sie sicherstellen, synchrone Ausführung , das Problem der unvorhersehbar verschachtelte Ausgabe bleibt.

Die bash wird in der Regel drucken, bevor <:

Der folgende Befehl ein, wenn in ksh oder AFTER laufen, die problematischen Verhaltensweisen zeigt (Sie es mehrmals zu sehen beide Symptome können laufen müssen) / em> von den Ausgang Ersetzungen und dem Ausgang von letzterem unvorhersehbar verschachtelt werden kann.

printf 'line %s\n' {1..30} | tee >(cat -n) >(cat -n) >/dev/null; echo AFTER

Kurz gesagt: :

  • Die Gewährleistung einer bestimmten pro-Befehlsausgabefolge:

    • Weder bash noch ksh noch zsh Unterstützung des.
  • Synchronausführung:

    • Doable, außer mit stderr -sourced Ausgabeprozess Ersetzungen:
      • In zsh, sie sind immer asynchron.
      • In ksh, sie überhaupt nicht funktionieren .

Wenn Sie mit diesen Einschränkungen leben können, wird unter Verwendung von Ausgangsprozess Ersetzungen einen gangbaren Weg (zum Beispiel, wenn sie alle schreiben, um Ausgabedateien zu trennen).


Beachten Sie, dass tzot ist viel umständlicher, aber potenziell POSIX-konforme Lösung auch unberechenbar Ausgabeverhalten zeigt ; jedoch durch wait verwenden, können Sie sicherstellen, dass nachfolgende Befehle ausgeführt wird nicht starten, bis alle Hintergrundprozesse beendet haben.
Sehen unten für eine robuste, synchron, serialisiert-Ausgang Implementierung .


Die einzige einfach bash Lösung mit vorhersagbaren Ausgabeverhalten ist die folgende, die jedoch ist untragbar langsam mit großem Eingang Sets , weil Shell-Schleifen sind von Natur aus langsam.
Beachten Sie auch, dass diese wechselt die Ausgangsleitungen von den Zielbefehlen .

while IFS= read -r line; do 
  tr 1 a <<<"$line"
  tr 1 b <<<"$line"
done < <(echo '123')

Unix (mit GNU Parallel)

Installieren von GNU parallel ermöglicht eine robuste Lösung mit serialisiert (pro-Befehl) -Ausgang das erlaubt zusätzlich parallele Ausführung :

$ echo '123' | parallel --pipe --tee {} ::: 'tr 1 a' 'tr 1 b'
a23
b23

parallel standardmäßig stellt sicher, dass die Ausgabe von den verschiedenen Befehlen nicht verschachteln (dieses Verhalten kann geändert werden - siehe man parallel).

Hinweis: Einige Linux-Distributionen kommen mit einem andere parallel-Dienstprogramm, das mit dem Befehl nicht über Arbeit; verwenden parallel --version zu bestimmen, welche, falls vorhanden, die Sie haben.


Fenster

Jay Bazuzi die hilfreiche Antwort zeigt, wie es geht in Powershell . Das heißt: Seine Antwort ist die analoge des Loopings oben beantworten bash, wird es untragbar langsam mit großen Eingang setzt und auch wechselt die Ausgangsleitungen von dem Ziel Befehle .



bash-basierte, aber ansonsten tragbare Unix-Lösung mit synchroner Ausführung und Ausgabe Serialisierung

Das folgende ist eine einfache, aber relativ robuste Umsetzung der Antwort vorgestellte Ansatz in der tzot dass zusätzlich bietet:

  • synchrone Ausführung
  • serialisiert (gruppiert) Ausgang

Während nicht streng POSIX-konform, weil es ein bash Skript ist, sollte es sein portable eine Unix-Plattform, hat bash .

Hinweis: Sie können eine Voll fliehen findenged Implementierung unter der MIT-Lizenz in diesen Gist .

Wenn Sie den Code unten als Skript fanout speichern, ausführbar machen und int Ihre PATH setzen, der Befehl von der Frage funktionieren wird, wie folgt:

$ echo 123 | fanout 'tr 1 a' 'tr 1 b'
# tr 1 a
a23
# tr 1 b
b23

fanout Skript Quellcode :

#!/usr/bin/env bash

# The commands to pipe to, passed as a single string each.
aCmds=( "$@" )

# Create a temp. directory to hold all FIFOs and captured output.
tmpDir="${TMPDIR:-/tmp}/$kTHIS_NAME-$$-$(date +%s)-$RANDOM"
mkdir "$tmpDir" || exit
# Set up a trap that automatically removes the temp dir. when this script
# exits.
trap 'rm -rf "$tmpDir"' EXIT 

# Determine the number padding for the sequential FIFO / output-capture names, 
# so that *alphabetic* sorting, as done by *globbing* is equivalent to
# *numerical* sorting.
maxNdx=$(( $# - 1 ))
fmtString="%0${#maxNdx}d"

# Create the FIFO and output-capture filename arrays
aFifos=() aOutFiles=()
for (( i = 0; i <= maxNdx; ++i )); do
  printf -v suffix "$fmtString" $i
  aFifos[i]="$tmpDir/fifo-$suffix"
  aOutFiles[i]="$tmpDir/out-$suffix"
done

# Create the FIFOs.
mkfifo "${aFifos[@]}" || exit

# Start all commands in the background, each reading from a dedicated FIFO.
for (( i = 0; i <= maxNdx; ++i )); do
  fifo=${aFifos[i]}
  outFile=${aOutFiles[i]}
  cmd=${aCmds[i]}
  printf '# %s\n' "$cmd" > "$outFile"
  eval "$cmd" < "$fifo" >> "$outFile" &
done

# Now tee stdin to all FIFOs.
tee "${aFifos[@]}" >/dev/null || exit

# Wait for all background processes to finish.
wait

# Print all captured stdout output, grouped by target command, in sequences.
cat "${aOutFiles[@]}"

Da @dF: erwähnt, dass Powershell hat T-Stück, ich dachte, dass ich einen Weg zeigen würde dies in Powershell zu tun.

PS > "123" | % { 
    $_.Replace( "1", "a"), 
    $_.Replace( "2", "b" ) 
}

a23
1b3

Beachten Sie, dass jedes Objekt aus dem ersten Befehl kommenden verarbeitet wird, bevor das nächste Objekt erstellt wird. Dies kann erlauben, sehr große Eingänge skaliert werden.

eine andere Art und Weise zu tun wäre,

 eval `echo '&& echo 123 |'{'tr 1 a','tr 1 b'} | sed -n 's/^&&//gp'`

Ausgabe:

a23
b23

keine Notwendigkeit, eine Subshell hier

erstellen
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top