Cách nhanh chóng để tìm dòng trong một tập tin mà không phải trong một tập tin khác?


241

Tôi có hai tệp lớn (bộ tên tệp). Khoảng 30.000 dòng trong mỗi tệp. Tôi đang cố gắng tìm một cách nhanh chóng để tìm các dòng trong tệp1 không có trong tệp2.

Ví dụ: nếu đây là tệp1:

line1
line2
line3

Và đây là file2:

line1
line4
line5

Sau đó, kết quả / đầu ra của tôi sẽ là:

line2
line3

Những công việc này:

grep -v -f file2 file1

Nhưng nó rất, rất chậm khi được sử dụng trên các tệp lớn của tôi.

Tôi nghi ngờ có một cách tốt để làm điều này bằng cách sử dụng diff (), nhưng đầu ra chỉ là các dòng, không có gì khác và dường như tôi không thể tìm thấy một công tắc cho điều đó.

Bất cứ ai có thể giúp tôi tìm một cách nhanh chóng để làm điều này, bằng cách sử dụng nhị phân bash và linux cơ bản?

EDIT: Để theo dõi câu hỏi của riêng tôi, đây là cách tốt nhất mà tôi đã tìm thấy cho đến nay bằng cách sử dụng diff ():

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

Chắc chắn, phải có một cách tốt hơn?


1
bạn có thể thử điều này nếu nó nhanh hơn:awk 'NR==FNR{a[$0];next}!($0 in a)' file2 file1 > out.txt
Kent


4
Cảm ơn bạn đã nói về grep -v -f file2 file1
Rahul Prasad


Cách đơn giản với bộ công cụ rút gọn : cat file1 file2 file2 | sort | uniq --unique, xem câu trả lời của tôi dưới đây.
Ondra Žižka

Câu trả lời:


233

Bạn có thể đạt được điều này bằng cách kiểm soát định dạng của các dòng cũ / mới / không thay đổi trong diffđầu ra GNU :

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

Các tập tin đầu vào nên được sắp xếp để làm việc này. Với bash(và zsh) bạn có thể sắp xếp tại chỗ với quy trình thay thế <( ):

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

Trong các dòng mớikhông thay đổi ở trên bị triệt tiêu, do đó chỉ thay đổi (tức là các dòng bị loại bỏ trong trường hợp của bạn) là đầu ra. Bạn cũng có thể sử dụng một vài difftùy chọn mà các giải pháp khác không cung cấp, chẳng hạn như -ibỏ qua trường hợp, hoặc tùy chọn khoảng trắng khác nhau ( -E, -b, -vvv) cho phù hợp ít nghiêm ngặt.


Giải trình

Các tùy chọn --new-line-format, --old-line-format--unchanged-line-formatcho phép bạn kiểm soát cách các diffđịnh dạng khác nhau, tương tự như printfđịnh dạng specifiers. Các tùy chọn này định dạng các dòng mới (đã thêm), (đã xóa) và không thay đổi . Đặt một thành trống "" sẽ ngăn đầu ra của loại đường đó.

Nếu bạn quen thuộc với định dạng diff hợp nhất , bạn có thể tạo lại một phần với:

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

Công %Lcụ xác định là dòng đang được đề cập và chúng tôi đặt tiền tố cho mỗi dấu "+" "-" hoặc "" diff -u (lưu ý rằng nó chỉ tạo ra sự khác biệt, nó thiếu --- +++@@các dòng ở đầu mỗi thay đổi được nhóm). Bạn cũng có thể sử dụng để làm những việc hữu ích khác như số lượng mỗi dòng với %dn.


Các diffphương pháp (cùng với các đề xuất khác commjoin) chỉ sản xuất các đầu ra mong đợi với sắp xếp đầu vào, mặc dù bạn có thể sử dụng <(sort ...)để sắp xếp tại chỗ. Đây là một awktập lệnh đơn giản (nawk) (lấy cảm hứng từ các tập lệnh được liên kết đến trong câu trả lời của Konsolebox) chấp nhận các tệp đầu vào được đặt hàng tùy ý xuất ra các dòng bị thiếu theo thứ tự chúng xảy ra trong tệp1.

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

Điều này lưu trữ toàn bộ nội dung của dòng file1 theo dòng trong một mảng được lập chỉ mục số dòng ll1[]và toàn bộ nội dung của dòng2 theo từng dòng trong một mảng kết hợp được lập chỉ mục nội dung dòng ss2[]. Sau khi cả hai tệp được đọc, lặp đi lặp lại ll1và sử dụng intoán tử để xác định xem dòng trong tệp1 có trong tệp2 không. (Điều này sẽ có đầu ra khác với diffphương thức nếu có trùng lặp.)

