Lệnh Unix để tìm các dòng phổ biến trong hai tệp


179

Tôi chắc chắn rằng tôi đã từng tìm thấy một lệnh unix có thể in các dòng chung từ hai hoặc nhiều tệp, có ai biết tên của nó không? Nó đơn giản hơn nhiều diff.


5
Các câu trả lời cho câu hỏi này không nhất thiết là những gì mọi người sẽ muốn, vì commyêu cầu các tệp đầu vào được sắp xếp. Nếu bạn chỉ muốn phổ biến từng dòng một, nó thật tuyệt. Nhưng nếu bạn muốn cái mà tôi gọi là "chống khác biệt", commthì đừng làm.
Robert P. Goldman

@ RobertP.Goldman có một cách để có được sự phổ biến giữa hai tệp khi tệp1 chứa một phần mẫu như pr-123-xy-45và tệp2 chứa ec11_orop_pr-123-xy-45.gz. Tôi cần file3 chứaec11_orop_pr-123-xy-45.gz
Chandan Choudhury

Xem phần này để sắp xếp các tệp văn bản theo từng dòng
y2k-shubham

Câu trả lời:


216

Lệnh bạn đang tìm kiếm là comm. ví dụ:-

comm -12 1.sorted.txt 2.sorted.txt

Đây:

-1 : chặn cột 1 (dòng duy nhất thành 1.sort.txt)

-2 : chặn cột 2 (các dòng duy nhất thành 2.sort.txt)


27
Cách sử dụng thông thường: comm -12 1.sort.txt 2.sort.txt
Fedir RYKHTIK

45
Trong khi comm cần các tệp được sắp xếp, bạn có thể lấy grep -f file1 file2 để lấy các dòng chung của cả hai tệp.
Ferdy

2
@ferdy (Lặp lại nhận xét của tôi từ câu trả lời của bạn, vì về cơ bản là câu trả lời lặp đi lặp lại được đăng dưới dạng nhận xét) grepthực hiện một số điều kỳ lạ mà bạn có thể không mong đợi. Cụ thể, mọi thứ trong 1.txtsẽ được hiểu là một biểu thức thông thường và không phải là một chuỗi đơn giản. Ngoài ra, bất kỳ dòng trống trong 1.txtsẽ phù hợp với tất cả các dòng trong 2.txt. Vì vậy, grepsẽ chỉ làm việc trong các tình huống rất cụ thể. Ít nhất bạn muốn sử dụng fgrep(hoặc grep -f) nhưng điều trống có lẽ sẽ tàn phá quá trình này.
Christopher Schultz

11
Xem câu trả lời của Ferdy dưới đây, và nhận xét của Christopher Schultz và tôi về nó. TL; DR - sử dụng . grep -F -x -f file1 file2
Jonathan Leffler

1
@bapors: Tôi đã cung cấp một câu hỏi và trả lời tự trả lời là Làm thế nào để lấy đầu ra từ commlệnh thành 3 tệp riêng biệt? Câu trả lời là quá lớn để phù hợp thoải mái ở đây.
Jonathan Leffler

62

Để dễ dàng áp dụng lệnh comm cho các tệp chưa sắp xếp , hãy sử dụng thay thế quy trình của Bash :

$ bash --version
GNU bash, version 3.2.51(1)-release
Copyright (C) 2007 Free Software Foundation, Inc.
$ cat > abc
123
567
132
$ cat > def
132
777
321

Vì vậy, các tập tin abc và def có một dòng chung, một dòng có "132". Sử dụng comm trên các tệp chưa được sắp xếp:

$ comm abc def
123
    132
567
132
    777
    321
$ comm -12 abc def # No output! The common line is not found
$

Dòng cuối cùng không tạo ra đầu ra, dòng chung không được phát hiện.

Bây giờ sử dụng comm trên các tệp được sắp xếp, sắp xếp các tệp với quá trình thay thế:

$ comm <( sort abc ) <( sort def )
123
            132
    321
567
    777
