Wie können Sie zwei Pipelines in Bash unterscheiden?

143

Wie können Sie diff zwei Pipelines ohne Verwendung von temporären Dateien in Bash? Angenommen, Sie haben zwei Befehlspipelines:

foo | bar
baz | quux

Und Sie möchten die diffin ihren Ausgaben finden. Eine Lösung wäre natürlich:

foo | bar > /tmp/a
baz | quux > /tmp/b
diff /tmp/a /tmp/b

Ist dies ohne die Verwendung temporärer Dateien in Bash möglich? Sie können eine temporäre Datei entfernen, indem Sie eine der Pipelines an diff weiterleiten:

foo | bar > /tmp/a
baz | quux | diff /tmp/a -

Sie können jedoch nicht beide Pipelines gleichzeitig in diff leiten (zumindest nicht auf offensichtliche Weise). Gibt es einen cleveren Trick, /dev/fdum dies zu tun, ohne temporäre Dateien zu verwenden?

Adam Rosenfield
quelle

Antworten:

146

Eine einzeilige Datei mit 2 tmp-Dateien (nicht das, was Sie wollen) wäre:

 foo | bar > file1.txt && baz | quux > file2.txt && diff file1.txt file2.txt

Mit bash könnten Sie jedoch versuchen:

 diff <(foo | bar) <(baz | quux)

 foo | bar | diff - <(baz | quux)  # or only use process substitution once

Die 2. Version wird Sie deutlicher daran erinnern, welche Eingabe welche war, indem Sie
-- /dev/stdinvs. ++ /dev/fd/63oder etwas anstelle von zwei nummerierten fds anzeigen.


Nicht einmal eine Named Pipe wird im Dateisystem angezeigt, zumindest unter Betriebssystemen, in denen bash die Prozessersetzung mithilfe von Dateinamen implementieren kann /dev/fd/63, um einen Dateinamen zu erhalten, den der Befehl öffnen und lesen kann, um tatsächlich aus einem bereits geöffneten Dateideskriptor zu lesen, den bash festgelegt hat vor dem Ausführen des Befehls. (dh bash verwendet pipe(2)vor fork und dup2leitet dann von der Ausgabe von quuxzu einem Eingabedateideskriptor für umdiff auf fd 63 um.)

Auf einem System ohne "magisch" /dev/fdoder/proc/self/fd bash bash möglicherweise Named Pipes, um die Prozessersetzung zu implementieren, verwaltet sie jedoch im Gegensatz zu temporären Dateien zumindest selbst, und Ihre Daten werden nicht in das Dateisystem geschrieben.

Sie können überprüfen, wie bash die Prozessersetzung implementiert echo <(true), um den Dateinamen zu drucken, anstatt daraus zu lesen. Es wird /dev/fd/63auf einem typischen Linux-System gedruckt . Oder um weitere Informationen darüber zu erhalten, welche Systemaufrufe bash verwendet, verfolgt dieser Befehl auf einem Linux-System Datei- und Dateideskriptorsystemaufrufe

strace -f -efile,desc,clone,execve bash -c '/bin/true | diff -u - <(/bin/true)'

Ohne Bash könnten Sie eine Named Pipe erstellen . Verwenden Sie -diese diffOption, um eine Eingabe von STDIN zu lesen und die Named Pipe als die andere zu verwenden:

mkfifo file1_pipe.txt
foo|bar > file1_pipe.txt && baz | quux | diff file1_pipe.txt - && rm file1_pipe.txt

Beachten Sie, dass Sie mit dem Befehl tee nur einen Ausgang an mehrere Eingänge weiterleiten können:

ls *.txt | tee /dev/tty txtlist.txt 

Der obige Befehl zeigt die Ausgabe von ls * .txt an das Terminal an und gibt sie an die Textdatei txtlist.txt aus.

Mit der Prozessersetzung können Sie jedoch teedieselben Daten in mehrere Pipelines einspeisen:

