Wie kann ich die Ausgabe von zwei Befehlen unterscheiden?

165

Ich hatte mir vorgestellt, dass der einfachste Weg, den Inhalt zweier ähnlicher Verzeichnisse zu vergleichen, so etwas wie wäre

diff `ls old` `ls new`

Aber ich verstehe, warum das nicht funktioniert. diffStatt zwei Streams, wie ich es mir erhofft hatte, wird eine große, lange Liste von Dateien auf der Kommandozeile ausgegeben. Wie übergebe ich die beiden Ausgänge direkt an diff?

Ternary
quelle

Antworten:

246

Substitution Befehl `…`ersetzt die Ausgabe des Befehls in die Kommandozeile, so dass diffdie Liste der Dateien in beiden Verzeichnissen als Argument sieht. Sie möchten, dass diffin der Befehlszeile zwei Dateinamen angezeigt werden und der Inhalt dieser Dateien die Verzeichnislisten sind. Das ist, was die Prozesssubstitution tut.

diff <(ls old) <(ls new)

Die Argumente, die diffaussehen wird /dev/fd/3und /dev/fd/4: sie sind entsprechende Datei - Deskriptoren zu zwei von bash erstellt Rohren. Wenn diffdiese Dateien geöffnet werden, wird sie mit der Leseseite jeder Pipe verbunden. Die Schreibseite jeder Pipe ist mit dem lsBefehl verbunden.

Gilles
quelle
49
echo <(echo) <(echo)hätte nie gedacht, dass dies so interessant sein könnte: D
Aquarius Power
3
Die Prozessersetzung wird nicht von allen Shells unterstützt , aber Pipe-Weiterleitungen sind eine gute Lösung .
Irfan434
1
Nur um zu erwähnen, dass das Parsen von ls nicht empfohlen wird. Unix.stackexchange.com/questions/128985/why-not-parse-ls
Katu
@Katu Das Problem mit lsist, dass es Dateinamen entstellt . Das Parsen der Ausgabe ist fragil (es funktioniert nicht mit „seltsamen“ Dateinamen). Für den Vergleich zweier Verzeichnislisten ist es in Ordnung, solange die Ausgabe eindeutig ist. Bei beliebigen Dateinamen würde dies eine Option erfordern, wie z --quoting-style=escape.
Gilles
1
@will <(…)erzeugt eine Pipe. Es scheint, dass die Verbindung mit Rohren nicht funktioniert, Sie können sie also nicht verwenden <(…). In zsh können Sie ersetzen <(…)durch =(…)und es wird funktionieren, weil =(…)die Zwischenausgaben in einer temporären Datei abgelegt werden. In Bash glaube ich nicht, dass es eine bequeme Syntax gibt, Sie müssten die temporären Dateien selbst verwalten.
Gilles
3

Bei Verwendung von zsh wird =(command)automatisch eine temporäre Datei erstellt und =(command)durch den Pfad der Datei selbst ersetzt. Bei der Befehlsersetzung $(command)wird diese durch die Ausgabe des Befehls ersetzt.

Es gibt also drei Möglichkeiten:

  1. Befehlsersetzung: $(...)
  2. Prozessersetzung: <(...)
  3. Prozesssubstitution mit zsh-Geschmack: =(...)

zsh Flavoured Process Subsitution, # 3, ist sehr nützlich und kann verwendet werden, um die Ausgabe von zwei Befehlen mit einem Diff-Tool zu vergleichen, zum Beispiel Beyond Compare:

bcomp  =(ulimit -Sa | sort) =(ulimit -Ha | sort)

Beachten Sie, dass Sie für Beyond Compare bcomp(anstelle von bcompare) verwenden müssen, da bcompder Vergleich gestartet und abgewartet wird , bis er abgeschlossen ist. Wenn Sie verwenden bcompare, wird der Vergleich gestartet und sofort beendet, wodurch die temporären Dateien, die zum Speichern der Ausgabe der Befehle erstellt wurden, verschwinden.

Lesen Sie hier mehr: http://zsh.sourceforge.net/Intro/intro_7.html

Beachten Sie auch Folgendes:

Beachten Sie, dass die Shell eine temporäre Datei erstellt und diese nach Beendigung des Befehls löscht.

