Prozessersetzung und Rohrleitung

86

Ich habe mich gefragt, wie ich das Folgende verstehen soll:

Die Standardausgabe eines Befehls in die Standardausgabe eines anderen Befehls zu leiten, ist eine leistungsstarke Technik. Aber was ist, wenn Sie die Standardausgabe mehrerer Befehle weiterleiten müssen? Hier kommt die Prozesssubstitution ins Spiel.

Mit anderen Worten, kann die Prozessersetzung das tun, was die Pipe kann?

Was kann die Prozessersetzung bewirken, die Pipe jedoch nicht?

Tim
quelle

Antworten:

134

Ein guter Weg, um den Unterschied zwischen ihnen herauszufinden, besteht darin, ein wenig in der Befehlszeile zu experimentieren. Trotz der visuellen Ähnlichkeit bei der Verwendung des <Zeichens unterscheidet es sich erheblich von einer Umleitung oder Pipe.

Verwenden wir den dateBefehl zum Testen.

$ date | cat
Thu Jul 21 12:39:18 EEST 2011

Dies ist ein sinnloses Beispiel, aber es zeigt, dass catdie Ausgabe von dateauf STDIN akzeptiert und wieder ausgespuckt wurde. Die gleichen Ergebnisse können durch Prozesssubstitution erzielt werden:

$ cat <(date)
Thu Jul 21 12:40:53 EEST 2011

Was jedoch gerade hinter den Kulissen geschah, war anders. Anstatt einen STDIN-Stream zu erhalten, catwurde tatsächlich der Name einer Datei übergeben, die geöffnet und gelesen werden musste. Sie können diesen Schritt sehen, indem Sie echoanstelle von verwenden cat.

$ echo <(date)
/proc/self/fd/11

Als cat den Dateinamen erhielt, las sie den Inhalt der Datei für uns. Auf der anderen Seite zeigte uns Echo nur den Namen der Datei, dass sie übergeben wurde. Dieser Unterschied wird deutlicher, wenn Sie weitere Substitutionen hinzufügen:

$ cat <(date) <(date) <(date)
Thu Jul 21 12:44:45 EEST 2011
Thu Jul 21 12:44:45 EEST 2011
Thu Jul 21 12:44:45 EEST 2011

$ echo <(date) <(date) <(date)
/proc/self/fd/11 /proc/self/fd/12 /proc/self/fd/13

Es ist möglich, die Prozessersetzung (die eine Datei generiert) und die Eingabeumleitung (die eine Datei mit STDIN verbindet) zu kombinieren:

$ cat < <(date)
Thu Jul 21 12:46:22 EEST 2011

Es sieht ziemlich ähnlich aus, aber dieses Mal wurde cat der STDIN-Stream anstelle eines Dateinamens übergeben. Sie können dies sehen, indem Sie es mit echo versuchen:

$ echo < <(date)
<blank>

Da das Echo STDIN nicht liest und kein Argument übergeben wurde, erhalten wir nichts.

Pipes und Eingabeumleitungen leiten Inhalte in den STDIN-Stream. Die Prozessersetzung führt die Befehle aus, speichert ihre Ausgabe in einer speziellen temporären Datei und übergibt diesen Dateinamen anstelle des Befehls. Bei jedem Befehl, den Sie verwenden, wird er als Dateiname behandelt. Beachten Sie, dass die erstellte Datei keine reguläre Datei ist, sondern eine Named Pipe, die automatisch entfernt wird, sobald sie nicht mehr benötigt wird.

Caleb
quelle
Wenn ich das richtig verstanden habe, sagt tldp.org/LDP/abs/html/process-sub.html#FTN.AEN18244, dass die Prozessersetzung temporäre Dateien erstellt, keine Named Pipes. Soweit mir bekannt ist, erstelle keine temporären Dateien. Beim Schreiben in die Pipe muss niemals auf die Festplatte geschrieben werden: stackoverflow.com/a/6977599/788700
Adobe,
Ich weiß, dass diese Antwort echt ist, da sie das Wort grok verwendet : D
aqn
2
@Adobe Sie können bestätigen , ob die temporäre Datei Prozess Substitution erzeugt ein Named Pipe mit: [[ -p <(date) ]] && echo true. Dies ergibt sich, truewenn ich es mit Bash 4.4 oder 3.2 starte.
De Novo
24

