Anmerkung der Redaktion :
- >(…)
ist ein Prozess Substitution , die eine ist Nicht - Standard - Shell - Funktion von einigem 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
.
- Die Ausgabe der Prozessersetzungen wird unvorhersehbar verschachtelt, und außer in zsh
kann die Pipeline beendet werden, bevor die darin enthaltenen Befehle ausgeführt werden >(…)
.
Verwenden Sie unter Unix (oder auf einem Mac) den folgenden tee
Befehl :
$ echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/null
b23
a23
Normalerweise würden Sie verwenden tee
, um die Ausgabe in mehrere Dateien umzuleiten, aber mit> (...) können Sie zu einem anderen Prozess umleiten. Also im Allgemeinen
$ proc1 | tee >(proc2) ... >(procN-1) >(procN) >/dev/null
wird tun was du willst.
Unter Fenstern glaube ich nicht, dass die eingebaute Shell ein Äquivalent hat. Die Windows PowerShell von Microsoft verfügt jedoch über einen tee
Befehl.
tee
Hängt die Ausgabe einer Prozessumleitung an,stdout
bevor sie über die Pipe gesendet wird. Dies unterscheidet sich deutlich von der Weiterleitung derselben Instanzstdout
an mehrere Befehle. @dF führt beispielsweiseecho 123 | tee >(tr 1 a) | tr 2 b
dazu1b3 ab3
, was im Kontext der ursprünglichen Frage keinen Sinn ergibt .inproc | tee >(outproc1) >(outproc2) > /dev/null | outproc
. outproc sieht nur die von outproc1 und outproc2 erzeugte Ausgabe. Die ursprüngliche Eingabe ist "weg".Wie dF sagte,
bash
erlaubt es, das>(…)
Konstrukt zu verwenden, das einen Befehl anstelle eines Dateinamens ausführt. (Es gibt auch das<(…)
Konstrukt, das die Ausgabe eines anderen Befehls anstelle eines Dateinamens ersetzt, aber das ist jetzt irrelevant, ich erwähne es nur der Vollständigkeit halber).Wenn Sie kein Bash haben oder auf einem System mit einer älteren Version von Bash ausgeführt werden, können Sie manuell das tun, was Bash tut, indem Sie FIFO-Dateien verwenden.
Der generische Weg, um das zu erreichen, was Sie wollen, ist:
HINWEIS: Aus Kompatibilitätsgründen würde ich dies
$(…)
mit Backquotes tun , aber ich konnte diese Antwort nicht schreiben (das Backquote wird in SO verwendet). Normalerweise ist das$(…)
alt genug, um auch in alten Versionen von ksh zu funktionieren. Wenn dies nicht der Fall ist, fügen Sie den…
Teil in Anführungszeichen ein.quelle
mkfifo
eher als verwenden solltenmknod
, da nur der erstere POSIX-kompatibel ist. Außerdem ist die Verwendung von nicht zitierten Befehlsersetzungen spröde, und es besteht die Möglichkeit, Globbing aus Effizienzgründen zu verwenden. Ich habebash
mir erlaubt, in meiner Antwort eine robustere - wenn auch basierte - Lösung zu implementieren . Beachten Sie, dass dies$(…)
schon lange Teil von POSIX ist, daher würde ich mich von weniger vorhersehbaren fernhalten`…`
(und SO erlaubt definitiv die Verwendung`
in Codeblöcken und sogar Inline-Code-Bereichen (zumindest jetzt :)).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
undksh
(im Gegensatz zuzsh
- aber siehe Ausnahme unten):bash
undksh
Sie nicht warten , bis der Ausgabeprozess substitutions gelaicht Prozesse zu beenden, zumindest in der Standardeinstellung.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 Version93u+
) ermöglicht die Verwendung von argumentlosenwait
, 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 wartenwait $!
, aber ohne Argumentwait
funktioniert dies nicht. Dies ist für einen Befehl mit mehreren Ersetzungen des Ausgabeprozesses ungeeignet .Jedoch
bash
undksh
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
(abksh 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
oderksh
das problematische Verhalten (möglicherweise müssen Sie ihn mehrmals ausführen, um beide Symptome zu sehen ): Der BefehlAFTER
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 :
Garantieren einer bestimmten Ausgabesequenz pro Befehl:
bash
nochksh
nochzsh
unterstützen wir das.Synchrone Ausführung:
zsh
sind sie immer asynchron.ksh
sie überhaupt nicht .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 GNU
parallel
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 - sieheman parallel
).Hinweis: Einige Linux-Distributionen verfügen über ein anderes
parallel
Dienstprogramm, das mit dem obigen Befehl nicht funktioniert. Verwenden Sieparallel --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 AusgabeserialisierungDas Folgende ist eine einfache, aber einigermaßen robuste Implementierung des in der Antwort von tzot vorgestellten Ansatzes , der zusätzlich Folgendes vorsieht:
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' # 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[@]}"
quelle
Da @dF: erwähnt hat, dass PowerShell ein Tee hat, dachte ich, ich würde einen Weg zeigen, dies in PowerShell zu tun.
PS > "123" | % { $_.Replace( "1", "a"), $_.Replace( "2", "b" ) } a23 1b3
Beachten Sie, dass jedes Objekt, das aus dem ersten Befehl kommt, verarbeitet wird, bevor das nächste Objekt erstellt wird. Dies kann eine Skalierung auf sehr große Eingaben ermöglichen.
quelle
while IFS= read -r line; do tr 1 a <<<"$line"; tr 1 b <<<"$line"; done < <(echo '123')
Bash, das in Bezug auf das Gedächtnis gut skaliert , aber nicht in Bezug auf die Leistung .Sie können die Ausgabe auch in einer Variablen speichern und für die anderen Prozesse verwenden:
out=$(proc1); echo "$out" | proc2; echo "$out" | proc3
Dies funktioniert jedoch nur, wenn
proc1
endet irgendwann :-)proc1
produziert nicht zu viel Ausgabe (weiß nicht, wo die Grenzen liegen, aber es ist wahrscheinlich dein RAM)Es ist jedoch leicht zu merken und bietet Ihnen mehr Optionen für die Ausgabe der Prozesse, die Sie dort erzeugt haben, z.
out=$(proc1); echo $(echo "$out" | proc2) / $(echo "$out" | proc3) | bc
Ich hatte Schwierigkeiten, so etwas mit dem
| tee >(proc2) >(proc3) >/dev/null
Ansatz zu machen.quelle
Ein anderer Weg wäre:
eval `echo '&& echo 123 |'{'tr 1 a','tr 1 b'} | sed -n 's/^&&//gp'`
Ausgabe:
Hier muss keine Unterschale erstellt werden
quelle
echo 123 |{tr 1 a,tr 1 b}
was sich beschwert, dass{tr
es nicht existiert. Wenn Sie zusätzliche Leerzeichen eingeben, wartet es aufgrund des Kommas auf zusätzliche Eingaben. Wenn Sie das Komma in ein Semikolon oder ein kaufmännisches Und ändern, wird nur das erste gedruckt - nicht beide.bash
,ksh
,zsh
) durch die Befehlszeile zu schaffenecho '&& echo 123 |'{'tr 1 a','tr 1 b'} | sed -n 's/^&&//gp'
in einer Zeichenfolge und dann diese Zeichenfolge vorbeieval
. Das heißt, es (a) erstellt 3 Unterschalen beim Erstellen der Zeichenfolge (1 für die`...`
und 2 für die Segmente der eingebetteten Pipeline, und (b), was noch wichtiger ist, dupliziert den Eingabebefehl, so dass eine separate Kopie erstellt wird wird für jeden Zielbefehltr
ausgeführt. Abgesehen von der Ineffizienz führt der zweimalige Ausführen desselben Befehls nicht unbedingt zweimal zur gleichen Ausgabe.