und das Folgende ist der Unterschied zwischen den beiden Arten der von zsh unterstützten Prozessersetzung (dh # 2 und # 3):

Wenn Sie die Manpage von zsh lesen, stellen Sie möglicherweise fest, dass <(...) eine andere Form der Prozessersetzung ist, die = (...) ähnelt. Es gibt einen wichtigen Unterschied zwischen den beiden. Im Fall <(...) erstellt die Shell eine Named Pipe (FIFO) anstelle einer Datei. Dies ist besser, da es das Dateisystem nicht ausfüllt. aber es funktioniert nicht in allen Fällen. Wenn wir in den obigen Beispielen = (...) durch <(...) ersetzt hätten, hätten alle außer fgrep -f <(...) aufgehört zu funktionieren. Sie können eine Pipe nicht bearbeiten oder als E-Mail-Ordner öffnen. fgrep hat jedoch kein Problem damit, eine Liste von Wörtern aus einer Pipe zu lesen. Sie fragen sich vielleicht, warum die diff <(foo) -Leiste nicht funktioniert, da foo | Diff - Bar funktioniert; Dies liegt daran, dass diff eine temporäre Datei erstellt, wenn es feststellt, dass eines seiner Argumente - ist, und dann seine Standardeingabe in die temporäre Datei kopiert.

Referenz: https://unix.stackexchange.com/questions/393349/differenz zwischen- subshells-and- process-substitution

Ashutosh Jindal
quelle
2
$(...)ist keine Prozessersetzung, sondern eine Befehlssetzung . <(...)ist Prozesssubstitution. Deshalb wird in der zitierten Passage überhaupt nicht erwähnt $(...).
muru
2

Fischmuschel

In Fish Shell musst du in psub pipen . Hier ist ein Beispiel eines Heroku- und Dokku-Konfigurationsvergleichs mit Beyond Compare :

bcompare (ssh [email protected] dokku config myapp | sort | psub) (heroku config -a myapp | sort | psub)
WooYek
quelle
1
Ein weiteres grafisches Diff-Tool ist meldOpen Source und in den Repositories von Ubuntu und EPEL verfügbar. meldmerge.org
phiphi
0

Ich verwende oft die in der akzeptierten Antwort beschriebene Technik:

diff <(ls old) <(ls new)

Aber ich finde, ich benutze es normalerweise mit viel komplexeren Befehlen als im obigen Beispiel. In solchen Fällen kann es ärgerlich sein, den Befehl diff zu erstellen. Ich habe einige Lösungen gefunden, die für andere nützlich sein könnten.

In 99% der Fälle probiere ich die entsprechenden Befehle aus, bevor ich diff ausführe. Folglich sind die Befehle, die ich unterscheiden möchte, genau dort in meinem Verlauf ... warum nicht sie verwenden?

Ich benutze den eingebauten Fix Command (fc) -Bash, um die letzten beiden Befehle auszuführen:

$ echo A
A
$ echo B
B
$ diff --color <( $(fc -ln -1 -1) ) <( $(fc -ln -2 -2 ) )
1c1
< B
---
> A

Die fc Flags sind:

-n : Keine Nummer. Es unterdrückt die Befehlsnummern bei der Auflistung.

-l : Auflistung: Die Befehle werden in der Standardausgabe aufgelistet.

Sie -1 -1beziehen sich auf die Start- und Endposition in der Historie, in diesem Fall vom letzten Befehl bis zum letzten Befehl, der nur den letzten Befehl liefert.

Zuletzt schließen wir dies ein $(), um den Befehl in einer Subshell auszuführen.

Offensichtlich ist es etwas mühsam, dies einzugeben, damit wir einen Alias ​​erstellen können:

alias dl='diff --color <( $(fc -ln -1 -1) ) <( $(fc -ln -2 -2 ) )'

Oder wir können eine Funktion erstellen:

dl() {
    if [[ -z "$1" ]]; then
        first="1"
    else
        first="$1"
    fi
    if [[ -z "$2" ]]; then
        last="2"
    else
        last="$2"
    fi
    # shellcheck disable=SC2091
    diff --color <( $(fc -ln "-$first" "-$first") ) <( $(fc -ln "-$last" "-$last") )
}

Dies unterstützt die Angabe der zu verwendenden Verlaufszeilen. Nach der Verwendung von beiden finde ich, dass der Alias ​​die Version ist, die ich bevorzuge.

htaccess
quelle