$ comm -12 <( sort abc ) <( sort def )
132

Bây giờ chúng tôi đã có dòng 132!


2
như vậy ... sort abc > abc.sorted, sort dev > def.sortedvà sau đó comm -12 abc.sorted def.sorted?
Nikana Reklawyks

1
@NikanaReklawyks Và sau đó nhớ xóa các tệp tạm thời sau đó và đối phó với việc dọn dẹp trong trường hợp có lỗi. Trong nhiều trường hợp, quá trình thay thế cũng sẽ nhanh hơn rất nhiều vì bạn có thể tránh I / O của đĩa miễn là kết quả phù hợp với bộ nhớ.
tripleee

29

Để bổ sung cho lớp lót Perl, đây là awktương đương:

awk 'NR==FNR{arr[$0];next} $0 in arr' file1 file2

Điều này sẽ đọc tất cả các dòng từ file1vào mảng arr[], và sau đó kiểm tra từng dòng trong file2nếu nó đã tồn tại trong mảng (tức là file1). Các dòng được tìm thấy sẽ được in theo thứ tự xuất hiện file2. Lưu ý rằng so sánh in arrsử dụng toàn bộ dòng từ file2dưới dạng chỉ mục đến mảng, do đó, nó sẽ chỉ báo cáo kết quả khớp chính xác trên toàn bộ dòng.


2
Đây là câu trả lời chính xác. Không ai trong số những người khác có thể được thực hiện để làm việc nói chung (tôi đã không thử perlnhững người khác, bởi vì). Cảm ơn một triệu, cô
entonio

1
Giữ nguyên thứ tự khi hiển thị các dòng chung có thể thực sự hữu ích trong một số trường hợp sẽ loại trừ comm vì điều đó.
tuxayo

1
Trong trường hợp bất kỳ ai cũng muốn làm điều tương tự dựa trên một cột nhất định nhưng không biết awk, chỉ cần thay cả $ 0 bằng $ 5 chẳng hạn cho cột 5 để bạn có được các dòng được chia sẻ trong 2 tệp có cùng từ trong cột 5
FatihSarigol

24

Có lẽ bạn có ý nghĩa comm?

So sánh các tệp được sắp xếp FILE1 và FILE2 theo từng dòng.

Không có tùy chọn, sản xuất đầu ra ba cột. Cột một chứa các dòng duy nhất cho FILE1, cột hai chứa các dòng duy nhất cho FILE2 và cột ba chứa các dòng chung cho cả hai tệp.

Bí mật trong việc tìm kiếm những thông tin này là các trang thông tin. Đối với các chương trình GNU, chúng chi tiết hơn nhiều so với trang người dùng của chúng. Hãy thử info coreutilsvà nó sẽ liệt kê cho bạn tất cả các dụng cụ nhỏ hữu ích.


19

Trong khi

grep -v -f 1.txt 2.txt > 3.txt

cung cấp cho bạn sự khác biệt của hai tệp (những gì trong 2.txt và không phải trong 1.txt), bạn có thể dễ dàng thực hiện

grep -f 1.txt 2.txt > 3.txt

để thu thập tất cả các dòng chung, sẽ cung cấp một giải pháp dễ dàng cho vấn đề của bạn. Nếu bạn đã sắp xếp các tập tin, commdù sao bạn cũng nên dùng . Trân trọng!


2
greplàm một số điều kỳ lạ bạn có thể không mong đợi. Cụ thể, mọi thứ trong 1.txtsẽ được hiểu là một biểu thức thông thường và không phải là một chuỗi đơn giản. Ngoài ra, bất kỳ dòng trống trong 1.txtsẽ phù hợp với tất cả các dòng trong 2.txt. Vì vậy, điều này sẽ chỉ làm việc trong các tình huống rất cụ thể.
Christopher Schultz