Trong trường hợp các tệp đủ lớn để lưu trữ cả hai đều gây ra vấn đề về bộ nhớ, bạn có thể trao đổi CPU cho bộ nhớ bằng cách chỉ lưu trữ tệp1 và xóa các kết quả trùng khớp trên đường đi khi tệp 2 được đọc.

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

Ở trên lưu trữ toàn bộ nội dung của tệp1 trong hai mảng, một mảng được lập chỉ mục theo số dòng ll1[], một được lập chỉ mục theo nội dung dòng ss1[]. Sau đó, khi file2 được đọc, mỗi dòng khớp sẽ bị xóa khỏi ll1[]ss1[]. Cuối cùng, các dòng còn lại từ file1 là đầu ra, giữ nguyên thứ tự ban đầu.

Trong trường hợp này, với sự cố như đã nêu, bạn cũng có thể phân chia và chinh phục bằng GNU split(lọc là phần mở rộng GNU), chạy lặp lại với các đoạn của tệp1 và đọc tệp 2 hoàn toàn mỗi lần:

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

Lưu ý việc sử dụng và vị trí của -ý nghĩa stdintrên gawkdòng lệnh. Điều này được cung cấp bởi splittừ file1 trong khối 20000 dòng trên mỗi lần gọi.

Đối với người dùng trên các hệ thống phi GNU, có gần như chắc chắn một coreutils GNU gói bạn có thể có được, kể cả trên OSX như một phần của của Apple Xcode công cụ cung cấp GNU diff, awkmặc dù chỉ là một POSIX / BSD splitchứ không phải là một phiên bản GNU.


1
Điều này thực hiện chính xác những gì tôi cần, trong một phần rất nhỏ thời gian của grep khổng lồ. Cảm ơn!
Niels2000

1
Tìm thấy trang chủ gnu
Juto

một số người trong chúng ta không có trên gnu [OS X bsd ở đây ...] :)
rogerdpack 27/215

1
Tôi giả sử bạn có nghĩa là diff: nói chung các tệp đầu vào sẽ khác nhau, 1 được trả về difftrong trường hợp đó. Hãy coi đó là một phần thưởng ;-) Nếu bạn đang thử nghiệm trong tập lệnh shell 0 và 1 là các mã thoát dự kiến, 2 chỉ ra một vấn đề.
mr.spuratic

1
@ mr.spuratic ah yeah, bây giờ tôi tìm thấy nó trong man diff. Cảm ơn!
Archeosudoerus

242

Lệnh comm (viết tắt của "common") có thể hữu íchcomm - 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 

Các mantập tin thực sự khá dễ đọc cho việc này.


6
Hoạt động hoàn hảo trên OSX.
pisaruk

40
Yêu cầu cho đầu vào được sắp xếp có lẽ nên được làm nổi bật.
tripleee

20
commcũng có một tùy chọn để xác minh đầu vào được sắp xếp, --check-order(điều này dường như vẫn xảy ra, nhưng tùy chọn này sẽ khiến nó bị lỗi thay vì tiếp tục). Nhưng để sắp xếp các tệp, chỉ cần thực hiện: com -23 <(sort file1) <(sort file2)vân vân
michael

Tôi đã so sánh một tệp được tạo trong Windows với một tệp được tạo trong Linux và có vẻ như nó commkhông hoạt động. Phải mất một thời gian tôi mới nhận ra rằng đó là về các kết thúc dòng: ngay cả các dòng trông giống hệt nhau được coi là khác nhau nếu chúng có các kết thúc dòng khác nhau. Lệnh này dos2unixcó thể được sử dụng để chuyển đổi các kết thúc dòng CRLF thành LF.
ZeroOne

23

Giống như konsolebox đề xuất, giải pháp áp phích grep

grep -v -f file2 file1

thực sự hoạt động rất tốt (nhanh) nếu bạn chỉ cần thêm -Ftùy chọn, để coi các mẫu là các chuỗi cố định thay vì các biểu thức thông thường. Tôi đã xác minh điều này trên một cặp ~ 1000 danh sách tệp mà tôi phải so sánh. Với -Fnó mất 0,031 giây (thực), trong khi không mất 2,278 giây (thực), khi chuyển hướng đầu ra grep sang wc -l.

Các thử nghiệm này cũng bao gồm công -xtắc, là phần cần thiết của giải pháp nhằm đảm bảo độ chính xác hoàn toàn trong trường hợp tệp 2 chứa các dòng khớp với một phần, nhưng không phải tất cả, một hoặc nhiều dòng trong tệp1.

Vì vậy, một giải pháp không yêu cầu sắp xếp các đầu vào, nhanh chóng, linh hoạt (độ nhạy trường hợp, v.v.) là:

grep -F -x -v -f file2 file1

