การลบบรรทัดจากไฟล์หนึ่งซึ่งอยู่ในไฟล์อื่น


126

ฉันมีไฟล์f1:

line1
line2
line3
line4
..
..

ฉันต้องการลบบรรทัดทั้งหมดที่อยู่ในไฟล์อื่นf2:

line2
line8
..
..

ฉันได้ลองทำอะไรบางอย่างcatและsedไม่ได้ใกล้เคียงกับที่ฉันตั้งใจ ฉันจะทำเช่นนี้ได้อย่างไร?



หากคุณต้องการลบบรรทัดจากไฟล์ที่ "มี" สตริงออกจากไฟล์อื่น (เช่นการจับคู่บางส่วน) โปรดดูที่unix.stackexchange.com/questions/145079/…
rogerdpack

คำตอบ:


154

grep -v -x -f f2 f1 ควรทำเคล็ดลับ

คำอธิบาย:

  • -v เพื่อเลือกบรรทัดที่ไม่ตรงกัน
  • -x เพื่อจับคู่ทั้งบรรทัดเท่านั้น
  • -f f2 เพื่อรับรูปแบบจาก f2

เราสามารถใช้แทนgrep -Fหรือfgrepเพื่อจับคู่สตริงคงที่จากf2แทนที่จะเป็นรูปแบบ (ในกรณีที่คุณต้องการลบเส้นในลักษณะ "สิ่งที่คุณเห็นว่าคุณได้รับอะไร" แทนที่จะใช้เส้นf2เป็นรูปแบบนิพจน์ทั่วไป)


22
สิ่งนี้มีความซับซ้อน O (n²) และจะเริ่มใช้เวลาหลายชั่วโมงในการดำเนินการให้เสร็จสมบูรณ์เมื่อไฟล์มี K มากกว่าสองสามบรรทัด
Arnaud Le Blanc

11
การหาว่าแอลกอรีทึมที่ SO แนะนำมีความซับซ้อน O (n ^ 2) มีเพียงความซับซ้อน O (n) เท่านั้น แต่ยังคงใช้เวลาแข่งขันได้หลายชั่วโมง
HDave

2
ฉันเพิ่งลองสิ่งนี้กับไฟล์ 2 ไฟล์ขนาด ~ 2k แต่ละไฟล์และถูกฆ่าโดยระบบปฏิบัติการ (ยอมรับว่านี่เป็น VM ที่ไม่ทรงพลัง แต่ก็ยังคงอยู่)
Trebor Rude

1
ฉันชอบความสง่างามของสิ่งนี้ ฉันชอบความเร็วของคำตอบของ Jona Christopher Sahnwal มากกว่า
Alex Hall

1
@ arnaud576875: แน่ใจเหรอ? grepมันขึ้นอยู่กับการดำเนินงานของ หากประมวลผลล่วงหน้าf2อย่างถูกต้องก่อนที่จะเริ่มค้นหาการค้นหาจะใช้เวลา O (n) เท่านั้น
HelloGoodbye

57

ลองใช้ comm แทน (สมมติว่า f1 และ f2 "เรียงลำดับแล้ว")

comm -2 -3 f1 f2

5
ฉันไม่แน่ใจว่าcommวิธีแก้ปัญหาคือคำถามที่ไม่ได้ระบุว่าf1มีการเรียงลำดับบรรทัดที่จำเป็นต้องใช้comm
gabuzo

1
สิ่งนี้ใช้ได้ผลสำหรับฉันเนื่องจากไฟล์ของฉันถูกจัดเรียงและมี 250,000+ บรรทัดในหนึ่งในนั้นมีเพียง 28,000 ในอีกรายการ ขอบคุณ!
ฤดูหนาว

1
เมื่อใช้งานได้ (จัดเรียงไฟล์อินพุต) นี่จะเร็วมาก!
Mike Jarvis

เช่นเดียวกับในโซลูชันของ arnaud576875 สำหรับฉันโดยใช้ cygwin สิ่งนี้จะกำจัดบรรทัดที่ซ้ำกันในไฟล์ที่สองซึ่งอาจต้องการเก็บไว้
Alex Hall

