Xóa các dòng khỏi một tệp nằm trong tệp khác


126

Tôi có một tệp f1:

line1
line2
line3
line4
..
..

Tôi muốn xóa tất cả các dòng trong một tệp khác f2:

line2
line8
..
..

Tôi đã thử một cái gì đó với catsed, thậm chí không gần với những gì tôi dự định. Tôi có thể làm cái này như thế nào?



Nếu bạn đang tìm cách xóa các dòng khỏi tệp "thậm chí chứa" các chuỗi khỏi tệp khác (ví dụ: đối sánh từng phần), hãy xem unix.stackexchange.com/questions/145079/…
rogerdpack

Câu trả lời:


154

grep -v -x -f f2 f1 nên làm thủ thuật.

Giải trình:

  • -v để chọn các dòng không khớp
  • -x để khớp toàn bộ dòng
  • -f f2 để lấy mẫu từ f2

Một thay vì có thể sử dụng grep -Fhoặc fgrepđể phù hợp với chuỗi cố định từ f2chứ không phải là mô hình (trong trường hợp bạn muốn loại bỏ các dòng trong một "những gì bạn thấy nếu những gì bạn nhận được" cách hơn là điều trị các dòng trong f2như mẫu regex).


22
Điều này có độ phức tạp O (n²) và sẽ bắt đầu mất hàng giờ để hoàn thành sau khi tệp chứa hơn một vài K dòng.
Arnaud Le Blanc,

11
Việc tìm ra các thuật toán gợi ý SO có độ phức tạp O (n ^ 2) chỉ có độ phức tạp O (n), nhưng vẫn có thể mất hàng giờ để cạnh tranh.
HDave

2
Tôi vừa thử điều này trên 2 tệp có khoảng ~ 2k dòng mỗi tệp và nó đã bị giết bởi hệ điều hành (được, đây là một máy ảo không quá mạnh, nhưng vẫn còn).
Trebor Rude

1
Tôi thích sự sang trọng của nó; Tôi thích tốc độ trả lời của Jona Christopher Sahnwal hơn.
Alex Hall

1
@ arnaud576875: Bạn chắc chứ? Nó phụ thuộc vào việc thực hiện grep. Nếu nó xử lý f2trước đúng cách trước khi bắt đầu tìm kiếm thì việc tìm kiếm sẽ chỉ mất O (n) thời gian.
HelloGoodbye

57

Hãy thử dấu phẩy (giả sử f1 và f2 "đã được sắp xếp")

comm -2 -3 f1 f2

5
Tôi không chắc chắn commlà giải pháp có câu hỏi không chỉ ra rằng các dòng trong f1đều được sắp xếp mà là một điều kiện tiên quyết để sử dụngcomm
gabuzo

1
Điều này hiệu quả với tôi, vì các tệp của tôi đã được sắp xếp và có hơn 250.000 dòng ở một trong số chúng, chỉ có 28.000 ở dòng còn lại. Cảm ơn!
Mùa đông

1
Khi điều này hoạt động (các tệp đầu vào được sắp xếp), điều này cực kỳ nhanh chóng!
Mike Jarvis

Như trong giải pháp của arnaud576875, đối với tôi khi sử dụng cygwin, điều này đã loại bỏ các dòng trùng lặp trong tệp thứ hai có thể muốn được giữ lại.
Alex Hall

9
Bạn có thể sử dụng thay thế tiến trình để sắp xếp các tập tin đầu tiên, tất nhiên:comm -2 -3 <(sort f1) <(sort f2)
davemyron

14

Để loại trừ các tệp không quá lớn, bạn có thể sử dụng các mảng liên kết của AWK.

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

Đầu ra sẽ có cùng thứ tự với tệp "from-this.txt". Các tolower()chức năng làm cho nó case-insensitive, nếu bạn cần điều đó.

Độ phức tạp của thuật toán có thể sẽ là O (n) (loại trừ-this.txt size) + O (n) (from-this.txt size)


Tại sao bạn lại nói rằng các tệp không quá lớn? Nỗi sợ hãi ở đây là (tôi giả sử) chạy hệ thống ra khỏi bộ nhớ hệ thống để tạo băm, hoặc có một số hạn chế khác?
rogerdpack

cho tín đồ, có những lựa chọn tích cực hơn thậm chí khác để "Sanitize" các dòng (kể từ khi so sánh phải là chính xác để sử dụng các mảng kết hợp), cựu unix.stackexchange.com/a/145132/8337
rogerdpack

@rogerdpack: Một tệp loại trừ lớn sẽ yêu cầu một mảng băm lớn (và thời gian xử lý lâu). Một "from-this.txt" lớn sẽ chỉ yêu cầu một thời gian xử lý dài.
Tạm dừng cho đến khi có thông báo mới.

