Możesz to osiągnąć kontrolując formatowanie starych / nowych / niezmienionych linii w diff
danych 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 diff
opcji, że inne rozwiązania nie oferują, takie jak -i
ignorowanie wielkości liter lub różne opcje whitespace ( -E
, -b
, -v
etc) dla mniej ścisłe dopasowanie.
Wyjaśnienie
Opcje --new-line-format
, --old-line-format
i --unchanged-line-format
pozwalają kontrolować sposób diff
formatuje różnice, podobne do printf
formatu 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 %L
jest 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
.
diff
Metoda (wraz z innymi sugestiami comm
i 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 ll1
i użyj in
operatora, aby ustalić, czy wiersz w pliku 1 jest obecny w pliku 2. (Będzie to miało inny wynik niż diff
metoda, 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 stdin
w gawk
wierszu poleceń. Zapewnia to split
plik 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
, awk
choć tylko POSIX / BSD split
zamiast wersji GNU.
awk 'NR==FNR{a[$0];next}!($0 in a)' file2 file1 > out.txt