13
@ChristopherSchultz: Có thể nâng cấp câu trả lời này để hoạt động tốt hơn bằng cách sử dụng các grepký hiệu POSIX , được hỗ trợ bởi grephầu hết các biến thể Unix hiện đại. Thêm -F(hoặc sử dụng fgrep) để triệt tiêu các biểu thức thông thường. Thêm -x(chính xác) để chỉ khớp toàn bộ dòng.
Jonathan Leffler

Tại sao chúng ta nên dùng commcho các tập tin được sắp xếp?
Ulysse BN

2
@UlysseBN commcó thể hoạt động với các tệp lớn tùy ý miễn là chúng được sắp xếp vì nó chỉ cần giữ ba dòng trong bộ nhớ (tôi đoán GNU commthậm chí sẽ biết chỉ giữ một tiền tố nếu các dòng thực sự dài). Các grepgiải pháp cần phải giữ tất cả các biểu thức tìm kiếm trong bộ nhớ.
tripleee

9

Nếu hai tệp chưa được sắp xếp, bạn có thể sử dụng:

comm -12 <(sort a.txt) <(sort b.txt)

và nó sẽ hoạt động, tránh thông báo lỗi comm: file 2 is not in sorted order khi làm comm -12 a.txt b.txt.


Bạn đúng, nhưng điều này về cơ bản là lặp lại một câu trả lời khác , điều này thực sự không mang lại lợi ích gì. Nếu bạn quyết định trả lời một câu hỏi cũ hơn và có câu trả lời đúng, việc thêm câu trả lời mới vào cuối ngày có thể không giúp bạn nhận được bất kỳ tín dụng nào. Nếu bạn có một số thông tin mới đặc biệt hoặc bạn tin chắc rằng các câu trả lời khác đều sai, bằng mọi cách hãy thêm một câu trả lời mới, nhưng 'một câu trả lời khác' đưa ra thông tin cơ bản tương tự trong một thời gian dài sau khi câu hỏi được hỏi thường thắng ' t kiếm được cho bạn nhiều tín dụng.
Jonathan Leffler

Tôi thậm chí không nhìn thấy câu trả lời này @JonathanLeffler vì phần này nằm ở cuối câu trả lời, trộn lẫn với các yếu tố khác của câu trả lời trước đó. Trong khi câu trả lời khác chính xác hơn, lợi ích của tôi tôi nghĩ là đối với người muốn tìm giải pháp nhanh sẽ chỉ có 2 dòng để đọc. Đôi khi chúng tôi đang tìm kiếm câu trả lời chi tiết và đôi khi chúng tôi đang vội và một câu trả lời sẵn sàng để đọc nhanh là tốt.
Basj

Ngoài ra tôi không quan tâm đến tín dụng / đại diện, tôi đã không đăng cho mục đích này.
Basj

1
Cũng lưu ý rằng cú pháp thay thế quy trình <(command)không khả chuyển sang trình bao POSIX, mặc dù nó hoạt động trong Bash và một số khác.
tripleee

8
perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/'  file1 file2

cái này hoạt động tốt hơn commlệnh khi nó tìm kiếm từng dòng file1trong file2đó commsẽ chỉ so sánh nếu dòng ntrong file1bằng với dòng ntrong file2.
teriiehina

1
@teriiehina: Không; commkhông chỉ đơn giản so sánh dòng N trong tệp1 với dòng N trong tệp2. Nó hoàn toàn có thể quản lý tốt một loạt các dòng được chèn vào một trong hai tệp (tương đương với việc xóa một loạt các dòng khỏi tệp khác, tất nhiên). Nó chỉ đòi hỏi các đầu vào phải được sắp xếp theo thứ tự.
Jonathan Leffler

Tốt hơn so với commcâu trả lời nếu một người muốn giữ trật tự. Tốt hơn là awktrả lời nếu không muốn trùng lặp.
tuxayo

Một lời giải thích có ở đây: stackoverflow.com/questions/17552789/
Kẻ


3

Trên phiên bản giới hạn của Linux (như QNAP (Nas) tôi đang làm việc):

  • comm không tồn tại
  • grep -f file1 file2có thể gây ra một số vấn đề như đã nói bởi @ChristopherSchultz và việc sử dụng grep -F -f file1 file2rất chậm (hơn 5 phút - chưa hoàn thành - hơn 2-3 giây với phương pháp bên dưới trên các tệp trên 20MB)

