คำสั่ง Unix เพื่อค้นหาบรรทัดที่พบบ่อยในสองไฟล์


179

ฉันแน่ใจว่าฉันเคยพบคำสั่ง unix ซึ่งสามารถพิมพ์บรรทัดทั่วไปจากไฟล์สองไฟล์ขึ้นไปไม่มีใครรู้จักชื่อของมัน มันง่ายกว่าdiffมาก


5
คำตอบสำหรับคำถามนี้ไม่จำเป็นต้องเป็นสิ่งที่ทุกคนต้องการเนื่องจากcommต้องใช้ไฟล์อินพุตที่เรียงลำดับ หากคุณต้องการเพียงแค่บรรทัดต่อบรรทัดมันยอดเยี่ยม แต่ถ้าคุณต้องการสิ่งที่ฉันจะเรียกว่า "ต่อต้านแตกต่าง" commไม่ทำงาน
Robert P. Goldman

@ RobertP.Goldman จะมีวิธีที่จะได้รับร่วมกันระหว่างสองไฟล์เมื่อ file1 มีรูปแบบบางส่วนเหมือนpr-123-xy-45และ file2 ec11_orop_pr-123-xy-45.gzมี ฉันต้องการ file3 ที่มีec11_orop_pr-123-xy-45.gz
Chandan Choudhury

ดูสิ่งนี้สำหรับการจัดเรียงไฟล์ข้อความแบบทีละบรรทัด
y2k-shubham

คำตอบ:


216

commคำสั่งที่คุณกำลังมองหาคือ เช่น:-

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

ที่นี่:

-1 : ระงับคอลัมน์ 1 (บรรทัดที่ไม่ซ้ำกับ 1.sorted.txt)

-2 : ระงับคอลัมน์ 2 (บรรทัดที่ไม่ซ้ำกับ 2.sorted.txt)


27
การใช้งานทั่วไป: comm -12 1.sorted.txt 2.sorted.txt
Fedir RYKHTIK

45
ในขณะที่การสื่อสารต้องการไฟล์ที่เรียงคุณอาจใช้ grep -f file1 file2 เพื่อรับบรรทัดทั่วไปของไฟล์ทั้งสอง
ferdy

2
@ferdy (ทำซ้ำความคิดเห็นของฉันจากคำตอบของคุณเป็นหลักเป็นคำตอบซ้ำโพสต์เป็นความคิดเห็น) grepทำสิ่งแปลก ๆ ที่คุณอาจไม่คาดหวัง โดยเฉพาะทุกอย่างใน1.txtจะถูกตีความว่าเป็นนิพจน์ปกติและไม่ใช่สตริงธรรมดา นอกจากนี้ใด ๆ ในบรรทัดว่างจะตรงกับสายทั้งหมดใน1.txt 2.txtดังนั้นgrepจะทำงานในสถานการณ์ที่เฉพาะเจาะจงมากเท่านั้น อย่างน้อยคุณก็ต้องการที่จะใช้fgrep(หรือgrep -f) แต่สิ่งที่ว่างเปล่าอาจจะทำให้เกิดความเสียหายในกระบวนการนี้
Christopher Schultz

11
ดูFerdy 's คำตอบด้านล่างและคริสชูลท์ซ ' s และความคิดเห็นของฉันที่มัน TL; DR - grep -F -x -f file1 file2การใช้งาน
Jonathan Leffler

1
@bapors: ฉันได้ให้คำถาม & คำตอบที่ตอบเองเป็นวิธีการรับผลลัพธ์จากcommคำสั่งเป็น 3 ไฟล์แยกกันได้อย่างไร คำตอบนั้นใหญ่เกินไปที่จะใส่ลงไปอย่างสะดวกสบายที่นี่
Jonathan Leffler

62

หากต้องการใช้คำสั่งcommกับไฟล์ที่ไม่ได้เรียงลำดับอย่างง่ายดายให้ใช้การทดแทนกระบวนการของ 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

ดังนั้นไฟล์ abc และ def จึงมีหนึ่งบรรทัดเหมือนกันดังนั้นบรรทัดที่มี "132" การใช้commบนไฟล์ที่ไม่เรียงลำดับ:

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

บรรทัดสุดท้ายไม่มีเอาต์พุตไม่พบบรรทัดทั่วไป

ตอนนี้ใช้commบนไฟล์ที่เรียงลำดับแล้วเรียงลำดับไฟล์ด้วยการทดแทนกระบวนการ:

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