Ich nehme an, Sie sprechen von einer bashoder einer anderen erweiterten Shell, da die posix-Shell keine Prozessersetzung enthält .

bash Man-Page-Berichte:

Prozessersetzung Die
Prozessersetzung wird auf Systemen unterstützt, die Named Pipes (FIFOs) oder die Methode / dev / fd zum Benennen offener Dateien unterstützen. Es hat die Form <(Liste) oder> (Liste). Die Prozessliste wird ausgeführt, wobei die Eingabe oder Ausgabe mit einem FIFO oder einer Datei in / dev / fd verbunden ist. Der Name dieser Datei wird als Ergebnis der Erweiterung als Argument an den aktuellen Befehl übergeben. Wenn das Formular> (Liste) verwendet wird, wird beim Schreiben in die Datei eine Eingabe für die Liste bereitgestellt. Wenn das <(Listen) -Formular verwendet wird, sollte die als Argument übergebene Datei gelesen werden, um die Ausgabe der Liste zu erhalten.

Wenn verfügbar, wird die Prozessersetzung gleichzeitig mit der Parameter- und Variablenerweiterung, der Befehlssubstitution und der arithmetischen Erweiterung durchgeführt.

Mit anderen Worten und aus praktischer Sicht können Sie einen Ausdruck wie den folgenden verwenden

<(commands)

als Dateiname für andere Befehle, für die eine Datei als Parameter erforderlich ist. Oder Sie können die Umleitung für eine solche Datei verwenden:

while read line; do something; done < <(commands)

Zurück zu Ihrer Frage: Prozessersetzung und Pipes haben meines Erachtens nicht viel gemeinsam.

Wenn Sie die Ausgabe mehrerer Befehle nacheinander weiterleiten möchten, können Sie eine der folgenden Formen verwenden:

(command1; command2) | command3
{ command1; command2; } | command3

Sie können aber auch die Umleitung für die Prozessersetzung verwenden

command3 < <(command1; command2)

schließlich, wenn command3ein Dateiparameter akzeptiert wird (anstelle von stdin)

command3 <(command1; command2)
Enzotib
quelle
also machen <() und <<() den gleichen Effekt, oder?
Solfish
@solfish: nicht genau: Der Befehl kann überall dort verwendet werden, wo ein Dateiname erwartet wird. Der zweite
Befehl
23

Hier sind drei Dinge, die Sie mit der Prozessersetzung tun können, die sonst unmöglich sind.

Mehrere Prozesseingänge

diff <(cd /foo/bar/; ls) <(cd /foo/baz; ls)

Mit Rohren geht das einfach nicht.

STDIN erhalten

Angenommen, Sie haben Folgendes:

curl -o - http://example.com/script.sh
   #/bin/bash
   read LINE
   echo "You said ${LINE}!"

Und Sie möchten es direkt ausführen. Das Folgende scheitert kläglich. Bash verwendet bereits STDIN zum Lesen des Skripts, sodass andere Eingaben nicht möglich sind.

curl -o - http://example.com/script.sh | bash 

Aber dieser Weg funktioniert einwandfrei.