Vì vậy, đây là những gì tôi đã làm:

sort file1 > file1.sorted
sort file2 > file2.sorted

diff file1.sorted file2.sorted | grep "<" | sed 's/^< *//' > files.diff
diff file1.sorted files.diff | grep "<" | sed 's/^< *//' > files.same.sorted

Nếu files.same.sortedsẽ có cùng thứ tự so với bản gốc, thì hãy thêm dòng này cho cùng thứ tự so với tệp1:

awk 'FNR==NR {a[$0]=$0; next}; $0 in a {print a[$0]}' files.same.sorted file1 > files.same

hoặc, cho cùng một thứ tự so với tệp2:

awk 'FNR==NR {a[$0]=$0; next}; $0 in a {print a[$0]}' files.same.sorted file2 > files.same

2

Chỉ để tham khảo nếu ai đó vẫn đang tìm cách làm điều này cho nhiều tệp, hãy xem câu trả lời được liên kết để Tìm dòng phù hợp trên nhiều tệp.


Kết hợp hai câu trả lời này ( ans1ans2 ), tôi nghĩ bạn có thể nhận được kết quả bạn cần mà không cần sắp xếp các tệp:

#!/bin/bash
ans="matching_lines"

for file1 in *
do 
    for file2 in *
        do 
            if  [ "$file1" != "$ans" ] && [ "$file2" != "$ans" ] && [ "$file1" != "$file2" ] ; then
                echo "Comparing: $file1 $file2 ..." >> $ans
                perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/' $file1 $file2 >> $ans
            fi
         done 
done

Đơn giản chỉ cần lưu nó, cung cấp cho nó quyền thực thi ( chmod +x compareFiles.sh) và chạy nó. Nó sẽ lấy tất cả các tệp có trong thư mục làm việc hiện tại và sẽ tạo ra một so sánh tất cả so với tất cả để lại trong tệp "khớp_lines".

Những điều cần cải thiện:

  • Bỏ qua thư mục
  • Tránh so sánh tất cả các tệp hai lần (file1 vs file2 và file2 vs file1).
  • Có thể thêm số dòng bên cạnh chuỗi phù hợp

-2
rm file3.txt

cat file1.out | while read line1
do
        cat file2.out | while read line2
        do
                if [[ $line1 == $line2 ]]; then
                        echo $line1 >>file3.out
                fi
        done
done

Điều này nên làm điều đó.


1
Bạn có thể nên sử dụng rm -f file3.txtnếu bạn sẽ xóa tệp; sẽ không báo cáo bất kỳ lỗi nào nếu tập tin không tồn tại. OTOH, sẽ không cần thiết nếu tập lệnh của bạn chỉ đơn giản lặp lại với đầu ra tiêu chuẩn, cho phép người dùng tập lệnh chọn nơi đầu ra sẽ đi. Cuối cùng, có lẽ bạn muốn sử dụng $1$2(đối số dòng lệnh) thay vì tên tệp cố định ( file1.outfile2.out). Điều đó rời khỏi thuật toán: nó sẽ bị chậm. Nó sẽ đọc file2.outmột lần cho mỗi dòng trong file1.out. Sẽ chậm nếu các tệp lớn (giả sử nhiều kilobyte).
Jonathan Leffler

Mặc dù điều này có thể hoạt động trên danh nghĩa nếu bạn có đầu vào không chứa bất kỳ siêu ký tự shell nào (gợi ý: xem những cảnh báo nào bạn nhận được từ shellcheck.net ), cách tiếp cận ngây thơ này rất kém hiệu quả. Một công cụ như grep -Fđọc một tệp vào bộ nhớ và sau đó thực hiện một lần chuyển qua các tệp khác để tránh lặp lại liên tục trên cả hai tệp đầu vào.
tripleee
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.