Możesz to osiągnąć kontrolując formatowanie starych / nowych / niezmienionych linii w diffdanych wyjściowych GNU :
diff --new-line-format="" --unchanged-line-format="" file1 file2
Pliki wejściowe należy posortować, aby to zadziałało. Za pomocą bash(i zsh) możesz sortować na miejscu z podstawieniem procesu <( ):
diff --new-line-format="" --unchanged-line-format="" <(sort file1) <(sort file2)
W powyższym nowym i niezmienionym wierszu są pomijane, więc wyprowadzane są tylko zmienione (tj. Usunięte linie w twoim przypadku). Można również skorzystać z kilku diffopcji, że inne rozwiązania nie oferują, takie jak -iignorowanie wielkości liter lub różne opcje whitespace ( -E, -b, -vetc) dla mniej ścisłe dopasowanie.
Wyjaśnienie
Opcje --new-line-format, --old-line-formati --unchanged-line-formatpozwalają kontrolować sposób diffformatuje różnice, podobne do printfformatu specyfikatorami. Te opcje formatują odpowiednio nowe (dodane), stare (usunięte) i niezmienione linie. Ustawienie pustego „” zapobiega wyjściu tego rodzaju linii.
Jeśli znasz zunifikowany format różnic , możesz go częściowo odtworzyć za pomocą:
diff --old-line-format="-%L" --unchanged-line-format=" %L" \
--new-line-format="+%L" file1 file2
Specyfikatorem %Ljest linia, o której mowa, i każdy z nich poprzedza „+” „-” lub „”, podobnie jak diff -u
(zauważ, że wyświetla tylko różnice, brakuje linii --- +++i @@na górze każdej zgrupowanej zmiany). Można również użyć tego robić inne przydatne rzeczy jak liczba każdej linii z %dn.
diffMetoda (wraz z innymi sugestiami commi join) produkują tylko oczekiwane wyjście z posortowanej wejścia, choć można użyć <(sort ...)do sortowania w miejscu. Oto prosty awk(nawk) skrypt (zainspirowany skryptami połączonymi w odpowiedzi Konsolebox), który akceptuje arbitralnie uporządkowane pliki wejściowe i wyświetla brakujące wiersze w kolejności, w jakiej występują w pliku1.
# output lines in file1 that are not in file2
BEGIN { FS="" } # preserve whitespace
(NR==FNR) { ll1[FNR]=$0; nl1=FNR; } # file1, index by lineno
(NR!=FNR) { ss2[$0]++; } # file2, index by string
END {
for (ll=1; ll<=nl1; ll++) if (!(ll1[ll] in ss2)) print ll1[ll]
}
To przechowuje całą zawartość pliku1 linia po linii w tablicy indeksowanej numerem wiersza ll1[]oraz całą zawartość pliku2 linia po linii w tablicy asocjacyjnej indeksowanej treści linii ss2[]. Po odczytaniu obu plików, iteruj ll1i użyj inoperatora, aby ustalić, czy wiersz w pliku 1 jest obecny w pliku 2. (Będzie to miało inny wynik niż diffmetoda, jeśli istnieją duplikaty).
W przypadku, gdy pliki są wystarczająco duże, aby ich przechowanie powodowało problem z pamięcią, możesz wymienić procesor na pamięć, przechowując tylko plik 1 i usuwając dopasowania podczas odczytu pliku 2.
BEGIN { FS="" }
(NR==FNR) { # file1, index by lineno and string
ll1[FNR]=$0; ss1[$0]=FNR; nl1=FNR;
}
(NR!=FNR) { # file2
if ($0 in ss1) { delete ll1[ss1[$0]]; delete ss1[$0]; }
}
END {
for (ll=1; ll<=nl1; ll++) if (ll in ll1) print ll1[ll]
}
Powyżej przechowuje całą zawartość pliku1 w dwóch tablicach, jedna indeksowana według numeru linii ll1[], druga indeksowana według zawartości linii ss1[]. Następnie, gdy plik2 jest czytany, każda pasująca linia jest usuwana z ll1[]i ss1[]. Na koniec wyprowadzane są pozostałe wiersze z pliku1, zachowując pierwotną kolejność.
W tym przypadku, przy opisanym problemie, możesz także dzielić i podbijać za pomocą GNU split(filtrowanie jest rozszerzeniem GNU), powtarzane przebiegi z fragmentami pliku 1 i całkowite czytanie pliku 2 za każdym razem:
split -l 20000 --filter='gawk -f linesnotin.awk - file2' < file1
Zwróć uwagę na użycie i umiejscowienie -znaczenia stdinw gawkwierszu poleceń. Zapewnia to splitplik file1 w porcjach po 20000 linii na wywołanie.
Dla użytkowników systemów innych niż GNU, to prawie na pewno coreutils GNU pakiet można uzyskać, w tym na OSX jako część firmy Apple Xcode narzędzi GNU, która przewiduje diff, awkchoć tylko POSIX / BSD splitzamiast wersji GNU.
awk 'NR==FNR{a[$0];next}!($0 in a)' file2 file1 > out.txt