ฉันมีไฟล์f1
:
line1
line2
line3
line4
..
..
ฉันต้องการลบบรรทัดทั้งหมดที่อยู่ในไฟล์อื่นf2
:
line2
line8
..
..
ฉันได้ลองทำอะไรบางอย่างcat
และsed
ไม่ได้ใกล้เคียงกับที่ฉันตั้งใจ ฉันจะทำเช่นนี้ได้อย่างไร?
ฉันมีไฟล์f1
:
line1
line2
line3
line4
..
..
ฉันต้องการลบบรรทัดทั้งหมดที่อยู่ในไฟล์อื่นf2
:
line2
line8
..
..
ฉันได้ลองทำอะไรบางอย่างcat
และsed
ไม่ได้ใกล้เคียงกับที่ฉันตั้งใจ ฉันจะทำเช่นนี้ได้อย่างไร?
คำตอบ:
grep -v -x -f f2 f1
ควรทำเคล็ดลับ
คำอธิบาย:
-v
เพื่อเลือกบรรทัดที่ไม่ตรงกัน-x
เพื่อจับคู่ทั้งบรรทัดเท่านั้น-f f2
เพื่อรับรูปแบบจาก f2
เราสามารถใช้แทนgrep -F
หรือfgrep
เพื่อจับคู่สตริงคงที่จากf2
แทนที่จะเป็นรูปแบบ (ในกรณีที่คุณต้องการลบเส้นในลักษณะ "สิ่งที่คุณเห็นว่าคุณได้รับอะไร" แทนที่จะใช้เส้นf2
เป็นรูปแบบนิพจน์ทั่วไป)
grep
มันขึ้นอยู่กับการดำเนินงานของ หากประมวลผลล่วงหน้าf2
อย่างถูกต้องก่อนที่จะเริ่มค้นหาการค้นหาจะใช้เวลา O (n) เท่านั้น
ลองใช้ comm แทน (สมมติว่า f1 และ f2 "เรียงลำดับแล้ว")
comm -2 -3 f1 f2
comm
วิธีแก้ปัญหาคือคำถามที่ไม่ได้ระบุว่าf1
มีการเรียงลำดับบรรทัดที่จำเป็นต้องใช้comm
comm -2 -3 <(sort f1) <(sort f2)
สำหรับการยกเว้นไฟล์ที่ไม่ใหญ่เกินไปคุณสามารถใช้อาร์เรย์ที่เชื่อมโยงของ 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)
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
คล้ายกับคำตอบของ 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 เร็วกว่าการเรียงลำดับมากและฉันก็ชอบที่จะรักษาคำสั่งเดิม
f
ชัดเจนกว่าNR == FNR
แต่นั่นเป็นเรื่องของรสนิยม การมอบหมายลงในแฮชควรเร็วมากจนไม่มีความแตกต่างของความเร็วที่วัดได้ระหว่างสองเวอร์ชัน ฉันคิดว่าฉันคิดผิดเกี่ยวกับความซับซ้อน - ถ้าการค้นหาคงที่การอัปเดตก็ควรจะคงที่เช่นกัน (โดยเฉลี่ย) ฉันไม่รู้ว่าทำไมฉันถึงคิดว่าการอัปเดตเป็นลอการิทึม ฉันจะแก้ไขคำตอบของฉัน
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
โซลูชันอื่นล้มเหลวด้วยไฟล์แยกที่ว่างเปล่าและสามารถรับได้เพียงไฟล์เดียว
ถ้าคุณมี 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 ไฟล์ที่สร้างขึ้น
การเปรียบเทียบเวลาระหว่างคำตอบอื่น ๆ :
$ 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
ดูเหมือนจะเป็นงานที่เหมาะสำหรับเชลล์ 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
คุณลองนี้กับ sed?
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
ไม่ได้เป็น 'การเขียนโปรแกรมคำตอบ แต่นี่เป็นวิธีที่รวดเร็วและสกปรก: เพียงแค่ไปที่http://www.listdiff.com/compare-2-lists-difference-tool
เห็นได้ชัดว่าจะไม่ทำงานกับไฟล์ขนาดใหญ่ แต่มันก็เป็นเคล็ดลับสำหรับฉัน หมายเหตุ: