Unix ( bash, ksh, zsh)
Die Antwort von dF. enthält den Keim einer Antwort, die auf Substitutionen des Prozessprozesses basiert teeund 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/shunter Ubuntu funktionieren) nicht unterstützen. Das Targeting von Shell-Skripten /bin/shsollte 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 bashund 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 -
bashund kshSie 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 teewird 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.
zshist 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 waitfunktioniert dies nicht. Dies ist für einen Befehl mit mehreren Ersetzungen des Ausgabeprozesses ungeeignet .
Jedoch bashund kshkann 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 zshes (lobenswerterweise) standardmäßig mit den (weitaus häufigeren) stdout- Ausgabeprozess-Ersetzungen | catsynchron 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 bashoder kshdas problematische Verhalten (möglicherweise müssen Sie ihn mehrmals ausführen, um beide Symptome zu sehen ): Der Befehl AFTERwird 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 waitSie 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
parallelStandardmäß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 --versiondiese 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 bashAntwort 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 bashSkript 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 , PATHfunktioniert der Befehl aus der Frage wie folgt:
$ echo 123 | fanout 'tr 1 a' 'tr 1 b'
a23
b23
fanoutSkript-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[@]}"