Bir dosyadan başka bir dosyadaki satırları silme


126

Bir dosyam var f1:

line1
line2
line3
line4
..
..

Başka bir dosyadaki tüm satırları silmek istiyorum f2:

line2
line8
..
..

Ben bir şey denedim catve sedhatta yakın hangi amaçla hareket değildi ki. Bunu nasıl yapabilirim?



Başka bir dosyadan dizeleri "içeren" bir dosyadan satırları kaldırmak istiyorsanız (örneğin kısmi eşleşmeler) bkz. Unix.stackexchange.com/questions/145079/…
rogerdpack

Yanıtlar:


154

grep -v -x -f f2 f1 hile yapmalı.

Açıklama:

  • -v eşleşmeyen satırları seçmek için
  • -x sadece tüm satırları eşleştirmek için
  • -f f2 kalıpları almak f2

Bir yerine kullanabilir grep -Fveya fgrepmaç için sabit dizeleri gelen f2ziyade desenleri (eğer yerine çizgi tedavi daha bir "eğer sen görmek ne elde" şekilde satırları kaldırmak istediğiniz f2regex desenleri gibi).


22
Bu O (n²) karmaşıklığına sahiptir ve dosyalar birkaç K satırından fazlasını içerdiğinde tamamlanması saatler sürecektir.
Arnaud Le Blanc

11
Hangi SO'nun önerdiği algoritmaların O (n ^ 2) karmaşıklığına sahip olduğunu bulmak yalnızca O (n) karmaşıklığına sahiptir, ancak yine de rekabet etmek saatler sürebilir.
HDave

2
Bunu her biri ~ 2k satırlık 2 dosyada denedim ve işletim sistemi tarafından öldürüldü (verildi, bu çok güçlü olmayan bir VM, ama yine de).
Trebor Rude

1
Bunun zarafetini seviyorum; Jona Christopher Sahnwal'ın cevabının hızını tercih ederim.
Alex Hall

1
@ arnaud576875: Emin misin? Uygulanmasına bağlıdır grep. f2Aramaya başlamadan önce düzgün bir şekilde ön işlem yaparsa , arama sadece O (n) zaman alacaktır.
HelloGoodbye

57

Onun yerine haberleşmeyi deneyin (f1 ve f2'nin "zaten sıralanmış" olduğunu varsayarak)

comm -2 -3 f1 f2

5
Emin değilim commçözümün, f1kullanım için ön koşul olan satırların sıralandığını göstermediğinden emin değilimcomm
gabuzo

1
Dosyalarım sıralandığı ve birinde 250.000'den fazla, diğerinde yalnızca 28.000 satır olduğu için bu benim için çalıştı. Teşekkürler!
Kış

1
Bu çalıştığında (girdi dosyaları sıralandı), bu son derece hızlıdır!
Mike Jarvis

Arnaud576875'in çözümünde olduğu gibi, benim için cygwin kullanıyorum, bu ikinci dosyadaki saklanmak isteyebilecek yinelenen satırları ortadan kaldırdı.
Alex Hall

9
İlk önce dosyaları sıralamak için işlem ikamesini kullanabilirsiniz, tabii ki:comm -2 -3 <(sort f1) <(sort f2)
davemyron

14

Çok büyük olmayan dosyaları dışlamak için, AWK'nın ilişkilendirilebilir dizilerini kullanabilirsiniz.

awk 'NR == FNR { list[tolower($0)]=1; next } { if (! list[tolower($0)]) print }' exclude-these.txt from-this.txt 

Çıktı, "from-this.txt" dosyasıyla aynı sırada olacaktır. tolower()Bunu gerekirse fonksiyon, küçük harf duyarsız hale getirir.

Algoritmik karmaşıklık muhtemelen O (n) (bu.txt boyutunu dışla) + O (n) (this.txt boyutundan) olacaktır.


Neden çok büyük olmayan dosyalar söylüyorsunuz? Buradaki korku (varsayıyorum) karma oluşturmak için sistemi sistem belleğinin dışında çalıştırmak veya başka bir sınırlama var mı?
rogerdpack

takipçiler için, satırları "sterilize etmek" için daha agresif bir seçenek daha var (çünkü karşılaştırmanın ilişkili diziyi kullanmak için kesin olması gerekir), ex unix.stackexchange.com/a/145132/8337
rogerdpack

@rogerdpack: Büyük bir dışlama dosyası, büyük bir karma dizisi (ve uzun bir işlem süresi) gerektirir. Büyük bir "from-this.txt" yalnızca uzun bir işlem süresi gerektirecektir.
sonraki duyuruya kadar duraklatıldı.

1
Bu exclude-these.txt, boşsa başarısız olur (yani herhangi bir çıktı üretmez) . @ jona-christopher-sahnwaldt'ın aşağıdaki cevabı bu durumda işe yarar. Birden fazla dosya da belirtebilirsiniz, örn.awk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 done.out failed.out f=2 all-files.out
Graham Russell

11

Dennis Williamson'ın cevabına benzer şekilde (çoğunlukla sözdizimsel değişiklikler, örneğin numara yerine dosya numarasını açıkça belirlemek NR == FNR):

awk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 exclude-these.txt f=2 from-this.txt

Erişim r[$0], o satır için bir giriş oluşturur, bir değer ayarlamaya gerek yoktur.

Awk'nin sabit arama ve (ortalama olarak) sabit güncelleme süresine sahip bir hash tablosu kullandığını varsayarsak, bunun zaman karmaşıklığı O (n + m) olacaktır, burada n ve m dosyaların uzunluklarıdır. Benim durumumda, n ~ 25 milyon ve m ~ 14000 idi. Awk çözümü sıralamadan çok daha hızlıydı ve ben de orijinal sırayı korumayı tercih ettim.


Bunun Dennis Williamson cevabından farkı nedir? Karma bir atama yapmaması, bundan biraz daha hızlı olması tek farkı mı? Algoritmik karmaşıklık onunkiyle aynı mı?
rogerdpack

Fark çoğunlukla sözdizimseldir. Değişkeni fdaha net buluyorum NR == FNR, ama bu bir zevk meselesi. Hash içine atama o kadar hızlı olmalı ki, iki sürüm arasında ölçülebilir bir hız farkı olmamalıdır. Sanırım karmaşıklık konusunda yanılmışım - eğer arama sabitse, güncelleme de sabit olmalıdır (ortalama olarak). Güncellemenin neden logaritmik olacağını düşündüğümü bilmiyorum. Cevabımı düzenleyeceğim.
jcsahnwaldt Monica'yı yeniden etkinleştir

Bu cevaplardan birkaçını denedim ve bu AMAZEBALLS hızlıydı. Yüzbinlerce satırlık dosyalarım vardı. Büyü gibi çalıştı!
Bay T,

1
Bu benim tercih ettiğim çözüm. Birden çok dosyayla çalışır ve ayrıca boş dışlama dosyaları örn awk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 empty.file done.out failed.out f=2 all-files.out. Oysa diğer awkçözüm boş dışlama dosyasıyla başarısız olur ve yalnızca birini alabilir.
Graham Russell

5

Ruby'niz varsa (1.9+)

#!/usr/bin/env ruby 
b=File.read("file2").split
open("file1").each do |x|
  x.chomp!
  puts x if !b.include?(x)
end

O (N ^ 2) karmaşıklığına sahip olan. Performansı önemsemek istiyorsanız, işte başka bir versiyon

b=File.read("file2").split
a=File.read("file1").split
(a-b).each {|x| puts x}

Bu, çıkarma işlemini gerçekleştirmek için bir hash kullanır, dolayısıyla karmaşıklık O (n) (a'nın boyutu) + O (n) (b'nin boyutu)

aşağıda user576875'in izniyle, ancak 100 bin satırla yukarıdakilerin küçük bir ölçütü:

$ for i in $(seq 1 100000); do echo "$i"; done|sort --random-sort > file1
$ for i in $(seq 1 2 100000); do echo "$i"; done|sort --random-sort > file2
$ time ruby test.rb > ruby.test

real    0m0.639s
user    0m0.554s
sys     0m0.021s

$time sort file1 file2|uniq -u  > sort.test

real    0m2.311s
user    0m1.959s
sys     0m0.040s

$ diff <(sort -n ruby.test) <(sort -n sort.test)
$

diff oluşturulan 2 dosya arasında hiçbir fark olmadığını göstermek için kullanıldı.


1
Bu O (n²) karmaşıklığına sahiptir ve dosyalar birkaç K satırından fazlasını içerdiğinde tamamlanması saatler sürecektir.
Arnaud Le Blanc

Bu noktayı pek umursamıyorum çünkü büyük dosyalardan bahsetmedi.
kurumi

3
Bu kadar savunmacı olmaya gerek yok, sanki @ user576875 sizin cevabınızı veya herhangi bir şeyi olumsuz vermiş değil. :-)
John Parker

çok güzel ikinci versiyon, yakut kazanır :)
Arnaud Le Blanc

4

Diğer çeşitli cevaplar arasında bazı zamanlama karşılaştırmaları:

$ for n in {1..10000}; do echo $RANDOM; done > f1
$ for n in {1..10000}; do echo $RANDOM; done > f2
$ time comm -23 <(sort f1) <(sort f2) > /dev/null

real    0m0.019s
user    0m0.023s
sys     0m0.012s
$ time ruby -e 'puts File.readlines("f1") - File.readlines("f2")' > /dev/null

real    0m0.026s
user    0m0.018s
sys     0m0.007s
$ time grep -xvf f2 f1 > /dev/null

real    0m43.197s
user    0m43.155s
sys     0m0.040s

sort f1 f2 | uniq -u her iki dosyada birden çok kez görünen satırları kaldırdığı için simetrik bir fark bile değildir.

comm ayrıca stdin ve burada dizelerle de kullanılabilir:

echo $'a\nb' | comm -23 <(sort) <(sort <<< $'c\nb') # a

2

Görünüşe göre SQLite kabuğu için uygun bir iş:

create table file1(line text);
create index if1 on file1(line ASC);
create table file2(line text);
create index if2 on file2(line ASC);
-- comment: if you have | in your files then specify  .separator ××any_improbable_string×× 
.import 'file1.txt' file1
.import 'file2.txt' file2
.output result.txt
select * from file2 where line not in (select line from file1);
.q

1

Denediniz mi bu sed ile?

sed 's#^#sed -i '"'"'s%#g' f2 > f2.sh

sed -i 's#$#%%g'"'"' f1#g' f2.sh

sed -i '1i#!/bin/bash' f2.sh

sh f2.sh

0

'Programlama' cevabı değil ama işte hızlı ve kirli bir çözüm: http://www.listdiff.com/compare-2-lists-difference-tool adresine gitmeniz yeterli .

Açıkçası büyük dosyalar için işe yaramayacak ama benim için hile yaptı. Birkaç not:

  • Web sitesine herhangi bir şekilde bağlı değilim (bana hala inanmıyorsanız, o zaman çevrimiçi olarak farklı bir araç arayabilirsiniz; "çevrimiçi fark listesi ayarla" arama terimini kullandım)
  • Bağlantılı web sitesi her liste karşılaştırmasında ağ aramaları yapıyor gibi görünüyor, bu nedenle onu hassas verilerle beslemeyin
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.