Warum steht in der Ausgabe von „echo 123 >(cat)“ ein „/dev/fd/63“?

Warum steht in der Ausgabe von „echo 123 >(cat)“ ein „/dev/fd/63“?
$ echo 123 | cat 
123

macht, was ich erwartet habe, beide Befehle werden in derselben Shell ausgeführt.

Aber wenn ich sie mit dem >( ... )Ausdruck verbinde, der die Ausgabe eines Befehls in der Shell mit einem zweiten in einer Subshell verbindet, erhalte ich Folgendes:

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

dies gilt auch für andere Werte:

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

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

Ich dachte , command1 >(command2)es ist dasselbe wie command1 | command2, aber in command1 >(command2)befindet sich jeder Befehl in einer anderen Shell, daher sollten sie dieselbe Ausgabe haben. Wo liege ich falsch?

Antwort1

Die Prozesssubstitution >(thing)wird durch einen Dateinamen ersetzt. Dieser Dateiname entspricht einer Datei, die an die Standardeingabe des thingSubstituenten angeschlossen ist.

Das Folgende wäre ein besseres Anwendungsbeispiel:

$ sort -o >(cat -n >/tmp/out) ~/.profile

Dadurch wird die Datei sortiert ~/.profileund die Ausgabe gesendet, cat -ndie die Zeilen aufzählt und das Ergebnis speichert /tmp/out.

Um also Ihre Frage zu beantworten: Sie erhalten diese Ausgabe, weil echodie beiden Argumente erhalten werden 123und /dev/fd/63. /dev/fd/63die Datei ist, die mit der Standardeingabe des catProzesses bei der Prozessersetzung verbunden ist.

Ändern Sie Ihren Beispielcode leicht:

$ echo 101 > >(cat)

Dies würde nur 101eine Standardausgabe erzeugen (die Ausgabe von echowürde in die Datei umgeleitet, die als Eingabe für dient cat, und catwürde den Inhalt dieser Datei auf der Standardausgabe erzeugen).


Beachten Sie auch, dass in der cmd1 | cmd2Pipeline cmd2möglicherweise überhaupt nicht in derselben Shell ausgeführt wird wie cmd1(abhängig von der von Ihnen verwendeten Shell-Implementierung). ksh93funktioniert wie beschrieben (dieselbe Shell), während basheine Sub-Shell für erstellt wird cmd2(es sei denn, die lastpipeShell-Option ist festgelegt und die Jobsteuerung ist nicht aktiv).

Antwort2

Zur Vollständigkeit

cmd1 >(cmd2)

ist meist das gleiche wie

cmd1 | cmd2

in der yashShell, und nur in dieser Shell.

In dieser Schale >(cmd)ist ProzessUmleitungim Gegensatz zu >(cmd)/ ksh/ bashdas zshist ProzessAuswechslung.

Es ist nicht streng gleichwertig, da in cmd1 >(cmd2)nicht yashauf wartet cmd2. Daher kann es sein, dass Sie Folgendes feststellen:

$ yash -c 'echo A >(cat); echo B'
B
A
$ yash -c 'echo A | cat; echo B'
A
B

Im Gegensatz dazuAuswechslungwird zu einem Dateipfad erweitert (normalerweise eine benannte Pipe oder ein , /dev/fd/<x>wobei <x>es sich um einen FD zu einer zuvor erstellten Pipe handelt), der, wenn er zum Schreiben geöffnet wird, das Senden der Ausgabe an ermöglicht cmd.

Obwohl die Prozesssubstitution von eingeführt wurde ksh, können Sie sie in dieser Shell nicht als Argument an Umleitungen übergeben.

ksh -c 'cmd1 > >(cmd2)'

um yashdie Prozessumleitung zu emulieren, funktioniert nicht. Dort soll man den aus der Ersetzung resultierenden Dateinamen als Argument an einen Befehl wie in übergeben:

ksh -c 'diff <(echo a) <(echo b)'

Es wird in bashund funktionieren zsh.

Bei der Prozessumleitung von bashlike for wartet die Shell jedoch nicht auf den Befehl ( ). Also:yashcmd2

$ bash -c 'echo A > >(cat); echo B'
B
A

kshProzesssubstitution kann emuliert werden mit yash:

cmd1 /dev/fd/5 5>(cmd2)   

Wie:

diff /dev/fd/3 3<(echo a) /dev/fd/4 4<(echo b)

Antwort3

Weildas ist, was Prozesssubstitution bewirkt, lässt es den Befehl innerhalb der Ersetzung als Dateiname erscheinen. Intern verbindet es die Befehle über eine Pipe und gibt den /dev/fd/NNPfad zum Hauptbefehl an, sodass dieser den bereits geöffneten Dateideskriptor für die Pipe öffnen kann.

Es ist nicht dasselbe wie eine Pipe. Pipes stellen eine Verbindung stdouther, stdinohne irgendetwas zu beinhalten, das wie Dateinamen aussieht. Die Prozesssubstitution ist flexibler, da Sie mehrere solcher Substitutionen in einer Befehlszeile haben können, erfordert jedoch den Hauptbefehl, um Dateien nach Namen zu öffnen (eg catstatt echo).

Sie können eine Pipe mit Prozesssubstitution emulieren, indem Sie etwa Folgendes tun:

echo foo > >(cat -n)

Antwort4

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

# We can trace how the commands are executed
# so long as we avoid using shell builtin commands,
# and run the equivalent external program instead, i.e. /usr/bin/echo

$ strace -f -e execve bash -c '/usr/bin/echo >(cat /dev/null)'
execve("/usr/bin/bash", ["bash", "-c", "/usr/bin/echo >(cat /dev/null)"], [/* 56 vars */]) = 0
strace: Process 4213 attached
[pid  4212] execve("/usr/bin/echo", ["/usr/bin/echo", "/dev/fd/63"], [/* 56 vars */]) = 0
strace: Process 4214 attached
[pid  4214] execve("/usr/bin/cat", ["cat", "/dev/null"], [/* 56 vars */]/dev/fd/63
) = 0
[pid  4212] +++ exited with 0 +++
[pid  4214] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=4214, si_uid=1001, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++

# Apparently, the order of evaluation is arranged so this works nicely:

$ echo 1 > >(cat > /dev/null)
$

verwandte Informationen