Bir dosyada diğerinde olmayan satırları bulmanın hızlı yolu?


241

İki büyük dosyam var (dosya adları kümesi). Her dosyada yaklaşık 30.000 satır. Dosya2 dosyada mevcut olmayan satırları bulmak için hızlı bir yol bulmaya çalışıyorum.

Örneğin, bu dosya1 ise:

line1
line2
line3

Ve bu dosya2:

line1
line4
line5

Sonra benim sonuç / çıktı:

line2
line3

Bu çalışıyor:

grep -v -f file2 file1

Ancak büyük dosyalarımda kullanıldığında çok, çok yavaş.

Diff () kullanarak bunu yapmanın iyi bir yolu olduğundan şüpheleniyorum, ancak çıktı sadece çizgiler, başka bir şey olmalı ve bunun için bir anahtar bulamıyorum.

Herkes bash ve temel linux ikili dosyaları kullanarak bunu yapmanın hızlı bir yolunu bulmama yardımcı olabilir mi?

EDIT: Kendi sorumu takip etmek için, bu şimdiye kadar diff () kullanarak buldum en iyi yolu:

diff file2 file1 | grep '^>' | sed 's/^>\ //'

Elbette, daha iyi bir yol olmalı mı?


1
daha hızlı olursa bunu deneyebilirsiniz:awk 'NR==FNR{a[$0];next}!($0 in a)' file2 file1 > out.txt
Kent


4
Grep -v -f file2 file1 hakkında bilgi verdiğiniz için teşekkür ederiz
Rahul Prasad


Azaltılmış araç seti ile basit bir yol: cat file1 file2 file2 | sort | uniq --uniqueaşağıdaki cevabımı görün.
Ondra Žižka

Yanıtlar:


233

GNU diffçıktısında eski / yeni / değişmemiş çizgilerin biçimlendirmesini kontrol ederek bunu başarabilirsiniz :

diff --new-line-format="" --unchanged-line-format=""  file1 file2

Bunun çalışması için girdi dosyaları sıralanmalıdır . İle bash(ve zsh) sen yerinde süreç ikamesi ile sıralayabilir <( ):

diff --new-line-format="" --unchanged-line-format="" <(sort file1) <(sort file2)

Yukarıdaki yeni ve değişmemiş çizgiler bastırılır, bu nedenle yalnızca değiştirilen (yani, durumunuzdaki kaldırılan çizgiler) çıkarılır. Ayrıca birkaç kullanabilir diffgibi diğer çözümler sunmuyoruz seçenekleri, -idava veya çeşitli boşluk seçeneklerini (görmezden -E, -b, -vdaha az sıkı eşleştirme için vs).


açıklama

Seçenekleri --new-line-format, --old-line-formatve --unchanged-line-formatsen yolu kontrol etmesine izin diffbenzer farklılıklar, biçimlendirir printfbiçim belirteçleri. Bu seçenekler yeni (eklenmiş), eski (kaldırılmış) ve değiştirilmemiş formatlar sırasıyla satırları . Birini "" boş olarak ayarlamak, bu tür hatların çıkışını engeller.

Eğer aşina değilseniz birleşik fark biçimi, sen kısmen bunu birlikte yeniden oluşturabilirsiniz:

diff --old-line-format="-%L" --unchanged-line-format=" %L" \
     --new-line-format="+%L" file1 file2

%LBelirteci Söz konusu satır ve biz "+" "her önüne - gibi" veya "" diff -u (yalnızca çıkış farklılıklar, bu yoksun olduğunu not --- +++ve @@her gruplandırılmış değişim üstündeki çizgiler). Ayrıca gibi diğer yararlı şeyler yapmak için kullanabilir sayı her satırı ile %dn.


diff(Diğer önerilerle birlikte yöntem commve join) sadece beklenen çıktıyı üretmek sıralı kullanabilirsiniz olsa girdi <(sort ...)yerinde sıralamak. İşte awkkeyfi olarak sıralanan girdi dosyalarını kabul eden ve eksik satırları dosya1'de göründükleri sırayla çıkaran basit (nawk) bir komut dosyası (Konsolebox'ın cevabında bağlantılı olan komut dosyalarından esinlenilmiştir) .

# 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]
}

Bu, dosya1 içeriğinin tamamını satır satır dizinli bir dizide ll1[]satır satır ve dosya2 içeriğinin tamamını satır satır dizinli ilişkilendirilebilir dizide saklar ss2[]. Her iki dosya da okunduktan sonra yineleyin ll1ve inoperatör1'i dosya1'deki satırın dosya2'de olup olmadığını belirlemek için kullanın . ( diffYinelemeler varsa , bu yöntem için farklı bir çıktı olacaktır .)

Dosyaların her ikisini de saklamak bellek sorununa neden olacak kadar büyükse, yalnızca dosya1 depolayarak ve dosya2 okunduğunda eşleşmeleri silerek CPU'yu bellek için takas edebilirsiniz.

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]
}