ตอนนี้เราได้รับ 132 บรรทัด!


2
ดังนั้น ... sort abc > abc.sorted, sort dev > def.sortedแล้วcomm -12 abc.sorted def.sorted?
Nikana Reklawyks

1
@NikanaReklawyks แล้วอย่าลืมลบไฟล์ชั่วคราวในภายหลังและรับมือกับการล้างข้อมูลในกรณีที่เกิดข้อผิดพลาด ในหลายสถานการณ์การทดแทนกระบวนการจะเร็วกว่ามากเนื่องจากคุณสามารถหลีกเลี่ยงดิสก์ I / O ได้ตราบใดที่ผลลัพธ์นั้นพอดีกับหน่วยความจำ
tripleee

29

เพื่อเสริม Perl หนึ่งซับนี่คือมันawkเทียบเท่า:

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

การทำเช่นนี้จะอ่านทุกบรรทัดจากfile1เข้าไปในอาร์เรย์arr[]จากนั้นตรวจสอบแต่ละบรรทัดfile2ว่ามีอยู่แล้วภายในอาร์เรย์ (เช่นfile1) file2เส้นที่พบจะได้รับการตีพิมพ์ในลำดับที่ปรากฏใน โปรดทราบว่าการเปรียบเทียบin arrใช้ทั้งบรรทัดจากfile2เป็นดัชนีไปยังอาร์เรย์ดังนั้นจะรายงานเฉพาะการจับคู่ที่ตรงกันทั้งหมด


2
นี่ (!) เป็นคำตอบที่ถูกต้อง ไม่มีใครสามารถทำงานได้ตามปกติ (ฉันไม่ได้ลองperlเพราะคนอื่น) ขอบคุณล้านคุณ
entonio

1
การรักษาลำดับเมื่อแสดงบรรทัดทั่วไปอาจมีประโยชน์จริง ๆ ในบางกรณีที่จะยกเว้นการคอมมิชชันเนื่องจากสิ่งนั้น
tuxayo

1
ในกรณีที่ทุกคนต้องการทำสิ่งเดียวกันโดยใช้คอลัมน์ที่แน่นอน แต่ไม่รู้ว่า awk เพียงแทนที่ทั้ง $ 0 ด้วย $ 5 สำหรับคอลัมน์ 5 เพื่อให้คุณได้รับบรรทัดที่แชร์ใน 2 ไฟล์ด้วยคำเดียวกันในคอลัมน์ 5
FatihSarigol

24

บางทีคุณอาจจะหมายถึงcomm?

เปรียบเทียบไฟล์ที่เรียงลำดับ FILE1 และ FILE2 ทีละบรรทัด

หากไม่มีตัวเลือกให้สร้างเอาต์พุตสามคอลัมน์ คอลัมน์หนึ่งมีบรรทัดที่ไม่ซ้ำกับ FILE1 คอลัมน์ที่สองมีบรรทัดที่ไม่ซ้ำกับ FILE2 และคอลัมน์ที่สามมีบรรทัดที่ใช้ร่วมกันกับทั้งสองไฟล์

ความลับในการค้นหาข้อมูลเหล่านี้คือหน้าข้อมูล สำหรับโปรแกรม GNU พวกเขามีรายละเอียดมากกว่า man-pages ลองinfo coreutilsแล้วมันจะแสดงรายการสิ่งของที่เป็นประโยชน์เล็ก ๆ ทั้งหมดให้กับคุณ


19

ในขณะที่

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

ให้ความแตกต่างของสองไฟล์ (คืออะไรใน 2.txt และไม่ใช่ใน 1.txt) คุณสามารถทำได้อย่างง่ายดาย

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

เพื่อรวบรวมบรรทัดทั่วไปทั้งหมดซึ่งควรให้วิธีแก้ไขปัญหาของคุณได้ง่าย หากคุณมีไฟล์ที่เรียงลำดับคุณควรดำเนินการcommต่อไป ความนับถือ!


2
grepทำสิ่งแปลก ๆ ที่คุณอาจไม่คาดคิด โดยเฉพาะทุกอย่างใน1.txtจะถูกตีความว่าเป็นนิพจน์ปกติและไม่ใช่สตริงธรรมดา นอกจากนี้ใด ๆ ในบรรทัดว่างจะตรงกับสายทั้งหมดใน1.txt 2.txtดังนั้นสิ่งนี้จะใช้ได้เฉพาะในสถานการณ์ที่เฉพาะเจาะจงเท่านั้น
Christopher Schultz