1
Điều này không thành công (tức là không tạo ra bất kỳ đầu ra nào) nếu exclude-these.txttrống. Câu trả lời của @ jona-christopher-sahnwaldt dưới đây phù hợp trong trường hợp này. Bạn cũng có thể chỉ định file nhiều ví dụ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

Tương tự như câu trả lời của Dennis Williamson (hầu hết là các thay đổi về cú pháp, ví dụ: đặt số tệp một cách rõ ràng thay vì NR == FNRthủ thuật):

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

Việc truy cập r[$0]sẽ tạo mục nhập cho dòng đó, không cần đặt giá trị.

Giả sử awk sử dụng bảng băm với thời gian tra cứu liên tục và (trung bình) không đổi, thì độ phức tạp về thời gian của bảng này sẽ là O (n + m), trong đó n và m là độ dài của tệp. Trong trường hợp của tôi, n là ~ 25 triệu và m ~ 14000. Giải pháp awk nhanh hơn nhiều so với sắp xếp và tôi cũng thích giữ thứ tự ban đầu hơn.


Câu trả lời này khác với câu trả lời của Dennis Williamson như thế nào? Có phải sự khác biệt duy nhất là nó không thực hiện việc gán vào hàm băm, nhanh hơn một chút so với điều này không? Độ phức tạp của thuật toán giống như của anh ta?
rogerdpack

Sự khác biệt chủ yếu là cú pháp. Tôi thấy biến frõ ràng hơn NR == FNR, nhưng đó là vấn đề của thị hiếu. Việc gán hàm băm phải nhanh đến mức không có sự khác biệt về tốc độ có thể đo lường được giữa hai phiên bản. Tôi nghĩ rằng tôi đã sai về độ phức tạp - nếu tra cứu là không đổi, cập nhật cũng phải không đổi (trung bình). Tôi không biết tại sao tôi nghĩ cập nhật sẽ là logarit. Tôi sẽ chỉnh sửa câu trả lời của mình.
jcsahnwaldt Phục hồi Monica

Tôi đã thử một loạt các câu trả lời và câu trả lời này nhanh chóng AMAZEBALLS. Tôi đã có các tập tin với hàng trăm nghìn dòng. Làm việc như người ở!
Ông T

1
Đây là giải pháp ưa thích của tôi. Nó hoạt động với nhiều tệp và cũng có các tệp loại trừ trống, ví dụ 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. Trong khi awkgiải pháp khác không thành công với tệp loại trừ trống và chỉ có thể lấy một tệp.
Graham Russell

5

nếu bạn có 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

Trong đó có độ phức tạp O (N ^ 2). Nếu bạn muốn quan tâm đến hiệu suất, đây là một phiên bản khác

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

sử dụng một hàm băm để thực hiện phép trừ, độ phức tạp O (n) (kích thước của a) + O (n) (kích thước của b) cũng vậy

đây là một điểm chuẩn nhỏ, được sự cho phép của người dùng576875, nhưng với 100K dòng, ở trên:

$ 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 được sử dụng để cho thấy không có sự khác biệt giữa 2 tệp được tạo.


1
Điều này có độ phức tạp O (n²) và sẽ bắt đầu mất hàng giờ để hoàn thành sau khi tệp chứa hơn một vài K dòng.
Arnaud Le Blanc

Tôi không thực sự quan tâm đến thời điểm này, bởi vì anh ấy không đề cập đến bất kỳ tệp lớn nào.
kurumi

3
Không cần phải phòng thủ như vậy, không có nghĩa là nếu @ user576875 từ chối câu trả lời của bạn hoặc bất cứ điều gì. :-)
John Parker

rất đẹp phiên bản thứ hai, chiến thắng ruby :)
Arnaud Le Blanc

4

Một số so sánh về thời gian giữa các câu trả lời khác nhau:

$ 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 thậm chí không phải là một sự khác biệt đối xứng, bởi vì nó loại bỏ các dòng xuất hiện nhiều lần trong một trong hai tệp.

comm cũng có thể được sử dụng với chuỗi stdin và đây:

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

2

Có vẻ là một công việc phù hợp với SQLite shell:

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

Bạn đã thử điều này với 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

0

Không phải là câu trả lời 'lập trình' nhưng đây là một giải pháp nhanh chóng và hiệu quả: chỉ cần truy cập http://www.listdiff.com/compare-2-lists-difference-tool .

Rõ ràng là sẽ không hoạt động đối với các tệp lớn nhưng nó đã giúp tôi. Một số lưu ý:

  • Tôi không liên kết với trang web theo bất kỳ cách nào (nếu bạn vẫn không tin tôi, thì bạn có thể tìm kiếm một công cụ khác trực tuyến; tôi đã sử dụng cụm từ tìm kiếm "đặt danh sách khác biệt trực tuyến")
  • Trang web được liên kết dường như thực hiện các cuộc gọi mạng trên mọi so sánh danh sách, vì vậy đừng cung cấp cho nó bất kỳ dữ liệu nhạy cảm nào
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.