9
คุณสามารถใช้การทดแทนกระบวนการเพื่อจัดเรียงไฟล์ก่อนได้แน่นอน:comm -2 -3 <(sort f1) <(sort f2)
davemyron

14

สำหรับการยกเว้นไฟล์ที่ไม่ใหญ่เกินไปคุณสามารถใช้อาร์เรย์ที่เชื่อมโยงของ AWK ได้

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

ผลลัพธ์จะอยู่ในลำดับเดียวกับไฟล์ "from-this.txt" tolower()ฟังก์ชั่นที่ทำให้กรณีตายถ้าคุณต้องการที่

ความซับซ้อนของอัลกอริทึมอาจเป็น O (n) (ไม่รวมขนาด these.txt) + O (n) (ขนาด from-this.txt)


ทำไมคุณถึงบอกว่าไฟล์ไม่ใหญ่เกินไป? ความกลัวที่นี่คือ (ฉันถือว่า) awk เรียกใช้ระบบจากหน่วยความจำระบบเพื่อสร้างแฮชหรือมีข้อ จำกัด อื่น ๆ หรือไม่?
rogerdpack

สำหรับผู้ติดตามยังมีตัวเลือกอื่น ๆ ที่ก้าวร้าวกว่าในการ "ฆ่าเชื้อ" เส้น (เนื่องจากการเปรียบเทียบจะต้องใช้อาร์เรย์ที่เชื่อมโยงกันอย่างแน่นอน) เช่นunix.stackexchange.com/a/145132/8337
rogerdpack

@rogerdpack: ไฟล์แยกขนาดใหญ่จะต้องใช้แฮชอาร์เรย์ขนาดใหญ่ (และใช้เวลาประมวลผลนาน) "from-this.txt" ขนาดใหญ่จะต้องใช้เวลาประมวลผลที่ยาวนานเท่านั้น
หยุดชั่วคราวจนกว่าจะมีประกาศอีกครั้ง

