Porównaj dwa pliki wiersz po wierszu i wygeneruj różnicę w innym pliku

121

Chcę porównać plik1 z plikiem2 i wygenerować plik3, który zawiera wiersze w pliku1, których nie ma w pliku2.

Balualways
źródło
Próbowałem diff, ale generuje kilka liczb i innych symboli przed różnymi wierszami, co utrudnia mi porównywanie plików.
niedziela,

Odpowiedzi:

216

diff (1) nie jest odpowiedzią, ale comm (1) jest.

NAME
       comm - compare two sorted files line by line

SYNOPSIS
       comm [OPTION]... FILE1 FILE2

...

       -1     suppress lines unique to FILE1

       -2     suppress lines unique to FILE2

       -3     suppress lines that appear in both files

Więc

comm -2 -3 file1 file2 > file3

Pliki wejściowe muszą zostać posortowane. Jeśli tak nie jest, najpierw je posortuj. Można to zrobić za pomocą pliku tymczasowego lub ...

comm -2 -3 <(sort file1) <(sort file2) > file3

pod warunkiem, że twoja powłoka obsługuje podstawianie procesów (bash tak).

sorpigal
źródło
1
Pamiętaj, że dwa pliki muszą być posortowane i są unikalne
andy
6
Możesz pogrupować opcje razem:comm -23
Paolo M
Co oznacza „posortowane”? Że linie mają tę samą kolejność? Wtedy prawdopodobnie jest w porządku w większości przypadków użycia - tak jak w przypadku sprawdzania, które linie zostały dodane, porównując ze starszą wersją z kopią zapasową. Jeśli nowo dodane linie nie mogą znajdować się między istniejącymi liniami, jest to większy problem.
Egor Hans
@EgorHans: jeśli plik zawiera np. Wiersze zawierające liczby całkowite, takie jak „3 \ n1 \ n3 \ n2 \ n”, to wiersze należy najpierw uporządkować w kolejności rosnącej lub malejącej, np. „\ 1 \ n2 \ n3 \ n3 \ n” z duplikatami sąsiadujący. To jest „posortowane” i oba pliki muszą być posortowane w podobny sposób. Gdy nowszy plik ma nowe wiersze, nie ma znaczenia, czy znajdują się one „między istniejącymi wierszami”, ponieważ po sortowaniu ich nie ma, są w kolejności posortowanej.
sorpigal
48

Narzędzie Unix diffjest przeznaczone dokładnie do tego celu.

$ diff -u file1 file2 > file3

Więcej informacji na temat opcji, różnych formatów wyjściowych itp. Można znaleźć w instrukcji i w Internecie.

Thanatos
źródło
8
To nie spełnia żądanej pracy; wstawia kilka dodatkowych znaków, nawet przy użyciu przełączników wiersza poleceń sugerowanych w innych odpowiedziach.
Xenocyon
20

Rozważ to:
plik a.txt:

abcd
efgh

plik b.txt:

abcd

Możesz znaleźć różnicę w:

diff -a --suppress-common-lines -y a.txt b.txt

Wynik będzie:

efgh 

Możesz przekreślić wynik w pliku wyjściowym (c.txt) za pomocą:

diff -a --suppress-common-lines -y a.txt b.txt > c.txt

To odpowie na twoje pytanie:

„... który zawiera linie w pliku1, których nie ma w pliku2”.

Neilvert Noval
źródło
2
Istnieją dwa ograniczenia tej odpowiedzi: (1) działa tylko dla krótkich linii (domyślnie mniej niż 80 znaków, chociaż można to zmienić) i, co ważniejsze, (2) dodaje „<” na końcu każdego linia, która musi zostać usunięta przez inny program (np. awk, sed).
sergut
W wielu przypadkach będziesz również chciał użyć -d, który postara się diffznaleźć najmniejszą możliwą różnicę. -i, -E, -w, -BI --suppress-blank-emptymoże być także przydatny czasami, choć nie zawsze. Jeśli nie wiesz, co pasuje do twojego przypadku użycia, spróbuj diff --helpnajpierw (co jest ogólnie dobrym pomysłem, gdy nie wiesz, co może zrobić polecenie).
Egor Hans
Ponadto, używając --line-format =% L, powstrzymujesz diff przed generowaniem jakichkolwiek dodatkowych znaków (przynajmniej pomoc mówi, że działa w ten sposób, ale zaraz go wypróbujesz).
Egor Hans
Również jest krótszy i wydaje się, że działa ten sam stackoverflow.com/a/27667185/1179925
mrgloom
8

Czasami diffjest to narzędzie, którego potrzebujesz, ale czasami joinjest bardziej odpowiednie. Pliki muszą być wstępnie posortowane lub, jeśli używasz powłoki obsługującej podstawianie procesów, takiej jak bash, ksh lub zsh, możesz sortować w locie.

join -v 1 <(sort file1) <(sort file2)
Wstrzymano do odwołania.
źródło
Powinieneś dostać za to medal! To było dokładnie to, czego szukałem przez ostatnie 2 godziny
Zatarra
7

