Wie kann ich die stdout eines Prozesses auf mehrere Prozesse unter Verwendung von (vorzugsweise unbenannte) Rohre in Unix (oder Windows) senden?
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
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
undksh
(im Gegensatz zuzsh
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
undksh
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; dietee
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 Version93u+
) ermöglicht die Verwendung von Argument losenwait
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 mitwait $!
, aber Argumente wenigerwait
tut nicht Arbeit, so dass diese nicht geeignet für einen Befehl mit mehrere Ausgabeprozess Ersetzungen. -
Doch
bash
undksh
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
(Standksh 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.
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
nochksh
nochzsh
Unterstützung des.
- Weder
-
Synchronausführung:
- Doable, außer mit stderr -sourced Ausgabeprozess Ersetzungen:
- In
zsh
, sie sind immer asynchron. - In
ksh
, sie überhaupt nicht funktionieren .
- In
- Doable, außer mit stderr -sourced Ausgabeprozess Ersetzungen:
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