Yukarıda, dosya1'in tüm içeriği biri satır numarasına ll1[]göre dizinlenmiş, biri satır içeriğine göre dizinlenmiş iki dizide depolanır ss1[]. Dosya2 okunur Sonra, her bir eşleşen satır silinir ll1[]ve ss1[]. Sonunda, dosya1'den kalan satırlar çıktı ve orijinal sıra korunur.

Bu durumda, belirtildiği gibi sorunla, GNU (filtreleme bir GNU uzantısıdır) kullanarak bölünebilir ve fethedebilirsinizsplit , dosya1 parçalarıyla her seferinde çalışır ve dosya2'yi her seferinde tamamen okuyabilirsiniz:

split -l 20000 --filter='gawk -f linesnotin.awk - file2' < file1

Kullanımı ve yerleştirme uygulaması -anlam stdinüzerinde gawkkomut satırından. Bu, splitinvokasyon başına 20000 satırlık parçalarda dosya1 tarafından sağlanır .

GNU dışı sistemlerde kullanıcılar için, neredeyse kesinlikle yoktur bir GNU coreutils parçası olarak OSX'te dahil, edinmek paketlediğinizden Apple Xcode GNU sağlar araçları diff, awkolsa sadece POSIX / BSD splityerine GNU sürümü.


1
Bu muazzam bir grep tarafından alınan zamanın küçük bir kısmında tam olarak ihtiyacım olanı yapıyor. Teşekkürler!
Niels2000


bazılarımız gnu üzerinde değiliz [OS X bsd burada ...] :)
rogerdpack

1
Şunu demek istiyorum diff: genel olarak girdi dosyaları farklı olacak diff, bu durumda 1 döndürülür . Bir bonus düşünün ;-) Bir kabuk komut dosyasında test ediyorsanız 0 ve 1 beklenen çıkış kodları ise, 2 bir sorun olduğunu gösterir.
mr.spuratic

1
@ mr.spuratic ah evet, şimdi onu buluyorum man diff. Teşekkürler!
Archeosudoerus

246

Comm (kısa "ortak" için) komutu yararlı olabilircomm - compare two sorted files line by line

#find lines only in file1
comm -23 file1 file2 

#find lines only in file2
comm -13 file1 file2 

#find lines common to both files
comm -12 file1 file2 

manDosya aslında bu oldukça okunabilir.


6
OSX üzerinde kusursuz çalışır.
pisaruk

41
Sıralı girdi gereksinimi belki de vurgulanmalıdır.
tripleee

21
commayrıca girdinin sıralandığını doğrulama seçeneği de vardır --check-order(yine de öyle görünüyor, ancak bu seçenek devam etmek yerine hataya neden olacaktır). Ancak dosyaları sıralamak için şunu yapın: com -23 <(sort file1) <(sort file2)ve benzeri
michael

Windows'da oluşturulan bir dosyayı Linux'ta oluşturulan bir dosyayla karşılaştırıyordum ve commhiç çalışmıyormuş gibi görünüyordu . Bunun satır sonlarıyla ilgili olduğunu anlamak biraz zaman aldı: Aynı görünen satırlar bile farklı satır sonlarına sahiplerse farklı kabul edilir. Komut dos2unix, CRLF satır sonlarını yalnızca LF'ye dönüştürmek için kullanılabilir.
ZeroOne

23

Konsolebox'ın önerdiği gibi, posterler grep çözümü

grep -v -f file2 file1

aslında -Fseçeneği eklerseniz , desenleri normal ifadeler yerine sabit dizeler olarak ele almak için harika (hızlı) çalışır . Ben karşılaştırmak zorunda kaldı ~ 1000 satır dosya listeleri bir çift doğruladı. Bununla -Fbirlikte grep çıktısını yeniden yönlendirirken 0.031 s (gerçek) alırken, 2.278 s (gerçek) sürdü wc -l.

Bu testler ayrıca -x, dosya2'nin dosya1'deki bir veya daha fazla satırın bir kısmıyla eşleşen, ancak tamamı ile eşleşmeyen satırlar içerdiği durumlarda tam doğruluk sağlamak için çözümün bir parçası olan anahtarı da içerir.

Dolayısıyla, girişlerin sıralanmasını gerektirmeyen bir çözüm hızlı, esnektir (büyük / küçük harf duyarlılığı, vb.):

grep -F -x -v -f file2 file1

Bu, grep'in tüm sürümleriyle çalışmaz, örneğin macOS'ta başarısız olur, burada dosya 1'deki bir satır, dosyasında bir alt dizesi olan başka bir satırla eşleşse bile dosya 2'de yok olarak gösterilecektir. . Alternatif olarak, bu çözümü kullanmak için macOS'a GNU grep yükleyebilirsiniz .


Evet, işe yarıyor ama bununla bile -Fiyi ölçeklenmiyor.
Molomby

Bu o kadar hızlı değil, vazgeçmeden önce ~ 500k satırlık 2 dosya için 5 dakika bekledim
cahen