1
สิ่งนี้ล้มเหลว (กล่าวคือไม่สร้างผลลัพธ์ใด ๆ ) หากexclude-these.txtว่างเปล่า คำตอบของ @ jona-christopher-sahnwaldt ด้านล่างใช้ได้กับกรณีนี้ คุณยังสามารถระบุไฟล์ได้หลายไฟล์เช่น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 (ส่วนใหญ่เป็นการเปลี่ยนแปลงทางไวยากรณ์เช่นการตั้งค่าหมายเลขไฟล์อย่างชัดเจนแทนที่จะเป็น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

การเข้าถึงr[$0]จะสร้างรายการสำหรับบรรทัดนั้นโดยไม่จำเป็นต้องตั้งค่า

สมมติว่า awk ใช้ตารางแฮชที่มีการค้นหาคงที่และ (โดยเฉลี่ย) เวลาอัปเดตคงที่ความซับซ้อนของเวลาจะเป็น O (n + m) โดยที่ n และ m คือความยาวของไฟล์ ในกรณีของฉัน n คือ ~ 25 ล้านและ m ~ 14000 โซลูชัน awk เร็วกว่าการเรียงลำดับมากและฉันก็ชอบที่จะรักษาคำสั่งเดิม


สิ่งนี้แตกต่างจากคำตอบของ Dennis Williamson อย่างไร? ความแตกต่างเพียงอย่างเดียวที่ไม่ได้ทำการมอบหมายลงในแฮชเร็วกว่านี้เล็กน้อยหรือไม่? ความซับซ้อนของอัลกอริทึมเหมือนกับของเขาหรือไม่?
rogerdpack

ความแตกต่างส่วนใหญ่เป็นวากยสัมพันธ์ ฉันพบว่าตัวแปรนั้นfชัดเจนกว่าNR == FNRแต่นั่นเป็นเรื่องของรสนิยม การมอบหมายลงในแฮชควรเร็วมากจนไม่มีความแตกต่างของความเร็วที่วัดได้ระหว่างสองเวอร์ชัน ฉันคิดว่าฉันคิดผิดเกี่ยวกับความซับซ้อน - ถ้าการค้นหาคงที่การอัปเดตก็ควรจะคงที่เช่นกัน (โดยเฉลี่ย) ฉันไม่รู้ว่าทำไมฉันถึงคิดว่าการอัปเดตเป็นลอการิทึม ฉันจะแก้ไขคำตอบของฉัน
jcsahnwaldt Reinstate Monica

ฉันลองใช้คำตอบเหล่านี้มากมายและคำตอบนี้คือ AMAZEBALLS อย่างรวดเร็ว ฉันมีไฟล์ที่มีหลายแสนบรรทัด ทำงานอย่างมีเสน่ห์!
นาย T

1
นี่คือทางออกที่ฉันต้องการ 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จะทำงานร่วมกับหลายไฟล์และยังว่างยกเว้นไฟล์เช่น ในขณะที่awkโซลูชันอื่นล้มเหลวด้วยไฟล์แยกที่ว่างเปล่าและสามารถรับได้เพียงไฟล์เดียว
Graham Russell

5

ถ้าคุณมี Ruby (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) หากคุณต้องการทราบเกี่ยวกับประสิทธิภาพนี่คือเวอร์ชันอื่น

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

ซึ่งใช้แฮชเพื่อให้มีผลต่อการลบดังนั้นความซับซ้อน O (n) (ขนาดของ a) + O (n) (ขนาดของ b)

นี่คือเกณฑ์มาตรฐานเล็กน้อยโดยได้รับความอนุเคราะห์จาก user576875 แต่มีเส้น 100K จากข้างต้น:

$ 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 ถูกใช้เพื่อแสดงว่าไม่มีความแตกต่างระหว่าง 2 ไฟล์ที่สร้างขึ้น


1
สิ่งนี้มีความซับซ้อน O (n²) และจะเริ่มใช้เวลาหลายชั่วโมงในการดำเนินการให้เสร็จสมบูรณ์เมื่อไฟล์มี K มากกว่าสองสามบรรทัด
Arnaud Le Blanc

ฉันไม่ค่อยสนใจในช่วงหัวเลี้ยวหัวต่อนี้เพราะเขาไม่ได้พูดถึงไฟล์ขนาดใหญ่ใด ๆ
kurumi

3
ไม่จำเป็นต้องมีการป้องกันไม่ใช่ว่า @ user576875 ลดคะแนนคำตอบของคุณหรืออะไรก็ตาม :-)
John Parker

รุ่นสองที่ดีมากทับทิมชนะ :)
Arnaud Le Blanc

4

การเปรียบเทียบเวลาระหว่างคำตอบอื่น ๆ :

$ 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 ไม่ใช่ความแตกต่างแบบสมมาตรเพราะจะลบบรรทัดที่ปรากฏหลายครั้งในไฟล์ใดไฟล์หนึ่ง

comm ยังสามารถใช้กับ stdin และสตริงที่นี่:

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

2

ดูเหมือนจะเป็นงานที่เหมาะสำหรับเชลล์ SQLite:

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


0

ไม่ได้เป็น 'การเขียนโปรแกรมคำตอบ แต่นี่เป็นวิธีที่รวดเร็วและสกปรก: เพียงแค่ไปที่http://www.listdiff.com/compare-2-lists-difference-tool

เห็นได้ชัดว่าจะไม่ทำงานกับไฟล์ขนาดใหญ่ แต่มันก็เป็นเคล็ดลับสำหรับฉัน หมายเหตุ:

  • ฉันไม่ได้มีส่วนเกี่ยวข้องกับเว็บไซต์ แต่อย่างใด (หากคุณยังไม่เชื่อฉันคุณสามารถค้นหาเครื่องมืออื่นทางออนไลน์ได้ฉันใช้คำค้นหา "set difference list online")
  • ดูเหมือนว่าเว็บไซต์ที่เชื่อมโยงจะทำการโทรผ่านเครือข่ายในการเปรียบเทียบทุกรายการดังนั้นอย่าให้ข้อมูลที่ละเอียดอ่อนใด ๆ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.