Was ist der Unterschied zwischen <<, <<< und <<in bash?

102

Was ist der Unterschied zwischen <<, <<<und < <in bash?

Searene
quelle
20
Zumindest in diesen Google-Zeiten ist es schwierig, nach diesen symbolbasierten Operatoren zu suchen. Gibt es eine Suchmaschine, in der Sie "<< <<< <<" einstecken und nützliche Informationen erhalten können?
Daniel Griscom
11
@ DanielGriscom Es gibt SymbolHound .
Dennis
1
@DanielGriscom Stack Exchange unterstützt die Suche nach Symbolen, aber dann ist etwas kaputt gegangen, und niemand hat es jemals behoben.
muru
Es ist bereits da (und seit fast einem Jahr): Was sind die Steuerungs- und Umleitungsoperatoren der Shell?
Scott

Antworten:

115

Hier dokumentieren

<<ist als here-documentStruktur bekannt. Sie lassen das Programm wissen, was der Endtext sein wird, und wann immer dieses Trennzeichen angezeigt wird, liest das Programm alle Informationen, die Sie dem Programm als Eingabe gegeben haben, und führt eine Aufgabe darauf aus.

Folgendes meine ich:

$ wc << EOF
> one two three
> four five
> EOF
 2  5 24

In diesem Beispiel weisen wir das wcProgramm an, auf eine EOFZeichenfolge zu warten , dann fünf Wörter EOFeinzugeben und anschließend zu signalisieren, dass die Eingabe abgeschlossen ist. In der Tat ist es ähnlich, als würde man wcvon selbst laufen , Wörter eingeben und dann drückenCtrlD

In der Bash werden diese über temporäre Dateien implementiert, normalerweise in der Form /tmp/sh-thd.<random string>, während sie in Strichen als anonyme Pipes implementiert werden. Dies kann über Tracing-Systemaufrufe mit straceBefehl beobachtet werden . Ersetzen Sie bashdurch, um shzu sehen, wie /bin/shdiese Umleitung ausgeführt wird.

$ strace -e open,dup2,pipe,write -f bash -c 'cat <<EOF
> test
> EOF'

Hier String

<<<ist bekannt als here-string. Anstatt Text einzugeben, geben Sie einem Programm eine vorgefertigte Textfolge. Mit einem solchen Programm, wie bcwir es tun können, bc <<< 5*4um nur die Ausgabe für diesen speziellen Fall zu erhalten, muss bc nicht interaktiv ausgeführt werden.

Hier werden Zeichenfolgen in bash über temporäre Dateien implementiert, in der Regel im Format /tmp/sh-thd.<random string>, die später nicht verknüpft werden. Dadurch belegen sie vorübergehend Speicherplatz, werden jedoch nicht in der Liste der /tmpVerzeichniseinträge angezeigt und existieren effektiv als anonyme Dateien, die möglicherweise noch vorhanden sind Der Dateideskriptor wird von der Shell selbst über den Dateideskriptor referenziert. Dieser Dateideskriptor wird vom Befehl geerbt und später über die dup2()Funktion auf den Dateideskriptor 0 (stdin) dupliziert . Dies kann über beobachtet werden

$ ls -l /proc/self/fd/ <<< "TEST"
total 0
lr-x------ 1 user1 user1 64 Aug 20 13:43 0 -> /tmp/sh-thd.761Lj9 (deleted)
lrwx------ 1 user1 user1 64 Aug 20 13:43 1 -> /dev/pts/4
lrwx------ 1 user1 user1 64 Aug 20 13:43 2 -> /dev/pts/4
lr-x------ 1 user1 user1 64 Aug 20 13:43 3 -> /proc/10068/fd

Und über Tracing-Systemaufrufe (Ausgabe aus Gründen der Lesbarkeit gekürzt; beachten Sie, wie temporäre Dateien als fd 3 geöffnet werden, Daten darauf geschrieben werden, dann mit dem O_RDONLYFlag fd 4 erneut geöffnet werden und später die Verknüpfung dup2()auf fd 0 aufheben, die catspäter vererbt wird ):

