เปรียบเทียบไฟล์สองไฟล์ทีละบรรทัดและสร้างความแตกต่างในไฟล์อื่น


121

ฉันต้องการเปรียบเทียบ file1 กับ file2 และสร้าง file3 ซึ่งมีบรรทัดใน file1 ซึ่งไม่มีอยู่ใน file2


ฉันลองใช้ความแตกต่าง แต่มันสร้างตัวเลขและสัญลักษณ์อื่น ๆ ที่ด้านหน้าของบรรทัดที่แตกต่างกันซึ่งทำให้ฉันเปรียบเทียบไฟล์ได้ยาก
อาทิตย์

คำตอบ:


216

diff (1) ไม่ใช่คำตอบ แต่ comm (1) คือ

NAME
       comm - compare two sorted files line by line

SYNOPSIS
       comm [OPTION]... FILE1 FILE2

...

       -1     suppress lines unique to FILE1

       -2     suppress lines unique to FILE2

       -3     suppress lines that appear in both files

ดังนั้น

comm -2 -3 file1 file2 > file3

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

comm -2 -3 <(sort file1) <(sort file2) > file3

โดยมีเงื่อนไขว่าเชลล์ของคุณรองรับการทดแทนกระบวนการ (bash does)


1
โปรดจำไว้ว่าต้องเรียงไฟล์สองไฟล์และไม่ซ้ำกัน
andy

6
คุณสามารถจัดกลุ่มตัวเลือกเข้าด้วยกัน:comm -23
Paolo M

"เรียงลำดับ" หมายความว่าอย่างไร ว่าเส้นมีคำสั่งเหมือนกันไหม? จากนั้นอาจเป็นเรื่องปกติสำหรับกรณีการใช้งานส่วนใหญ่เช่นเดียวกับการตรวจสอบว่ามีการเพิ่มบรรทัดใดบ้างโดยเปรียบเทียบกับเวอร์ชันเก่าที่สำรองไว้ หากบรรทัดที่เพิ่มใหม่ไม่สามารถอยู่ระหว่างบรรทัดที่มีอยู่นั่นเป็นปัญหามากกว่า
Egor Hans

@EgorHans: ถ้าไฟล์มีเช่นบรรทัดที่มีจำนวนเต็มเช่น "3 \ n1 \ n3 \ n2 \ n" บรรทัดแรกจะต้องเรียงลำดับจากน้อยไปมากหรือมากไปหาน้อยเช่น "\ 1 \ n2 \ n3 \ n3 \ n" ที่มีรายการซ้ำ ติดกัน นั่นคือ "เรียงลำดับ" และไฟล์ทั้งสองจะต้องเรียงลำดับในลักษณะที่คล้ายกัน เมื่อไฟล์ที่ใหม่กว่ามีบรรทัดใหม่ก็ไม่สำคัญว่าไฟล์จะอยู่ "ระหว่างบรรทัดที่มีอยู่" หรือไม่เพราะหลังจากเรียงลำดับแล้วไฟล์เหล่านั้นจะเรียงตามลำดับ
sorpigal

49

ยูทิลิตี้ Unix diffมีไว้เพื่อจุดประสงค์นี้

$ diff -u file1 file2 > file3

ดูคู่มือและอินเทอร์เน็ตสำหรับตัวเลือกรูปแบบเอาต์พุตต่างๆ ฯลฯ


8
ที่ไม่ทำงานที่ร้องขอ; มันจะแทรกอักขระพิเศษมากมายแม้จะใช้สวิตช์บรรทัดคำสั่งที่แนะนำในคำตอบอื่น ๆ
xenocyon

20

พิจารณาสิ่งนี้:
ไฟล์ a.txt:

abcd
efgh

ไฟล์ b.txt:

abcd

คุณสามารถพบความแตกต่างด้วย:

diff -a --suppress-common-lines -y a.txt b.txt

ผลลัพธ์จะเป็น:

efgh 