bash <(curl -o - http://example.com/script.sh)

Ausgehende Prozessersetzung

Beachten Sie auch, dass die Prozessersetzung auch umgekehrt funktioniert. Sie können also so etwas tun:

(ls /proc/*/exe >/dev/null) 2> >(sed -n \
  '/Permission denied/ s/.*\(\/proc.*\):.*/\1/p' > denied.txt )

Das ist ein verworrenes Beispiel, aber es sendet stdout an /dev/null, während stderr an ein sed-Skript weitergeleitet wird, um die Namen der Dateien zu extrahieren, für die der Fehler "Berechtigung verweigert" angezeigt wurde, und sendet dann DIESE Ergebnisse an eine Datei.

Beachten Sie, dass der erste Befehl und die stdout- Umleitung in Klammern ( Subshell ) angegeben sind, sodass nur das Ergebnis des Befehls THAT an gesendet wird /dev/nullund der Rest der Zeile nicht beeinträchtigt wird.

tylerl
quelle
Es ist erwähnenswert, dass in dem diffBeispiel , das Sie vielleicht über den Fall kümmern wollen , wo die cdfehlschlagen könnten: diff <(cd /foo/bar/ && ls) <(cd /foo/baz && ls).
Phk
"while piping stderr": Ist es nicht der Punkt, dass dies kein Piping ist, sondern eine FIFO-Datei durchläuft?
Gauthier
@ Gauthier nein; Der Befehl wird nicht durch ein FIFO, sondern durch einen Verweis auf den Dateideskriptor ersetzt. "Echo <(echo)" sollte also so etwas wie "/ dev / fd / 63" ergeben, ein Sonderzeichengerät, das von FD Nummer 63 liest oder schreibt.
tylerl
10

Wenn ein Befehl eine Liste von Dateien als Argumente verwendet und diese Dateien als Eingabe (oder Ausgabe, aber nicht allgemein) verarbeitet, kann jede dieser Dateien eine Named Pipe oder eine / dev / fd-Pseudodatei sein, die transparent durch die Prozesssubstitution bereitgestellt wird:

$ sort -m <(command1) <(command2) <(command3)

Dies leitet die Ausgabe der drei Befehle zum Sortieren weiter, da sort eine Liste von Eingabedateien in der Befehlszeile aufnehmen kann.

camh
quelle
1
IIRC Die Syntax <(Befehl) ist eine reine Bash-Funktion.
Philomath
@ Philomath: Es ist auch in ZSH.
Caleb
Nun, ZSH hat alles ... (oder versucht es zumindest).
Philomath
@Philomath: Wie wird die Prozessersetzung in anderen Shells implementiert?
21.07.11
4
@Philomath war <(), wie viele erweiterte Shell-Funktionen, ursprünglich eine ksh-Funktion und wurde von bash und zsh übernommen. psubist speziell ein Fisch-Feature, das nichts mit POSIX zu tun hat.
Gilles
3

Es ist zu beachten, dass die Prozessersetzung nicht auf das Formular beschränkt ist <(command), das die Ausgabe von commandals Datei verwendet. Es kann in der Form vorliegen, in >(command)die eine Datei als Eingabe eingespeist wird command. Dies wird auch im Zitat des Bash-Handbuchs in der Antwort von @enzotib erwähnt.

Für das date | catobige Beispiel wäre ein Befehl, der die Prozessersetzung des Formulars verwendet >(command), um den gleichen Effekt zu erzielen,

date > >(cat)

Beachten Sie, dass das >Vorherige >(cat)erforderlich ist. Dies lässt sich wiederum deutlich anhand echoder Antwort von @ Caleb veranschaulichen .

$ echo >(cat)
/dev/fd/63

Also, ohne die zusätzlichen >, date >(cat)wäre das gleiche wie date /dev/fd/63die eine Nachricht an stderr gedruckt wird.

Angenommen, Sie haben ein Programm, das nur Dateinamen als Parameter verwendet und nicht stdinoder verarbeitet stdout. Ich werde das vereinfachte Skript verwenden psub.sh, um dies zu veranschaulichen. Der Inhalt von psub.shist

#!/bin/bash
[ -e "$1" -a -e "$2" ] && awk '{print $1}' "$1" > "$2"

Grundsätzlich prüft es , dass beide ihre Argumente sind Dateien (nicht unbedingt reguläre Dateien) , und wenn dies der Fall ist, schreiben Sie das erste Feld jeder Zeile "$1"zu "$2"verwenden awk. Dann ist ein Befehl, der alles bisher Erwähnte kombiniert,

./psub.sh <(printf "a a\nc c\nb b") >(sort)

Dies wird gedruckt

a
b
c

und ist äquivalent zu

printf "a a\nc c\nb b" | awk '{print $1}' | sort

aber das Folgende wird nicht funktionieren, und wir müssen hier Prozesssubstitution verwenden,

printf "a a\nc c\nb b" | ./psub.sh | sort

oder seine äquivalente Form

printf "a a\nc c\nb b" | ./psub.sh /dev/stdin /dev/stdout | sort

Wenn außer dem oben Erwähnten ./psub.shauch gelesen stdinwird, gibt es kein entsprechendes Formular, und in diesem Fall können wir anstelle der Prozessersetzung nichts verwenden (natürlich können Sie auch eine Named Pipe- oder temporäre Datei verwenden, aber das ist eine andere Geschichte).

Weijun Zhou
quelle