Różnica między dwoma dużymi plikami

14

Mam „test1.csv” i zawiera

200,400,600,800
100,300,500,700
50,25,125,310

i test2.csv i zawiera

100,4,2,1,7
200,400,600,800
21,22,23,24,25
50,25,125,310
50,25,700,5

teraz

diff test2.csv test1.csv > result.csv

jest inny niż

diff test1.csv test2.csv > result.csv

Nie wiem, która kolejność jest prawidłowa, ale chcę czegoś innego, oba powyższe polecenia wygenerują coś podobnego

2 > 100,4,2,1,7
   3 2,3c3,5
   4 < 100,300,500,700
   5 < 50,25,125,310
   6 \ No newline at end of file
   7 ---
   8 > 21,22,23,24,25
   9 > 50,25,125,310

Chcę wypisać tylko różnicę, dlatego wyniki. Ccs powinny wyglądać tak

100,300,500,700
100,4,2,1,7
21,22,23,24,25
50,25,700,5

Próbowałem diff -qi diff -sale nie załatwi. Porządek nie ma znaczenia, ważne jest to, że chcę zobaczyć tylko różnicę, nie> ani <ani puste miejsce.

grep -FvF zrobił lewę na mniejszych plikach, a nie na dużych

pierwszy plik zawiera ponad 5 milionów linii, drugi plik zawiera 1300.

więc results.csv powinien dać ~ 4 998,700 linii

Próbowałem też, grep -F -x -v -f które nie działały.

Lynob
źródło
1
@ Tym razem zobaczyłem twój link i jestem starym członkiem, więc znam zasady, ale byłem nieostrożny, przepraszam :) edytowałem go, i zobaczyłem wyskakujące okienko, że post został edytowany, więc zrobiłeś dla mnie pracę i jestem wdzięczny Sir.
Lynob
50,25,125,310jest wspólny dla obu plików .. musisz usunąć to z pożądanego wyjścia.
heemayl
Czy zamówienie powinno zostać zachowane?
Kos
1
rodzaj zależy od tego, co chcesz zrobić z informacją, diff, IMO, służy do tworzenia łatki. W każdym razie jestem teraz pewien twojego najlepszego narzędzia, diff, grep, awk lub perl.
Panther

Odpowiedzi:

21

Brzmi jak praca dla comm:

$ comm -3 <(sort test1.csv) <(sort test2.csv)
100,300,500,700
    100,4,2,1,7
    21,22,23,24,25
    50,25,700,5

Jak wyjaśniono w man comm:

   -1     suppress column 1 (lines unique to FILE1)

   -2     suppress column 2 (lines unique to FILE2)

   -3     suppress column 3 (lines that appear in both files)

Tak, -3oznacza, że tylko linie, które są unikatowe dla jednego z plików zostanie wydrukowany. Są one jednak wcięte zgodnie z plikiem, w którym zostały znalezione. Aby usunąć kartę, użyj:

$ comm -3 <(sort test1.csv) <(sort test2.csv) | tr -d '\t'
100,300,500,700
100,4,2,1,7
21,22,23,24,25
50,25,700,5

W takim przypadku tak naprawdę nie musisz nawet sortować plików i możesz uprościć powyższe, aby:

comm -3 test1.csv test2.csv | tr -d '\t' > difference.csv
terdon
źródło
Nie dałeś się zwieść spacjom po 200,[...]linii, co? :)
Kos
@ Kos nie, najpierw usunąłem końcowe spacje z plików. Zakładałem, że pliki OP tak naprawdę ich nie mają.
terdon
6

Używanie grepz bashpodstawieniem procesu:

$ cat <(grep -vFf test2.csv test1.csv) <(grep -vFf test1.csv test2.csv)
100,300,500,700
100,4,2,1,7
21,22,23,24,25
50,25,700,5

Aby zapisać dane wyjściowe jako results.csv:

cat <(grep -vFf test2.csv test1.csv) <(grep -vFf test1.csv test2.csv) >results.csv
  • <()jest bashwzorcem zastępowania procesu

  • grep -vFf test2.csv test1.csv znajdzie linie tylko dla siebie test1.csv

  • grep -vFf test1.csv test2.csv znajdzie linie tylko dla siebie test2.csv

  • Na koniec podsumowujemy wyniki według cat

Lub, jak zasugerował Oli , możesz także użyć grupowania poleceń:

$ { grep -vFf test2.csv test1.csv; grep -vFf test1.csv test2.csv; }
100,300,500,700
100,4,2,1,7
21,22,23,24,25
50,25,700,5

Lub po prostu uruchamiaj jeden po drugim, ponieważ obaj piszą do STDOUT, w końcu zostaną dodani:

$ grep -vFf test2.csv test1.csv; grep -vFf test1.csv test2.csv
100,300,500,700
100,4,2,1,7
21,22,23,24,25
50,25,700,5
heemayl
źródło
1
Dlaczego catdwa przekierowane polecenia? Dlaczego po prostu nie uruchomić jednego? grep ... ; grep ...lub { grep ... ; grep ... ; }jeśli chcesz zrobić coś ze zbiorowym dorobkiem.
Oli
@Oli Dzięki .. to świetny pomysł .. nie myślałem o tym ..
heemayl
4

Jeśli kolejność wierszy nie jest istotna, użyj awklub perl:

awk '{seen[$0]++} END {for (i in seen) {if (seen[i] == 1) {print i}}}' 1.csv 2.csv

Użyj, grepaby uzyskać wspólne linie i odfiltrować je:

grep -hxvFf <(grep -Fxf 1.csv 2.csv) 1.csv 2.csv

Wewnętrzny grep otrzymuje linie wspólne, a następnie zewnętrzny grep znajduje linie, które nie pasują do tych wspólnych linii.

muru
źródło
Twoje polecenie awk po prostu ponownie implementuje sort | uniq -u, co daje złą odpowiedź, gdy jeden plik zawiera zduplikowane linie. W przypadku grep powiedziałbym „wewnętrzny” / „zewnętrzny”, a nie „wewnętrzny” / „zewnętrzny”.
Peter Cordes
@PeterCordes tak, to prawda i kim masz powiedzieć, że to zły wynik?
muru
Błędem w tym sensie, że nie jest to dokładnie to, o co pytano, w tym narożnym przypadku. Może to być to, czego ktoś chce, ale należy wskazać różnicę między tym, co awkwydrukujesz, a tym, co wydrukuje comm -3i diffodpowiedzi.
Peter Cordes
@PeterCordes jeszcze raz, kim masz to powiedzieć? Dopóki OP nie powie, że tego chcą, nie obchodzi mnie, czy wyjście różni się od tego comm -3. Nie widzę powodu, dla którego miałbym to wyjaśniać. Jeśli chcesz edytować notatkę, nie krępuj się.
muru
OP powiedział, że chce różnicy. Nie zawsze to produkuje twój program. Program, który wytwarza takie same dane wyjściowe dla jednej skrzynki testowej, ale nie spełnia opisu napisanego dla wszystkich przypadków, wymaga podniesienia głowy. Jestem tutaj, aby to powiedzieć, i to prawda, niezależnie od tego, kim jestem lub kim jesteś. Dodałem notatkę.
Peter Cordes
4

Skorzystaj z --*-line-format=...opcjidiff

Możesz diffdokładnie powiedzieć , czego potrzebujesz - wyjaśniono poniżej:

diff --old-line-format='%L' --new-line-format='%L' --unchanged-line-format='' f1.txt f2.txt

Możliwe jest określenie wyjścia diff w bardzo szczegółowy sposób, podobny do printfformatu liczbowego.

Linie z pierwszego pliku test1.csvnazywane są „starymi” liniami, a linie z drugiego test2.csv„nowymi” liniami. Ma to sens, gdy diffsłuży do sprawdzania, co zmieniło się w pliku.

Potrzebne są opcje, aby ustawić format „starych” linii, „nowych” linii i „niezmienionych” linii.
Potrzebne formaty są bardzo proste: w
przypadku zmienionych linii, nowych i starych, chcemy wyświetlać tylko tekst linii. %Lto symbol formatu tekstu wiersza.
Dla niezmienionych linii nie chcemy nic pokazywać.

Dzięki temu możemy napisać opcje takie jak --old-line-format='%L'i złożyć wszystko razem, korzystając z przykładowych danych:

$ diff --old-line-format='%L' --new-line-format='%L' --unchanged-line-format='' test1.csv test2.csv
100,4,2,1,7
100,300,500,700
21,22,23,24,25
50,25,700,5


Uwagi na temat wydajności

Ponieważ pliki mają inny rozmiar, spróbuj wymienić pliki wejściowe, jeśli to nie ma znaczenia, może być tak, że wewnętrzne działanie diffmoże obsługiwać jedną stronę lepiej niż drugą. Lepsze albo wymaga mniej pamięci, albo mniej obliczeń.

Istnieje możliwość optymalizacji korzystania diffz dużych plików: --speed-large-files. Wykorzystuje założenia dotyczące struktury pliku, więc nie jest jasne, czy to pomaga w twoim przypadku, ale warto go wypróbować.

Opcje formatu opisano man diffponiżej --LTYPE-line-format=LFMT.

Volker Siegel
źródło
3

Ponieważ kolejność nie musi być zachowana, po prostu:

sort test1.csv test2.csv | uniq -u
  • sort test1.csv test2.csv: scala i sortuje test1.csvoraztest2.csv
  • uniq -u: drukuje tylko linie, które nie mają duplikatu
kos
źródło
To nie działa, jeśli jeden plik zawiera wiersz dwa razy, który nie pojawia się w drugim pliku. Oba zdarzenia byłyby w diffrezultacie.
Volker Siegel