คุณสามารถกำหนดผลลัพธ์ใหม่ในไฟล์เอาต์พุต (c.txt) โดยใช้:

diff -a --suppress-common-lines -y a.txt b.txt > c.txt

สิ่งนี้จะตอบคำถามของคุณ:

"... ซึ่งมีบรรทัดใน file1 ซึ่งไม่มีอยู่ใน file2"


2
มีข้อ จำกัด สองประการสำหรับคำตอบนี้: (1) ใช้ได้เฉพาะกับบรรทัดสั้น ๆ (โดยค่าเริ่มต้นน้อยกว่า 80 ตัวอักษรแม้ว่าจะสามารถแก้ไขได้) และที่สำคัญกว่านั้น (2) จะเพิ่ม "<" ที่ส่วนท้ายของแต่ละ บรรทัดที่ต้องนำออกไปด้วยโปรแกรมอื่น (เช่น awk, sed)
sergut

ในหลาย ๆ กรณีคุณจะต้องใช้-dด้วยซึ่งจะdiffพยายามอย่างเต็มที่เพื่อค้นหาความแตกต่างที่น้อยที่สุดเท่าที่จะเป็นไปได้ -i, -E, -w, -Bและ--suppress-blank-emptyยังสามารถเป็นประโยชน์บางครั้งแม้จะไม่ได้เสมอ หากคุณไม่ทราบว่าอะไรเหมาะกับกรณีการใช้งานของคุณให้ลองdiff --helpก่อน (ซึ่งโดยทั่วไปเป็นความคิดที่ดีเมื่อคุณไม่รู้ว่าคำสั่งสามารถทำอะไรได้บ้าง)
Egor Hans

นอกจากนี้การใช้ --line-format =% L จะทำให้คุณแตกต่างจากการสร้างอักขระพิเศษใด ๆ (อย่างน้อยความช่วยเหลือบอกว่ามันใช้งานได้เช่นนี้ แต่กำลังจะลองใช้)
Egor Hans

นอกจากนี้ยังสั้นกว่าและดูเหมือนว่าจะใช้งานได้เหมือนกันstackoverflow.com/a/27667185/1179925
mrgloom

8

บางครั้งdiffก็เป็นยูทิลิตี้ที่คุณต้องการ แต่บางครั้งjoinก็เหมาะสมกว่า ไฟล์ต้องได้รับการจัดเรียงไว้ล่วงหน้าหรือหากคุณใช้เชลล์ที่รองรับการทดแทนกระบวนการเช่น bash, ksh หรือ zsh คุณสามารถจัดเรียงได้ทันที

join -v 1 <(sort file1) <(sort file2)

คุณควรได้รับเหรียญสำหรับสิ่งนี้! นั่นคือสิ่งที่ฉันมองหาในช่วง 2 ชั่วโมงที่ผ่านมา
Zatarra

7

ลอง

sdiff file1 file2

โดยปกติแล้วจะได้ผลดีกว่าสำหรับฉันมาก คุณอาจต้องการจัดเรียงไฟล์ก่อนหากลำดับของบรรทัดไม่สำคัญ (เช่นไฟล์กำหนดค่าข้อความบางไฟล์)

ตัวอย่างเช่น,

sdiff -w 185 file1.cfg file2.cfg

1
ยูทิลิตี้ดี! ฉันชอบวิธีการทำเครื่องหมายเส้นที่แตกต่าง ทำให้ง่ายต่อการเปรียบเทียบการกำหนดค่า สิ่งนี้ร่วมกับการเรียงลำดับเป็นคำสั่งผสมที่ร้ายแรง (เช่นsdiff <(sort file1) <(sort file2))
jmagnusson

3

หากคุณต้องการแก้ปัญหานี้ด้วย coreutils คำตอบที่ยอมรับนั้นดี:

comm -23 <(sort file1) <(sort file2) > file3

คุณยังสามารถใช้sd (สตรีม diff) ซึ่งไม่ต้องการการเรียงลำดับหรือการทดแทนกระบวนการและรองรับสตรีมที่ไม่มีที่สิ้นสุดเช่น:

cat file1 | sd 'cat file2' > file3

อาจจะไม่เป็นประโยชน์มากนักในตัวอย่างนี้ แต่ยังคงพิจารณาอยู่ ในบางกรณีคุณจะไม่สามารถใช้commหรือgrep -Fหรือไม่diffได้

นี่คือบล็อกโพสต์ที่ฉันเขียนเกี่ยวกับสตรีมที่แตกต่างกันบนเทอร์มินัลซึ่งแนะนำ sd


3

ยังไม่มีgrepวิธีแก้ปัญหา?

  • บรรทัดที่มีอยู่ใน file2 เท่านั้น:

    grep -Fxvf file1 file2 > file3
  • บรรทัดที่มีอยู่ใน file1 เท่านั้น:

    grep -Fxvf file2 file1 > file3
  • บรรทัดที่มีอยู่ในทั้งสองไฟล์:

    grep -Fxf file1 file2 > file3

2

มีคำตอบมากมายแล้ว แต่ไม่มีคำตอบใดที่สมบูรณ์แบบ IMHO คำตอบของ Thanatos จะมีอักขระพิเศษบางตัวต่อบรรทัดและคำตอบของ Sorpigal กำหนดให้ต้องเรียงไฟล์หรือเรียงลำดับล่วงหน้าซึ่งอาจไม่เพียงพอในทุกสถานการณ์

ฉันคิดว่าวิธีที่ดีที่สุดในการสร้างเส้นที่แตกต่างและไม่มีอะไรอื่น (ไม่มีอักขระพิเศษไม่มีการสั่งซื้อใหม่) คือการรวมกันของ diff , grepและawk(หรือคล้ายกัน)

หากบรรทัดไม่มี "<" หนึ่งบรรทัดสั้น ๆ สามารถ:

diff urls.txt* | grep "<" | sed 's/< //g'

แต่นั่นจะลบทุกอินสแตนซ์ของ "<" (น้อยกว่าช่องว่าง) ออกจากบรรทัดซึ่งไม่เป็นที่ยอมรับเสมอไป (เช่นซอร์สโค้ด) ตัวเลือกที่ปลอดภัยที่สุดคือใช้ awk:

diff urls.txt* | grep "<" | awk '{for (i=2; i<NF; i++) printf $i " "; print $NF}'

ซับเดียวนี้แตกต่างทั้งสองไฟล์จากนั้นกรองเอาท์พุตสไตล์ ed ของ diff จากนั้นจึงลบ "<" ที่ต่อท้าย วิธีนี้ใช้ได้แม้ว่าบรรทัดจะมี "<" อยู่บ้าง


1
comm ไม่ต้องการการเรียงลำดับ (ในเวอร์ชันที่ใหม่กว่า?) - เพียงใช้ --nocheck-order ฉันใช้สิ่งนี้มากเมื่อจัดการ csvs จาก CLI
ak5

2

ฉันแปลกใจที่ไม่มีใครพูดถึงdiff -yการสร้างเอาต์พุตแบบเคียงข้างกันเช่น:

diff -y file1 file2 > file3

และในfile3(บรรทัดต่างๆมีสัญลักษณ์|อยู่ตรงกลาง):

same     same
diff_1 | diff_2

1

ใช้ยูทิลิตี้ Diff และแยกเฉพาะบรรทัดที่ขึ้นต้นด้วย <ในเอาต์พุต


0
diff a1.txt a2.txt | grep '> ' | sed 's/> //' > a3.txt

ฉันลองคำตอบเกือบทั้งหมดในชุดข้อความนี้แล้ว แต่ไม่มีคำตอบใดที่สมบูรณ์ หลังจากไม่กี่เส้นทางข้างต้นได้ผลสำหรับฉัน ความแตกต่างจะทำให้คุณแตกต่าง แต่มีอักขระพิเศษที่ไม่ต้องการ โดยที่คุณเริ่มต้นเส้นความแตกต่างที่แท้จริงด้วย ">" ดังนั้นขั้นตอนต่อไปคือการgrepเส้นเริ่มต้นด้วย '>' และปฏิบัติตามโดยการลบเช่นเดียวกันกับsed


