Jak różnicować dane wyjściowe dwóch poleceń?

165

Wyobraziłem sobie, że najprostszym sposobem na porównanie zawartości dwóch podobnych katalogów byłoby coś takiego

diff `ls old` `ls new`

Ale rozumiem, dlaczego to nie działa; diffotrzymuje w wierszu poleceń dużą długą listę plików, a nie dwa strumienie, jak się spodziewałem. Jak przekazać dwa wyjścia, aby bezpośrednio się różnić?

Potrójny
źródło

Odpowiedzi:

246

Podstawienie polecenia zastępuje `…`dane wyjściowe polecenia w wierszu polecenia, dlatego difflistę plików w obu katalogach traktuje jako argumenty. To, czego chcesz, to diffzobaczyć dwie nazwy plików w wierszu poleceń i mieć zawartość tych plików w postaci list katalogów. Tak właśnie działa proces zastępowania .

diff <(ls old) <(ls new)

Argumenty, które diffbędą wyglądać /dev/fd/3i /dev/fd/4: są deskryptorami plików odpowiadającymi dwóm potokom utworzonym przez bash. Po diffotwarciu tych plików zostanie on podłączony do strony odczytu każdej potoku. Strona zapisu każdej potoku jest połączona z lspoleceniem.

Gilles
źródło
49
echo <(echo) <(echo)nigdy nie myślałem, że to może być tak interesujące: D
Wodnik Moc
3
Zastępowanie procesów nie jest obsługiwane przez wszystkie powłoki , ale przekierowania potoków są dobrym rozwiązaniem .
Irfan434,
1
Wystarczy wspomnieć, że podczas analizowania ls nie jest zalecane unix.stackexchange.com/questions/128985/why-not-parse-ls
katu
@Katu Problem lspolega na tym, że zmienia nazwy plików. Analiza wyników jest krucha (nie działa z „dziwnymi” nazwami plików). Porównywanie dwóch list katalogów jest w porządku, o ile dane wyjściowe są jednoznaczne. W przypadku dowolnych nazw plików wymagałoby to opcji takiej jak --quoting-style=escape.
Gilles
1
@will <(…)utworzy potok. Wygląda na to, że meld nie działa z rurami, więc nie możesz go użyć <(…). W zsh możesz zamienić <(…)na =(…)i to zadziała, ponieważ =(…)umieszcza wyniki pośrednie w pliku tymczasowym. W bash nie wydaje mi się, żeby istniała jakaś wygodna składnia, sam musiałbyś zarządzać plikami tymczasowymi.
Gilles
3

W przypadku zsh użycie =(command)automatycznie tworzy plik tymczasowy i zastępuje =(command)ścieżką samego pliku. Z Zastępowanie polecenia $(command)jest zastępowane wyjściem polecenia.

Istnieją więc trzy opcje:

  1. Zmiana polecenia: $(...)
  2. Zastąpienie procesu: <(...)
  3. Zastąpienie procesu o smaku zsh: =(...)

Subskrypcja procesu o smaku zsh # 3 jest bardzo przydatna i może być używana do porównywania wyników dwóch poleceń za pomocą narzędzia różnicowego, na przykład Beyond Compare:

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

W przypadku Beyond Compare zwróć uwagę, że musisz użyć bcomppowyższego (zamiast bcompare), ponieważ bcompuruchamia porównanie i czeka na jego zakończenie. Jeśli używasz bcompare, uruchamia to porównanie i natychmiast kończy pracę, dzięki czemu tymczasowe pliki utworzone w celu przechowywania danych wyjściowych poleceń znikają.

Przeczytaj więcej tutaj: http://zsh.sourceforge.net/Intro/intro_7.html

Zauważ też:

Zauważ, że powłoka tworzy plik tymczasowy i usuwa go po zakończeniu polecenia.

oraz następujące, co stanowi różnicę między dwoma typami podstawienia procesu obsługiwanego przez zsh (tj. # 2 i # 3):

Jeśli czytasz stronę podręcznika zsh, możesz zauważyć, że <(...) jest inną formą podstawienia procesu podobną do = (...). Istnieje ważna różnica między nimi. W przypadku <(...) powłoka tworzy nazwany potok (FIFO) zamiast pliku. Jest to lepsze, ponieważ nie wypełnia systemu plików; ale nie działa we wszystkich przypadkach. W rzeczywistości, gdybyśmy zastąpili = (...) w <(...) w powyższych przykładach, wszystkie z nich przestałyby działać, z wyjątkiem fgrep -f <(...). Nie można edytować potoku ani otwierać go jako folderu poczty; fgrep nie ma jednak problemu z odczytaniem listy słów z potoku. Możesz się zastanawiać, dlaczego pasek różnic <(foo) nie działa, skoro foo | diff - działa na pasku; dzieje się tak, ponieważ diff tworzy plik tymczasowy, jeśli zauważy, że jednym z jego argumentów jest -, a następnie kopiuje standardowe dane wejściowe do pliku tymczasowego.

Odniesienie: https://unix.stackexchange.com/questions/393349/difference-between-subshells-and-process-substitution

Ashutosh Jindal
źródło
2
$(...)nie jest podstawieniem procesu, jest podstawieniem polecenia . <(...)jest podstawieniem procesu. Dlatego cytowany fragment w ogóle nie wspomina $(...).
muru
2

Skorupa Rybna

W skorupce ryby musisz potokować do psub . Oto przykład porównania konfiguracji heroku i dokku z Beyond Compare :

bcompare (ssh [email protected] dokku config myapp | sort | psub) (heroku config -a myapp | sort | psub)
WooYek
źródło
1
Innym graficznym narzędziem melddo porównywania jest oprogramowanie open source i dostępne w repozytoriach Ubuntu i EPEL. meldmerge.org
phiphi
0

Często używam techniki opisanej w zaakceptowanej odpowiedzi:

diff <(ls old) <(ls new)

ale zwykle używam go z dużo bardziej złożonymi poleceniami niż w powyższym przykładzie. W takich przypadkach ułożenie komendy diff może być denerwujące. Wymyśliłem kilka rozwiązań, które inni mogą uznać za przydatne.

Uważam, że w 99% przypadków wypróbowuję odpowiednie polecenia przed uruchomieniem mechanizmu różnicowego. W związku z tym polecenia, które chcę udostępnić, są właśnie w mojej historii ... dlaczego ich nie użyć?

Korzystam z wbudowanego bash Fix Command (fc) do wykonania dwóch ostatnich poleceń:

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

Flagi FC to:

-n : brak numeru. Pomija numery poleceń podczas listowania.

-l : Listing: Polecenia są wymienione na standardowym wyjściu.

-1 -1odnoszą się do rozpoczęcia i zakończenia positing w historii, w tym przypadku ITS od ostatniego polecenia do ostatniego polecenia co daje tylko ostatnie polecenie.

Na koniec zamykamy to, $()aby wykonać polecenie w podpowłoce.

Oczywiście jest to trochę kłopotliwe w pisaniu, abyśmy mogli stworzyć alias:

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

Lub możemy stworzyć funkcję:

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") )
}

który obsługuje określenie linii historii do użycia. Po użyciu obu znajduję alias, który preferuję.

htaccess
źródło