Próbować

sdiff file1 file2

Zwykle działa znacznie lepiej w większości przypadków. Możesz chcieć posortować pliki wcześniej, jeśli kolejność linii nie jest ważna (np. Niektóre tekstowe pliki konfiguracyjne).

Na przykład,

sdiff -w 185 file1.cfg file2.cfg
Tagar
źródło
1
Niezłe narzędzie! Uwielbiam to, jak zaznacza zróżnicowane linie. Znacznie ułatwia porównywanie konfiguracji. To razem z sdiff <(sort file1) <(sort file2)
sortem
3

Jeśli chcesz rozwiązać ten problem za pomocą coreutils, akceptowana odpowiedź jest dobra:

comm -23 <(sort file1) <(sort file2) > file3

Możesz także użyć sd (stream diff), który nie wymaga sortowania ani podstawiania procesów i obsługuje nieskończone strumienie, na przykład:

cat file1 | sd 'cat file2' > file3

Prawdopodobnie nie jest to duża korzyść w tym przykładzie, ale nadal rozważ to; w niektórych przypadkach nie będzie w stanie wykorzystać commani grep -Fani diff.

Oto post na blogu, który napisałem o różnicowaniu strumieni na terminalu, który wprowadza sd.

mlg
źródło
3

Jednak nie ma greprozwiązania?

  • linie, które istnieją tylko w pliku2:

    grep -Fxvf file1 file2 > file3
  • linie, które istnieją tylko w pliku1:

    grep -Fxvf file2 file1 > file3
  • linie, które istnieją w obu plikach:

    grep -Fxf file1 file2 > file3
αғsнιη
źródło
2

Wiele odpowiedzi już jest, ale żadna z nich nie jest idealna IMHO. Odpowiedź Thanatosa pozostawia kilka dodatkowych znaków w linii, a odpowiedź Sorpigala wymaga sortowania lub wstępnego sortowania plików, co może nie być odpowiednie w każdych okolicznościach.

Myślę, że najlepszym sposobem na uzyskanie linii, które są różne i nic innego (bez dodatkowych znaków, bez ponownego zamawiania) jest kombinacją diff, grepi awk(lub podobny).

Jeśli wiersze nie zawierają znaku „<”, krótką linijką może być:

diff urls.txt* | grep "<" | sed 's/< //g'

ale to usunie każde wystąpienie „<” (mniej niż, spacja) z linii, co nie zawsze jest OK (np. kod źródłowy). Najbezpieczniejszą opcją jest użycie awk:

diff urls.txt* | grep "<" | awk '{for (i=2; i<NF; i++) printf $i " "; print $NF}'

Ta jedna linijka porównuje oba pliki, następnie filtruje wyjście diff w stylu ed, a następnie usuwa końcowe „<”, które dodaje diff. Działa to nawet wtedy, gdy linie zawierają same „<”.

sergut
źródło
1
comm nie wymaga sortowania (w nowszych wersjach?) - po prostu użyj --nocheck-order. Używam tego często podczas manipulowania CSV z CLI
ak5
2

Jestem zaskoczony, że nikt nie wspomniał diff -yo tworzeniu równoległego wyjścia , na przykład:

diff -y file1 file2 > file3

A w file3(różne linie mają symbol |w środku):

same     same
diff_1 | diff_2
xtluo
źródło
1

Użyj narzędzia Diff i wyodrębnij tylko wiersze zaczynające się od <w danych wyjściowych

Capslockk
źródło
0
diff a1.txt a2.txt | grep '> ' | sed 's/> //' > a3.txt

Wypróbowałem prawie wszystkie odpowiedzi w tym wątku, ale żadna nie była kompletna. Po kilku szlakach powyżej jeden zadziałał. diff da ci różnicę, ale z pewnymi niechcianymi specjalnymi charasami. gdzie rzeczywiste linie różnicy zaczynają się od „>”. więc następnym krokiem jest grep linie zaczynające się od '>', po którym następuje usunięcie tego samego z sedem .

tollin jose
źródło
1
To jest zły pomysł. Będziesz także musiał zmodyfikować linie zaczynające się od <. Zobaczysz to, jeśli zmienisz kolejność plików wejściowych. Nawet jeśli to zrobiłeś, chciałbyś ominąć grepużywając więcej sed: `diff a1 a2 | sed '/> / s ///' 'Może to nadal przerywać linie zawierające >lub <w odpowiedniej sytuacji i nadal pozostawia dodatkowe linie opisujące numery linii. Jeśli chciał spróbować tego podejścia jest lepszym sposobem byłoby: diff -C0 a1 a2 | sed -ne '/^[+-] /s/^..//p'.
sorpigal
0

Możesz użyć diffz następującym formatowaniem wyjściowym:

diff --old-line-format='' --unchanged-line-format='' file1 file2

--old-line-format='', wyłącz wyjście dla plik1, jeśli linia różniła się od porównania w pliku2.
--unchanged-line-format='', wyłącz wyjście, jeśli linie są takie same.

αғsнιη
źródło