cat *.txt | tee >(foo | bar > result1.txt)  >(baz | quux > result2.txt) | foobar
VonC
quelle
5
Auch ohne Bash können Sie temporäre Fifos verwendenmkfifo a; cmd >a& cmd2|diff a -; rm a
Unhammer
Sie können eine reguläre Pipe für eines der Argumente verwenden : pipeline1 | diff -u - <(pipeline2). Dann wird die Ausgabe Sie deutlicher daran erinnern, welche Eingabe welche war, indem Sie -- /dev/stdinvs. ++ /dev/fd/67oder etwas anstelle von zwei nummerierten fds anzeigen.
Peter Cordes
process substitution ( foo <( pipe )) ändert das Dateisystem nicht. Die Pipe ist anonym ; Es hat keinen Namen im Dateisystem . Die Shell verwendet den pipeSystemaufruf, um ihn zu erstellen, nicht mkfifo. Verwenden Sie strace -f -efile,desc,clone,execve bash -c '/bin/true | diff -u - <(/bin/true)'diese Option , um Datei- und Dateideskriptorsystemaufrufe zu verfolgen, wenn Sie sich selbst davon überzeugen möchten. Ist unter Linux /dev/fd/63Teil des /procvirtuellen Dateisystems. Es enthält automatisch Einträge für jeden Dateideskriptor und ist keine Kopie des Inhalts. Sie können das also nicht als "temporäre Datei" bezeichnen, es sei denn, es foo 3<bar.txtzählt
Peter Cordes
@ PeterCordes Gute Punkte. Ich habe Ihren Kommentar zur besseren Sichtbarkeit in die Antwort aufgenommen.
VonC
1
@PeterCordes Ich überlasse Ihnen jede Bearbeitung: Das macht Stack Overflow interessant: Jeder kann eine Antwort "reparieren".
VonC
127

In bash können Sie Subshells verwenden, um die Befehlspipelines einzeln auszuführen, indem Sie die Pipeline in Klammern setzen. Sie können diesen dann <voranstellen, um anonyme Named Pipes zu erstellen, die Sie dann an diff übergeben können.

Beispielsweise:

diff <(foo | bar) <(baz | quux)

Die anonymen Named Pipes werden von bash verwaltet, sodass sie automatisch erstellt und zerstört werden (im Gegensatz zu temporären Dateien).

BenM
quelle
1
Viel detaillierter als meine Redaktion über dieselbe Lösung - anonymer Stapel -. +1
VonC
4
Dies wird in Bash als Prozesssubstitution bezeichnet .
Franklin Yu
5

Einige Personen, die auf dieser Seite ankommen, suchen möglicherweise nach einem zeilenweisen Unterschied, für den stattdessen ein Verwendungszweck verwendet werden sollte commoder grep -fsollte.

Eine Sache, die hervorgehoben werden muss, ist, dass in allen Beispielen der Antwort die Unterschiede erst dann beginnen, wenn beide Streams beendet sind. Testen Sie dies mit zB:

comm -23 <(seq 100 | sort) <(seq 10 20 && sleep 5 && seq 20 30 | sort)

Wenn dies ein Problem ist, können Sie es versuchen sd (stream diff), das weder sortiert (wie es commtut) noch eine Prozessersetzung wie in den obigen Beispielen erfordert , um Größenordnungen oder Größenordnungen schneller alsgrep -f und unterstützt unendliche Streams.

Das von mir vorgeschlagene Testbeispiel würde so geschrieben sein sd :

seq 100 | sd 'seq 10 20 && sleep 5 && seq 20 30'

Aber der Unterschied ist das seq 100 man sich sofort würde seq 10. Beachten Sie, dass, wenn einer der Streams a isttail -f , der Diff nicht durch Prozessersetzung durchgeführt werden kann.

Hier ist ein Blogpost, den ich über unterschiedliche Streams auf dem Terminal geschrieben habe sd.

mlg
quelle