Diese Frage wurde durch " Reverse Grepping " motiviert , bei dem es darum ging, eine riesige Datei von unten nach oben zu erfassen.
tac file | grep whatever
Oder etwas effektiver:
grep whatever < <(tac file)
Das
< <(tac filename)
sollte so schnell wie ein Rohr sein
Es gibt auch viele interessante Kommentare von anderen Benutzern.
Meine Fragen:
- Was ist der Unterschied zwischen
|
und< <()
? - Warum ist einer schneller als der andere?
- Und was ist wirklich schneller?
- Warum hat niemand vorgeschlagen
xargs
?
bash
grep
performance
efficiency
c0rp
quelle
quelle
< <(...)
für0< <(...)
nicht steht<<(...)
.Antworten:
Die Konstruktion
<(tac file)
bewirkt, dass:/dev/fd
wird eine reguläre Pipe verwendet,/dev/fd/<the-file-descriptor-of-the-pipe>
die als Name verwendet wird.tac file
und verbinden Sie ihn mit einem Ende der Pipe.Nach dem Ersetzen wird die Befehlszeile wie folgt:
Und dann
grep
wird es ausgeführt, und es liest seine Standardeingabe (die die Pipe ist), liest es und sucht darin nach seinem ersten Argument.Das Endergebnis ist also das gleiche wie bei ...
... dass dieselben zwei Programme gestartet werden und immer noch eine Pipe verwendet wird, um sie zu verbinden. Die
<( ... )
Konstruktion ist jedoch komplizierter, da sie mehr Schritte umfasst und möglicherweise eine temporäre Datei (die Named Pipe) enthält.Das
<( ... )
Konstrukt ist eine Erweiterung und weder in der Standard-POSIX-Bourne-Shell noch auf Plattformen verfügbar, die keine/dev/fd
Pipes unterstützen oder benennen. Allein aus diesem Grund ist die tragbarerecommand | other-command
Form die bessere Wahl , da die beiden in Betracht gezogenen Alternativen in ihrer Funktionalität genau gleichwertig sind .Die
<( ... )
Konstruktion sollte wegen der zusätzlichen Faltung langsamer sein, aber es ist nur in der Startphase und ich erwarte nicht, dass der Unterschied leicht messbar ist.ANMERKUNG : Verwendet auf Linux SysV-Plattformen
< ( ... )
keine Named Pipes, sondern reguläre Pipes. Auf reguläre Pipes (in der Tat alle Dateideskriptoren) kann mit dem speziellen Namen verwiesen werden,/dev/fd/<file-descriptor-number
sodass die Shell diesen Namen für die Pipe verwendet. Auf diese Weise wird vermieden, dass eine echte Named Pipe mit einem echten temporären Dateinamen im realen Dateisystem erstellt wird. Obwohl der/dev/fd
Trick verwendet wurde, um diese Funktion zu implementieren, als sie ursprünglich angezeigt wurdeksh
, handelt es sich um eine Optimierung: Auf Plattformen, die dies nicht unterstützen, wird wie oben beschrieben eine reguläre Named Pipe im realen Dateisystem verwendet.AUCH HINWEIS : Um die Syntax als
<<( ... )
irreführend zu beschreiben . Tatsächlich ist es das<( ... )
, was durch den Namen einer Pipe ersetzt wird, und dann ist das andere<
Zeichen, das dem Ganzen vorangestellt ist, von dieser Syntax getrennt und es ist die reguläre bekannte Syntax zum Umleiten von Eingaben aus einer Datei.quelle
bash
undzsh
Unterstützung für Named Pipes für Systeme hinzugefügt, denen später / dev / fd / n fehlte.ksh
Funktion, keine Standard-POSIX-Funktion! Sicher genug,dash
scheint es nicht zu unterstützen. Dies ist ein weiterer Grund, dies zu vermeiden, wenn eine gleichwertige Alternative mit grundlegenden POSIX-Funktionen verfügbar ist.< <(command)
in Bezug auf das Rohr vorzuziehen ist|
. Wie von gnouc erläutert,|
muss jeder Befehl in einer Subshell ausgeführt werden, während die Prozessersetzung die Pipe offen lässt und den Befehl von der Standardausgabe speist. Es gibt mehr hier: wiki.bash-hackers.org/syntax/expansion/proc_subst und mywiki.wooledge.org/ProcessSubstitutioncommand | command
Rohrkonstruktion ist so effizient wie möglich. Die Shellfork()
erstellt jeden neuen Prozess in einer Pipeline. wird dannfork()
aber universell verwendet, um neue Prozesse zu erstellen - das wird nicht als Subshell betrachtet.Es gibt einen Unterschied zwischen ihnen:
|
Jeder Befehl wird in einer separaten Unterschale ausgeführt.<()
Führen Sie den Befehl aus, der im Hintergrund ersetzt wird.Für die nächsten beiden Fragen werden wir einige tun
strace
:pipe
::Process Substitution
::Sie sehen,
process substitution
ist langsamer alspipe
in diesem Fall, weil es mehr Systemaufruf verwendet. Beide verbringen viel Zeit damit, auf untergeordnete Prozesse zu warten, verwenden jedochprocess substitution
mehrwait4()
Systemaufruf und mehr Zeit für jeden Anruf alspipe
.Ich denke, hier
xargs
kann nichts helfen, das ist nicht seine Aufgabe.Aktualisieren
Wie von @ Gilles vorgeschlagen, mache ich einen Test mit einer größeren Datei, aus der 2 GB zufällige Daten generiert wurden
/dev/urandom
. Es zeigt, dasspipe
das wirklich schneller ist alsprocess substitution
.pipe
::process substitution
::quelle
/dev/fd/n
keine Named Pipes verwendet (obwohl unter Linux und nur Linux / dev / fd / n sich wie Named Pipes verhalten, wenn n ein Dateideskriptor für eine Pipe ist (benannt oder nicht)). Auf Systemen, die / dev / fd / n nicht unterstützen, verwenden einige Shells Named Pipes.ls -l <(:)
um sicherzustellen, dass keine Named Pipes verwendet werden.mkfifo
wie im Wikipedia-Artikel beschrieben erstellt. Eine Named Pipe ist einer dieser "speziellen" Dateitypen, die manchmal im Dateisystem angezeigt werden, z. B. UNIX-Domänensockets, Zeichengeräte und Blockgeräte.Ich konnte die von cuonglm gezeigten Ergebnisse nicht replizieren . Selbst mit einer 2-GB-Datei sehe ich in Bash 5 unter MacOS Mojave sehr ähnliche Zeitpunkte zwischen Prozessersetzung und Pipe. Dies ist für mich sinnvoll, da der mit einem Aufruf verbundene Overhead im Vergleich zur tatsächlichen Verarbeitung dieses Aufrufs für eine 2-GB-Datei minimal sein wird. Das Ausführen einer Iteration der Verwendung der Prozessersetzung gegenüber einer Pipe hängt also von der Zufälligkeit ab / welcher Befehl war Führen Sie zuerst aus, um den Dateiinhalt zwischenzuspeichern.
Ich war der Lage , die Ergebnisse zu replizieren in dieser Frage , die zeigen , dass Prozess Substitution schneller als Rohre über mehrere tausend Instanzen dieser Anrufe.
Hier sind die Befehle, die ich ausgeführt habe, und die Ausgabe:
pipe.sh :
proc-sh :
pipe-no-lastpipe.sh :
Test :
quelle