13
@ChristopherSchultz: เป็นไปได้ที่จะอัพเกรดคำตอบนี้ให้ทำงานได้ดีขึ้นโดยใช้grepสัญลักษณ์POSIX ซึ่งได้รับการสนับสนุนโดยgrepพบในตัวแปร Unix ที่ทันสมัยที่สุด เพิ่ม-F(หรือใช้fgrep) เพื่อระงับนิพจน์ทั่วไป เพิ่ม-x(ตรงทั้งหมด) เพื่อจับคู่ทั้งบรรทัดเท่านั้น
Jonathan Leffler

ทำไมเราควรใช้commไฟล์ที่เรียงลำดับ?
Ulysse BN

2
@ UlysseBN commสามารถทำงานกับไฟล์ขนาดใหญ่โดยพลการตราบใดที่พวกมันถูกเรียงลำดับเพราะมันจำเป็นต้องเก็บสามบรรทัดในหน่วยความจำเท่านั้น (ฉันเดาว่า GNU commจะรู้ว่าจะเก็บไว้เป็นคำนำหน้าถ้าบรรทัดยาวจริงๆ) grepวิธีการแก้ปัญหาความต้องการที่จะให้ทุกการแสดงออกการค้นหาในหน่วยความจำ
tripleee

9

หากไฟล์ทั้งสองยังไม่ได้จัดเรียงคุณสามารถใช้:

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

และมันจะทำงานหลีกเลี่ยงข้อผิดพลาดเมื่อทำcomm: file 2 is not in sorted order comm -12 a.txt b.txt


คุณพูดถูก แต่นี่เป็นการทำซ้ำคำตอบอื่นซึ่งไม่ได้ให้ประโยชน์ใด ๆ เลย หากคุณตัดสินใจที่จะตอบคำถามที่เก่ากว่าซึ่งมีคำตอบที่ถูกต้องและถูกต้องแล้วการเพิ่มคำตอบใหม่ในช่วงปลายวันอาจไม่ได้รับเครดิตใด ๆ หากคุณมีข้อมูลใหม่ที่โดดเด่นหรือคุณมั่นใจว่าคำตอบอื่น ๆ นั้นผิดทั้งหมดโดยเพิ่มคำตอบใหม่ แต่ 'อีกคำตอบ' ให้ข้อมูลพื้นฐานเดียวกันเป็นเวลานานหลังจากที่คำถามถูกถามมักจะ ' คุณไม่ได้รับเครดิตมาก
Jonathan Leffler

ฉันไม่ได้เห็นคำตอบนี้ @JonathanLeffler เพราะส่วนนี้เป็นคำตอบท้ายที่สุดแล้วผสมกับองค์ประกอบอื่นของคำตอบก่อนหน้านี้ ในขณะที่คำตอบอื่น ๆ มีความแม่นยำมากขึ้นประโยชน์ของฉันฉันคิดว่าสำหรับคนที่ต้องการวิธีแก้ปัญหาอย่างรวดเร็วจะมีเพียง 2 บรรทัดในการอ่าน บางครั้งเรากำลังมองหาคำตอบอย่างละเอียดและบางครั้งเราก็รีบร้อนและคำตอบแบบอ่านพร้อมวางที่รวดเร็วก็ใช้ได้
Basj

นอกจากนี้ฉันไม่สนใจเครดิต / ตัวแทนฉันไม่ได้โพสต์เพื่อจุดประสงค์นี้
Basj

1
โปรดสังเกตว่าไวยากรณ์การแทนที่กระบวนการ<(command)ไม่สามารถเคลื่อนย้ายไปยัง POSIX เชลล์ได้แม้ว่าจะใช้งานได้ใน Bash และอื่น ๆ
tripleee

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

นี้คือการทำงานที่ดีกว่าcommคำสั่งเป็นจะค้นหาแต่ละบรรทัดfile1ในfile2ที่commจะเปรียบเทียบเฉพาะในกรณีที่สายnในfile1เท่ากับสายในn file2
teriiehina

1
@teriiehina: ไม่; commไม่เพียงเปรียบเทียบ line N ใน file1 กับ line N ใน file2 มันสามารถจัดการชุดของบรรทัดที่แทรกไว้ในไฟล์ใดไฟล์หนึ่งได้อย่างสมบูรณ์แบบ (ซึ่งเทียบเท่ากับการลบชุดของบรรทัดจากไฟล์อื่น ๆ แน่นอน) มันแค่ต้องการอินพุตที่จะเรียงตามลำดับ
Jonathan Leffler