1
นี่เป็นความคิดที่ไม่ดี <นอกจากนี้คุณยังจะต้องมีการปรับเปลี่ยนสายที่เริ่มต้นด้วย คุณจะเห็นสิ่งนี้หากคุณสลับลำดับของไฟล์อินพุต แม้ว่าคุณจะทำสิ่งนี้คุณก็ไม่ต้องการเว้นgrepโดยใช้ sed เพิ่มเติม: `diff a1 a2 | sed '/> / s ///' `` ยังสามารถแบ่งบรรทัดที่มี>หรือ<อยู่ในสถานการณ์ที่ถูกต้องและยังคงเว้นบรรทัดพิเศษที่อธิบายหมายเลขบรรทัด diff -C0 a1 a2 | sed -ne '/^[+-] /s/^..//p'ถ้าคุณอยากจะลองวิธีนี้เป็นวิธีที่ดีกว่าที่จะเป็น:
sorpigal

0

คุณสามารถใช้diffกับการจัดรูปแบบผลลัพธ์ต่อไปนี้:

diff --old-line-format='' --unchanged-line-format='' file1 file2

--old-line-format=''ปิดการใช้งานเอาต์พุตสำหรับ file1 หากบรรทัดแตกต่างกันเมื่อเปรียบเทียบใน file2
--unchanged-line-format=''ปิดการใช้งานเอาต์พุตหากบรรทัดเหมือนกัน


0

หากคุณมีไฟล์ CSV ที่มีคอลัมน์เดียวหรือหลายคอลัมน์คุณสามารถดำเนินการ "diff" ทีละบรรทัดโดยใช้ฐานข้อมูลแบบฝัง sqlite3 มันมาพร้อมกับ python ดังนั้นควรมีอยู่ใน linux / mac ส่วนใหญ่ คุณสามารถเขียนสคริปต์คำสั่ง sqlite3 บน bash shell ได้โดยไม่จำเป็นต้องเขียน python

  1. สร้างไฟล์ a.csv และ b.csv ของคุณ
  2. ตรวจสอบให้แน่ใจว่าได้ติดตั้ง sqlite3 โดยใช้คำสั่ง "sqlite3 -help"
  3. เรียกใช้คำสั่งด้านล่างโดยตรงบน Linux / Mac shell (หรือใส่ไว้ในสคริปต์)
echo "
.mode csv
.import a.csv atable
.import b.csv btable
create table result as select * from atable EXCEPT select * from btable;
.output result.csv
select * from result ;
.quit
" | sqlite3 temp.db

หมายเหตุ: ตรวจสอบให้แน่ใจว่ามีการขึ้นบรรทัดใหม่สำหรับแต่ละคำสั่ง sqlite3

มันทำงานอย่างไร

  1. นำเข้า csv 2 รายการไปยัง "atable" และ "btable" ตามลำดับ
  2. ใช้เครื่องหมาย " except " sql เพื่อเลือกข้อมูลที่มีอยู่ใน "atable" แต่ไม่มีใน "btable" สร้างตาราง "ผลลัพธ์" โดยใช้คำสั่ง select query
  3. ส่งออกตารางผลลัพธ์ไปยัง result.csv โดยเรียกใช้ "select * from result;"

หากคุณต้องการดำเนินการกับคอลัมน์เฉพาะ sqlite3 หรือ db ใด ๆ ก็เป็นวิธีที่จะไป

ฉันได้ลองใช้ไฟล์หลาย GB ที่แตกต่างกันโดยใช้เครื่องมือ diff และ comm ในตัว Sqlite เอาชนะยูทิลิตี้ linux ได้หนึ่งไมล์

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.