$ strace -f -e open,read,write,dup2,unlink,execve bash -c 'cat <<< "TEST"'
execve("/bin/bash", ["bash", "-c", "cat <<< \"TEST\""], [/* 47 vars */]) = 0
...
strace: Process 10229 attached
[pid 10229] open("/tmp/sh-thd.uhpSrD", O_RDWR|O_CREAT|O_EXCL, 0600) = 3
[pid 10229] write(3, "TEST", 4)         = 4
[pid 10229] write(3, "\n", 1)           = 1
[pid 10229] open("/tmp/sh-thd.uhpSrD", O_RDONLY) = 4
[pid 10229] unlink("/tmp/sh-thd.uhpSrD") = 0
[pid 10229] dup2(4, 0)                  = 0
[pid 10229] execve("/bin/cat", ["cat"], [/* 47 vars */]) = 0
...
[pid 10229] read(0, "TEST\n", 131072)   = 5
[pid 10229] write(1, "TEST\n", 5TEST
)       = 5
[pid 10229] read(0, "", 131072)         = 0
[pid 10229] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=10229, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++

Meinung: Da Zeichenfolgen hier möglicherweise temporäre Textdateien verwenden, ist dies der mögliche Grund, warum Zeichenfolgen hier immer eine abschließende neue Zeile einfügen, da die Textdatei nach POSIX-Definition Zeilen haben muss, die mit einem Zeilenumbruch enden.

Prozessersetzung

Wie tldp.org erklärt,

Die Prozessersetzung speist die Ausgabe eines Prozesses (oder von Prozessen) in den Standard eines anderen Prozesses ein.

Tatsächlich ist dies also vergleichbar mit der Weiterleitung von stdout von einem Befehl zum anderen, z echo foobar barfoo | wc. Beachten Sie jedoch : In der bash-Manpage sehen Sie, dass es als gekennzeichnet ist <(list). Grundsätzlich können Sie also die Ausgabe mehrerer (!) Befehle umleiten.

Hinweis: Wenn Sie sagen, dass < <Sie sich nicht auf eine Sache beziehen, sondern auf zwei Umleitungen mit Einzel- <und Prozessumleitung der Ausgabe von <( . . .).

Was passiert nun, wenn wir nur die Substitution durchführen?

$ echo <(echo bar)
/dev/fd/63

