Unix ( bash
, ksh
, zsh
)
Die Antwort von dF. enthält den Keim einer Antwort, die auf Substitutionen des Prozessprozesses basiert tee
und diese ausgibt
( ) , die je nach Ihren Anforderungen möglicherweise funktionieren oder nicht :
>(...)
Beachten Sie, dass Prozessersetzungen eine nicht standardmäßige Funktion sind, die (meistens) nur POSIX-Features-Shells wie dash
(die beispielsweise /bin/sh
unter Ubuntu funktionieren) nicht unterstützen. Das Targeting von Shell-Skripten /bin/sh
sollte sich nicht auf diese verlassen.
echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/null
Die Fallstricke dieses Ansatzes sind:
unvorhersehbares, asynchrones Ausgabeverhalten : Die Ausgabestreams der Befehle innerhalb der Ausgabeprozessersetzungen >(...)
verschachteln sich auf unvorhersehbare Weise.
In bash
und ksh
(im Gegensatz zu zsh
- aber siehe Ausnahme unten):
- Die Ausgabe kann nach Beendigung des Befehls eintreffen .
- nachfolgende Befehle können die Ausführung beginnen , bevor die Befehle in der Prozess Ersetzungen beendet haben -
bash
und ksh
Sie nicht warten , bis der Ausgabeprozess substitutions gelaicht Prozesse zu beenden, zumindest in der Standardeinstellung.
- jmb bringt es gut in einen Kommentar zur Antwort von dF.:
Beachten Sie, dass die darin gestarteten Befehle >(...)
von der ursprünglichen Shell getrennt sind und Sie nicht leicht feststellen können, wann sie beendet sind. Das tee
wird beendet, nachdem alles geschrieben wurde, aber die ersetzten Prozesse verbrauchen immer noch die Daten aus verschiedenen Puffern im Kernel und in der Datei-E / A sowie die Zeit, die für die interne Verarbeitung der Daten benötigt wird. Sie können auf Rennbedingungen stoßen, wenn Ihre äußere Hülle sich dann auf alles verlässt, was durch die Unterprozesse erzeugt wird.
zsh
ist die einzige Shell, die standardmäßig auf den Abschluss der in den Ausgabeprozess-Ersetzungen ausgeführten Prozesse wartet , es sei denn, es handelt sich um stderr , das zu one ( 2> >(...)
) umgeleitet wird .
ksh
(zumindest ab Version 93u+
) ermöglicht die Verwendung von argumentlosen wait
, um auf den Abschluss der durch Substitution hervorgerufenen Prozesse des Ausgabeprozesses zu warten.
Beachten Sie jedoch, dass in einer interaktiven Sitzung möglicherweise auch auf ausstehende Hintergrundjobs gewartet wird .
bash v4.4+
Ich kann auf die zuletzt gestartete Ersetzung des Ausgabeprozesses mit warten wait $!
, aber ohne Argument wait
funktioniert dies nicht. Dies ist für einen Befehl mit mehreren Ersetzungen des Ausgabeprozesses ungeeignet .
Jedoch bash
und ksh
kann gezwungen zu warten , durch Rohrleitungen , den Befehl | cat
, aber beachten Sie, dass dies macht den Befehl ausführen in einer Sub - Shell . Vorsichtsmaßnahmen :
ksh
(ab ksh 93u+
) unterstützt das Senden von stderr an eine Ausgabeprozessersetzung nicht ( 2> >(...)
); Ein solcher Versuch wird stillschweigend ignoriert .
Während zsh
es (lobenswerterweise) standardmäßig mit den (weitaus häufigeren) stdout- Ausgabeprozess-Ersetzungen | cat
synchron ist , kann selbst die Technik sie nicht mit stderr- Ausgabeprozess-Ersetzungen synchronisieren ( 2> >(...)
).
Aber auch wenn Sie sicherstellen , synchrone Ausführung , das Problem der unvorhersehbar verschachtelten Ausgang bleibt.
Der folgende Befehl veranschaulicht beim Ausführen in bash
oder ksh
das problematische Verhalten (möglicherweise müssen Sie ihn mehrmals ausführen, um beide Symptome zu sehen ): Der Befehl AFTER
wird normalerweise vor der Ausgabe der Ausgabesubstitutionen gedruckt , und die Ausgabe der letzteren kann unvorhersehbar verschachtelt werden.
printf 'line %s\n' {1..30} | tee >(cat -n) >(cat -n) >/dev/null; echo AFTER
Kurzum :
Wenn Sie mit diesen Einschränkungen leben können, ist die Verwendung von Ausgabeprozessersetzungen eine praktikable Option (z. B. wenn alle in separate Ausgabedateien schreiben).
Beachten Sie, dass die viel umständlichere, aber möglicherweise POSIX-kompatible Lösung von tzot auch ein unvorhersehbares Ausgabeverhalten aufweist . Durch die Verwendung können wait
Sie jedoch sicherstellen, dass nachfolgende Befehle erst ausgeführt werden, wenn alle Hintergrundprozesse abgeschlossen sind.
Siehe unten für eine robustere, synchron, serialisiert-Ausgang Implementierung .
Die einzige einfache bash
Lösung mit vorhersehbarem Ausgabeverhalten ist die folgende, die jedoch bei großen Eingabesätzen unerschwinglich langsam ist , da Shell-Schleifen von Natur aus langsam sind.
Beachten Sie auch, dass dies die Ausgabezeilen von den Zielbefehlen abwechselt .
while IFS= read -r line; do
tr 1 a <<<"$line"
tr 1 b <<<"$line"
done < <(echo '123')
Unix (mit GNU Parallel)
Die Installation von GNUparallel
ermöglicht eine robuste Lösung mit serialisierter Ausgabe (pro Befehl) , die zusätzlich eine parallele Ausführung ermöglicht :
$ echo '123' | parallel --pipe --tee {} ::: 'tr 1 a' 'tr 1 b'
a23
b23
parallel
Standardmäßig wird sichergestellt, dass die Ausgabe der verschiedenen Befehle nicht verschachtelt wird (dieses Verhalten kann geändert werden - siehe man parallel
).
Hinweis: Einige Linux-Distributionen verfügen über ein anderes parallel
Dienstprogramm, das mit dem obigen Befehl nicht funktioniert. Verwenden Sie parallel --version
diese Option, um festzustellen, welche Sie gegebenenfalls haben.
Windows
Die hilfreiche Antwort von Jay Bazuzi zeigt, wie es in PowerShell geht . Dass sagte: seine Antwort ist die analoge die Looping bash
Antwort oben, es wird untragbar langsam mit großen Eingangsmengen und auch wechselt die Ausgangsleitungen von den Zielbefehlen .
bash
-basierte, aber ansonsten portable Unix-Lösung mit synchroner Ausführung und Ausgabeserialisierung
Das Folgende ist eine einfache, aber einigermaßen robuste Implementierung des in der Antwort von tzot vorgestellten Ansatzes , der zusätzlich Folgendes vorsieht:
- synchrone Ausführung
- serialisierte (gruppierte) Ausgabe
Obwohl es nicht streng POSIX-konform ist, da es sich um ein bash
Skript handelt, sollte es auf jede Unix-Plattform portierbar seinbash
.
Hinweis: In dieser Übersicht finden Sie eine umfassendere Implementierung, die unter der MIT-Lizenz veröffentlicht wurde .
Wenn Sie den folgenden Code als Skript speichern fanout
, ihn ausführbar machen und in Ihren Code einfügen , PATH
funktioniert der Befehl aus der Frage wie folgt:
$ echo 123 | fanout 'tr 1 a' 'tr 1 b'
a23
b23
fanout
Skript-Quellcode :
#!/usr/bin/env bash
aCmds=( "$@" )
tmpDir="${TMPDIR:-/tmp}/$kTHIS_NAME-$$-$(date +%s)-$RANDOM"
mkdir "$tmpDir" || exit
trap 'rm -rf "$tmpDir"' EXIT
maxNdx=$(( $# - 1 ))
fmtString="%0${#maxNdx}d"
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
mkfifo "${aFifos[@]}" || exit
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
tee "${aFifos[@]}" >/dev/null || exit
wait
cat "${aOutFiles[@]}"