ดีกว่าcommคำตอบหากต้องการรักษาความสงบเรียบร้อย ดีกว่าawkตอบถ้าไม่มีใครต้องการทำซ้ำ
tuxayo

คำอธิบายอยู่ที่นี่: stackoverflow.com/questions/17552789/…
Chris Koknat


3

ใน Linux รุ่น จำกัด (เช่น QNAP (NAS) ฉันทำงานอยู่):

  • ไม่มีการสื่อสาร
  • grep -f file1 file2สามารถทำให้เกิดปัญหาบางอย่างตามที่ @ChristopherSchultz พูดและการใช้งานgrep -F -f file1 file2ช้ามาก (มากกว่า 5 นาที - ยังไม่เสร็จ - มากกว่า 2-3 วินาทีด้วยวิธีการด้านล่างสำหรับไฟล์ที่มีขนาดเกิน 20MB)

ดังนั้นนี่คือสิ่งที่ฉันทำ:

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

หากfiles.same.sortedจะได้รับในคำสั่งเดียวกันกว่าที่เป็นต้นฉบับกว่าเพิ่มบรรทัดนี้สำหรับคำสั่งเดียวกันกว่า file1:

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

หรือสำหรับคำสั่งเดียวกันมากกว่า file2:

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

2

เพียงเพื่อการอ้างอิงหากมีใครบางคนยังคงมองหาวิธีการทำเช่นนี้กับหลาย ๆ ไฟล์ดูคำตอบที่เชื่อมโยงกับการหาเส้นที่ตรงกันในหลาย ๆ ไฟล์


เมื่อรวมสองคำตอบเหล่านี้ ( ans1และans2 ) ฉันคิดว่าคุณสามารถรับผลลัพธ์ที่ต้องการโดยไม่ต้องเรียงไฟล์:

#!/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

เพียงบันทึกไว้ให้สิทธิ์ดำเนินการ ( chmod +x compareFiles.sh) และเรียกใช้ จะใช้ไฟล์ทั้งหมดที่มีอยู่ในไดเรกทอรีการทำงานปัจจุบันและจะทำให้การเปรียบเทียบทั้งหมดเทียบกับทั้งหมดออกจากในไฟล์ "matching_lines"

สิ่งที่ต้องปรับปรุง:

  • ข้ามไดเรกทอรี
  • หลีกเลี่ยงการเปรียบเทียบไฟล์ทั้งหมดสองครั้ง (file1 vs file2 และ file2 vs file1)
  • อาจเพิ่มหมายเลขบรรทัดถัดจากสตริงที่ตรงกัน

-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

สิ่งนี้ควรทำ


1
คุณควรใช้rm -f file3.txtถ้าคุณจะลบไฟล์ ที่จะไม่รายงานข้อผิดพลาดใด ๆ หากไฟล์นั้นไม่มีอยู่ OTOH มันไม่จำเป็นถ้าสคริปต์ของคุณสะท้อนไปยังเอาต์พุตมาตรฐานปล่อยให้ผู้ใช้สคริปต์เลือกตำแหน่งที่เอาต์พุตควรไป ท้ายที่สุดคุณอาจต้องการใช้$1และ$2(อาร์กิวเมนต์บรรทัดคำสั่ง) แทนชื่อไฟล์คงที่ ( file1.outและfile2.out) นั่นทำให้อัลกอริทึม: มันจะช้า มันเป็นไปอ่านครั้งเดียวในแต่ละบรรทัดfile2.out file1.outมันจะช้าถ้าไฟล์มีขนาดใหญ่ (พูดหลายกิโลไบต์)
Jonathan Leffler

แม้ว่าสิ่งนี้สามารถใช้งานได้ในนามหากคุณมีอินพุตที่ไม่มีอักขระเชลล์ใด ๆ (คำใบ้: ดูคำเตือนที่คุณได้รับจากshellcheck.net ) วิธีการไร้เดียงสานี้ไม่มีประสิทธิภาพมากนัก เครื่องมือgrep -Fที่อ่านไฟล์หนึ่งไปยังหน่วยความจำแล้วส่งผ่านหนึ่งครั้งเพื่อหลีกเลี่ยงการวนซ้ำซ้ำ ๆ กันในไฟล์อินพุตทั้งสอง
tripleee
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.