aslında, bu yol hala comm yoldan daha yavaş, çünkü bu sıralanmamış dosyaları işleyebilir, bu nedenle sıra dışı bırakılarak sürüklenir, comm sıralama avantajı alır
workplaylifecycle

@workplaylifecycle Sıralama için zaman ayırmanız gerekir, bu da aşırı büyük bir darboğaz olabilir file2.
rwst

Ancak, -xseçenek ile grep görünüşte daha fazla bellek kullanıyor. file26-10 baytlık bir 180M kelime ile benim süreç Killed32GB RAM makinede var ...
rwst

11

sıralama ve fark hızı nedir?

sort file1 -u > file1.sorted
sort file2 -u > file2.sorted
diff file1.sorted file2.sorted

1
Diff yapmadan önce dosyaları sıralama ihtiyacını hatırlattığınız için teşekkürler. sort + diff ÇOK daha hızlıdır.
Niels2000

4
one liner ;-) diff <(sıralama dosyası1 -u) <(sıralama
dosyası2

11

Bazı minimal Linux dağıtımında örneğin "fantezi araçları", kısa iseniz, orada sadece bir çözümdür cat, sortve uniq:

cat includes.txt excludes.txt excludes.txt | sort | uniq --unique

Ölçek:

seq 1 1 7 | sort --random-sort > includes.txt
seq 3 1 9 | sort --random-sort > excludes.txt
cat includes.txt excludes.txt excludes.txt | sort | uniq --unique

# Output:
1
2    

Bu aynı zamanda nispeten hızlıdır grep.


1
Not - bazı uygulamalar --uniqueseçeneği tanımayacaktır . Bunun için standart POSIX seçeneğini kullanabilmelisiniz :| uniq -u
AndrewF

1
Örnekte, "2" nereden geldi?
Niels2000

1
@ Niels2000, seq 1 1 71'den 7'ye kadar, 1'den 7'ye kadar sayılar oluşturur, yani 1 2 3 4 5 6 7. Ve tam da 2'niz var!
Eirik Lygre

5
$ join -v 1 -t '' file1 file2
line2
line3

-tEğer çizgilerin bazılarında bir boşluk olsaydı, bütün çizgi karşılaştırır emin olur.


Gibi comm, joinbirleştirme işlemini yaptığınız alanda her iki giriş satırının da sıralanmasını gerektirir.
tripleee

4

Python'u kullanabilirsiniz:

python -c '
lines_to_remove = set()
with open("file2", "r") as f:
    for line in f.readlines():
        lines_to_remove.add(line.strip())

with open("f1", "r") as f:
    for line in f.readlines():
        if line.strip() not in lines_to_remove:
            print(line.strip())
'

4

Kullanım combinedan moreutils, paketin bir set programı olduğu destekleri not, and, or, xoroperasyonlar

combine file1 not file2

yani bana dosya1'de fakat dosya2'de olmayan satırlar ver

VEYA bana dosya1'deki satırları eksi dosya2'deki satırları verin

Not: combine herhangi bir işlem yapmadan önce her iki dosyadaki benzersiz satırları sıralar ve bulur, ancak diffbulmaz. Yani diffve çıktıları arasında farklar bulabilirsiniz combine.

Yani aslında diyorsunuz ki

Dosya1 ve dosya2'de farklı satırlar bulun ve sonra bana dosya2'de dosya2'de eksi satırlar verin

Deneyimlerime göre, diğer seçeneklerden çok daha hızlı


2

Ggre için fgrep kullanmak veya -F seçeneği eklemek yardımcı olabilir. Ancak daha hızlı hesaplamalar için Awk kullanabilirsiniz.

Bu Awk yöntemlerinden birini deneyebilirsiniz:

http://www.linuxquestions.org/questions/programming-9/grep-for-huge-files-826030/#post4066219


2
+1 Bu, girişlerin sıralanmasını gerektirmeyen tek cevaptır. Görünüşe göre OP bu gereksinimden memnun olsa da, birçok gerçek dünya senaryosunda kabul edilemez bir kısıtlamadır.
üçlü

1

Bunu genellikle yapma yöntemim --suppress-common-linesbayrağı kullanmaktır , ancak bunun yalnızca yan yana biçimde yapıldığını unutmayın.

diff -y --suppress-common-lines file1.txt file2.txt


0

Benim için normal bir if ve for döngüsü ifadesi kullanarak mükemmel çalıştı buldum.

for i in $(cat file2);do if [ $(grep -i $i file1) ];then echo "$i found" >>Matching_lines.txt;else echo "$i missing" >>missing_lines.txt ;fi;done

2
Bkz. DontReadLinesWithFor . Ayrıca, grepsonuçlarınızdan herhangi biri birden çok kelimeye genişliyorsa veya file2girişlerinizden herhangi biri kabuk tarafından bir glob olarak değerlendirilebiliyorsa , bu kod çok kötü davranır .
Charles Duffy
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.