Điều này không hoạt động với tất cả các phiên bản của grep, ví dụ như nó bị lỗi trong macOS, trong đó một dòng trong tệp 1 sẽ được hiển thị là không có trong tệp 2, mặc dù vậy, nếu nó phù hợp với một dòng khác là một chuỗi con của nó . Ngoài ra, bạn có thể cài đặt GNU grep trên macOS để sử dụng giải pháp này.


Vâng, nó hoạt động nhưng ngay cả với -Fđiều này không quy mô tốt.
Molomby

Điều này không nhanh như vậy, tôi đã đợi 5 phút cho 2 tệp ~ 500 nghìn dòng trước khi từ bỏ
cahen

trên thực tế, cách này vẫn chậm hơn so với comm, vì cách này có thể xử lý các tệp chưa được sắp xếp do đó bị kéo xuống bằng cách sắp xếp, comm tận dụng lợi thế của việc sắp xếp
workplaylifecycle

@workplaylifecycle Bạn cần thêm thời gian để sắp xếp có thể là nút cổ chai cực lớn file2.
rwst

Tuy nhiên, grep với -xtùy chọn rõ ràng sử dụng nhiều bộ nhớ hơn. Với file2180 triệu từ 6-10 byte chứa, quy trình của tôi đã có Killedtrên máy RAM 32 GB ...
vào

11

tốc độ như sắp xếp và khác biệt là gì?

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

1
Cảm ơn đã nhắc nhở tôi về sự cần thiết phải sắp xếp các tập tin trước khi làm khác. sắp xếp + diff là NHIỀU nhanh hơn.
Niels2000

4
một lớp lót ;-) diff <(sort file1 -u) <(sort file2 -u)
steveinatorx

11

Nếu bạn đang thiếu "công cụ ưa thích", ví dụ như trong một số bản phân phối Linux tối thiểu, có một giải pháp với chỉ cat, sortuniq:

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

Kiểm tra:

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    

Điều này cũng tương đối nhanh, so với grep.


1
Lưu ý - một số triển khai sẽ không nhận ra --uniquetùy chọn. Bạn sẽ có thể sử dụng tùy chọn POSIX được tiêu chuẩn hóa cho việc này:| uniq -u
AndrewF

1
Trong ví dụ, "2" đến từ đâu?
Niels2000

1
@ Niels2000, seq 1 1 7tạo các số từ 1, với số tăng 1, cho đến 7, tức là 1 2 3 4 5 6 7. Và ngay đó là số 2 của bạn!
Eirik Lygre

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

Các -tđảm bảo rằng nó sẽ so sánh toàn bộ dòng, nếu bạn đã có một không gian trong một số dòng.


Giống như comm, joinyêu cầu cả hai dòng đầu vào được sắp xếp trên trường bạn đang thực hiện thao tác nối trên.
tripleee

4

Bạn có thể sử dụng Python:

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

Sử dụng combinetừ moreutilsgói, một tiện ích bộ mà hỗ trợ not, and, or, xorhoạt động

combine file1 not file2

tức là cho tôi các dòng trong file1 nhưng không phải trong file2

HOẶC cho tôi các dòng trong file1 trừ các dòng trong file2

Lưu ý: combine sắp xếp và tìm các dòng duy nhất trong cả hai tệp trước khi thực hiện bất kỳ thao tác nào nhưng diffkhông. Vì vậy, bạn có thể tìm thấy sự khác biệt giữa đầu ra của diffcombine.

Vì vậy, trong thực tế, bạn đang nói

Tìm các dòng riêng biệt trong file1 và file2 và sau đó cho tôi các dòng trong file1 trừ các dòng trong file2

Theo kinh nghiệm của tôi, nó nhanh hơn nhiều so với các tùy chọn khác


2

Sử dụng tùy chọn fgrep hoặc thêm -F vào grep có thể giúp ích. Nhưng để tính toán nhanh hơn, bạn có thể sử dụng Awk.

Bạn có thể thử một trong những phương pháp Awk sau:

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


2
+1 Đây là câu trả lời duy nhất không yêu cầu sắp xếp đầu vào. Mặc dù rõ ràng OP hài lòng với yêu cầu đó, nhưng đó là một hạn chế không thể chấp nhận được trong nhiều tình huống thực tế.
tripleee

1

Cách tôi thường làm là sử dụng --suppress-common-linescờ, mặc dù lưu ý rằng điều này chỉ hoạt động nếu bạn thực hiện nó ở định dạng song song.

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


0

Tôi thấy rằng đối với tôi bằng cách sử dụng một câu lệnh if và for loop bình thường hoạt động hoàn hảo.

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
Xem DontReadLinesWithFor . Ngoài ra, mã này sẽ hành xử rất tệ nếu bất kỳ grepkết quả nào của bạn mở rộng thành nhiều từ hoặc nếu bất kỳ file2mục nào của bạn có thể được xử lý bằng shell như một quả địa cầu.
Charles Duffy
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.