Wie Sie sehen, erstellt die Shell einen temporären Dateideskriptor, /dev/fd/63in dem die Ausgabe gespeichert wird (was laut Gilles 'Antwort eine anonyme Pipe ist). Das bedeutet, <dass der Dateideskriptor als Eingabe in einen Befehl umgeleitet wird.

Ein sehr einfaches Beispiel wäre, die Ausgabe von zwei Echo-Befehlen durch einen Prozess in wc zu ersetzen:

$ wc < <(echo bar;echo foo)
      2       2       8

Hier lassen wir die Shell einen Dateideskriptor für alle Ausgaben in Klammern erstellen und leiten diese als Eingabe an. wcWie erwartet empfängt wc diesen Stream von zwei Echo-Befehlen, die für sich genommen zwei Zeilen mit jeweils einem Wort ausgeben. und passend haben wir 2 Wörter, 2 Zeilen und 6 Zeichen plus zwei gezählte neue Zeilen.

Randnotiz : Die Prozessersetzung kann als Bashismus bezeichnet werden (ein Befehl oder eine Struktur, die in fortgeschrittenen Shells verwendet werden kann bash, aber nicht von POSIX angegeben wurde), wurde jedoch kshvor der Bash-Existenz als ksh-Manpage implementiert und diese Antwort schlägt vor. Shells mögen tcshund haben mkshjedoch keine Prozessersetzung. Wie können wir also die Ausgabe mehrerer Befehle in einen anderen Befehl umleiten, ohne den Prozess zu ersetzen? Gruppierung plus Verrohrung!

$ (echo foo;echo bar) | wc
      2       2       8

Tatsächlich ist dies dasselbe wie im obigen Beispiel. Dies unterscheidet sich jedoch unter der Haube von der Prozessersetzung, da wir die gesamte Unterschale und wc die mit dem Rohr verbundene Länge ausfüllen . Auf der anderen Seite bewirkt die Prozessersetzung, dass ein Befehl einen temporären Dateideskriptor liest.

Wenn wir also eine Gruppierung mit Rohrleitungen durchführen können, warum brauchen wir dann eine Prozessersetzung? Weil wir manchmal keine Rohrleitungen verwenden können. Betrachten Sie das folgende Beispiel - Vergleichen der Ausgaben von zwei Befehlen mit diff(für die zwei Dateien erforderlich sind, und in diesem Fall geben wir zwei Dateideskriptoren an).

diff <(ls /bin) <(ls /usr/bin)
Sergiy Kolodyazhnyy
quelle
7
< <wird verwendet, wenn eine Prozessersetzung stdin liefert . Ein solcher Befehl aussehen könnte: cmd1 < <(cmd2). Zum Beispielwc < <(date)
John1024
2
< < ist keine Sache für sich, im Falle einer Prozesssubstitution <folgt lediglich etwas anderes, das gerade beginnt<
immibis
1
@muru Soweit ich weiß, <<<wurde es zuerst von der Unix-Portierung von Plan 9 RC-Shell implementiert und später von zsh, bash und ksh93 übernommen. Ich würde es dann nicht einen Bashismus nennen.
Juli
3
Ein weiteres Beispiel, bei dem Pipes nicht verwendet werden können: echo 'foo' | read; echo ${REPLY}Wird nicht zurückgegeben foo, da sie readin einer Unterschale gestartet werden - Pipes starten eine Unterschale. Wird jedoch read < <(echo 'foo'); echo ${REPLY}korrekt zurückgegeben foo, da keine Unterschale vorhanden ist.
Paddy Landau
26

< < ist ein Syntaxfehler:

$ cat < <
bash: syntax error near unexpected token `<'

< <()ist Prozessersetzung ( <()) kombiniert mit Umleitung ( <):

Ein ausgedachtes Beispiel:

$ wc -l < <(grep ntfs /etc/fstab)
4
$ wc -l <(grep ntfs /etc/fstab)
4 /dev/fd/63

Bei der Prozessersetzung wird der Pfad zum Dateideskriptor wie ein Dateiname verwendet. Wenn Sie einen Dateinamen nicht direkt verwenden möchten (oder können), kombinieren Sie die Prozessersetzung mit der Umleitung.

Klar, es gibt keinen < <Operator.

muru
quelle
Ich bekomme durch deine Antwort, dass <<() nützlicher ist als <(), oder?
Solfish
1
@solfish <()gibt eine dateinamenähnliche Sache an, daher ist es im Allgemeinen nützlich < <(), das Standardverzeichnis dort zu ersetzen, wo es möglicherweise nicht erforderlich ist. In wcgeschieht letzteres mehr nützlich zu sein. Es könnte an anderer Stelle weniger nützlich sein
muru
12

< <Wenn es sich um einen Syntaxfehler handelt, meinen Sie wahrscheinlich, dass command1 < <( command2 )dies eine einfache Eingabeumleitung gefolgt von einer Prozessersetzung ist, die sehr ähnlich, aber nicht äquivalent ist zu:

command2 | command1

Der Unterschied, vorausgesetzt Sie laufen, bashwird command1im zweiten Fall in einer Subshell ausgeführt, während er im ersten Fall in der aktuellen Shell ausgeführt wird. Das bedeutet, dass die eingegebenen Variablen command1bei der Prozessersetzungsvariante nicht verloren gehen.

jlliagre
quelle
11

< <wird einen Syntaxfehler geben. Die bestimmungsgemäße Verwendung ist wie folgt:

Erklären anhand von Beispielen:

Beispiel für < <():

while read line;do
   echo $line
done< <(ls)

Im obigen Beispiel stammt die Eingabe für die while-Schleife von dem lsBefehl, der zeilenweise gelesen und echoin der Schleife bearbeitet werden kann.

<()wird für die Prozesssubstitution verwendet. Weitere Informationen und Beispiele <()finden Sie unter diesem Link:

Prozessersetzung und Rohrleitung

schnüffeln
quelle