วิธีลบบรรทัดที่ปรากฏในไฟล์ B จากไฟล์ A อื่น?


160

ฉันมีไฟล์ขนาดใหญ่A (ประกอบด้วยอีเมล) หนึ่งบรรทัดสำหรับแต่ละเมล ฉันยังมีไฟล์ Bอีกอันที่มีเมลอีกชุดหนึ่ง

ฉันจะใช้คำสั่งใดเพื่อลบที่อยู่ทั้งหมดที่ปรากฏในไฟล์ B จากไฟล์ A

ดังนั้นว่าไฟล์ที่มีอยู่:

A
B
C

และไฟล์ B บรรจุอยู่:

B    
D
E

จากนั้นไฟล์ A ควรจะเหลือด้วย:

A
C

ตอนนี้ฉันรู้ว่านี่เป็นคำถามที่อาจจะได้รับการถามบ่อยขึ้น แต่ผมพบว่าเพียงหนึ่งคำสั่งออนไลน์ที่ให้ฉันข้อผิดพลาดที่มีตัวคั่นที่ไม่ดี

ความช่วยเหลือใด ๆ ที่จะได้รับการชื่นชมมาก! ใครบางคนจะมากับซับในที่ฉลาด แต่ฉันไม่ใช่ผู้เชี่ยวชาญเชลล์



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

คำตอบ:


204

หากไฟล์ถูกเรียงลำดับ (อยู่ในตัวอย่างของคุณ):

comm -23 file1 file2

-23ไม่แสดงบรรทัดที่อยู่ในทั้งสองไฟล์หรือเฉพาะในไฟล์ 2 หากไฟล์ไม่ถูกเรียงลำดับให้ไพพ์ผ่านsortก่อน ...

ดูหน้าคนที่นี่


8
comm -23 file1 file2 > file3จะส่งออกเนื้อหาใน file1 ไม่ได้อยู่ใน file2 ไปยัง file3 จากนั้นmv file3 file1ในที่สุดจะล้างเนื้อหาที่ซ้ำซ้อนใน file1
Spectral

2
comm -23 file1 file2 | sponge file1อีกวิธีหนึ่งคือการใช้งาน ไม่จำเป็นต้องล้างข้อมูล
Socowi

ลิงก์หน้าผู้ชายไม่โหลดสำหรับฉัน - ทางเลือก: linux.die.net/man/1/comm
Felix Rabe

@Socowi ฟองน้ำคืออะไร? ฉันไม่มีสิ่งนั้นในระบบของฉัน (macos 10.13)
Felix Rabe

@ FelixRabe ดีนั่นน่าเบื่อ แทนที่ด้วยลิงก์ของคุณ ขอขอบคุณ
เทพพอล

85

grep -Fvxf <lines-to-remove> <all-lines>

  • ทำงานกับไฟล์ที่ไม่ได้จัดเรียง
  • รักษาคำสั่ง
  • POSIX คือ

ตัวอย่าง:

cat <<EOF > A
b
1
a
0
01
b
1
EOF

cat <<EOF > B
0
1
EOF

grep -Fvxf B A

เอาท์พุท:

b
a
01
b

คำอธิบาย:

  • -F: ใช้สตริงตัวอักษรแทน BRE เริ่มต้น
  • -x: พิจารณาการจับคู่ที่ตรงกับทั้งบรรทัดเท่านั้น
  • -v: พิมพ์ที่ไม่ตรงกัน
  • -f file: ใช้รูปแบบจากไฟล์ที่กำหนด

วิธีนี้ช้ากว่าไฟล์ที่จัดเรียงล่วงหน้ากว่าวิธีอื่นเนื่องจากมันกว้างกว่า หากความเร็วมีความสำคัญเช่นกันโปรดดูที่: วิธีหาบรรทัดในไฟล์หนึ่งที่ไม่ได้อยู่ในอีกไฟล์หนึ่งอย่างรวดเร็วหรือไม่

นี่คือการทุบตีอัตโนมัติอย่างรวดเร็วสำหรับการทำงานในบรรทัด

remove-lines() (
  remove_lines="$1"
  all_lines="$2"
  tmp_file="$(mktemp)"
  grep -Fvxf "$remove_lines" "$all_lines" > "$tmp_file"
  mv "$tmp_file" "$all_lines"
)

GitHub ต้นน้ำ

การใช้งาน:

remove-lines lines-to-remove remove-from-this-file

ดูเพิ่มเติมที่: /unix/28158/is-there-a-tool-to-get-the-lines-in-one-file-that-are-not-in-another


55

awk เพื่อช่วยเหลือ!

วิธีการแก้ปัญหานี้ไม่จำเป็นต้องมีอินพุตเรียง คุณต้องระบุ fileB ก่อน

awk 'NR==FNR{a[$0];next} !($0 in a)' fileB fileA

ผลตอบแทน

A
C

มันทำงานยังไง?

NR==FNR{a[$0];next} สำนวนที่เป็นสำหรับการจัดเก็บไฟล์แรกในอาเรย์เป็นกุญแจสำหรับต่อมา "มี" การทดสอบ

NR==FNR กำลังตรวจสอบว่าเรากำลังสแกนไฟล์แรกโดยที่ตัวนับบรรทัดสากล (NR) เท่ากับตัวนับบรรทัดไฟล์ปัจจุบัน (FNR)

a[$0] เพิ่มบรรทัดปัจจุบันกับอาเรย์เป็นสำคัญทราบว่าพฤติกรรมเช่นนี้เป็นชุดที่จะไม่มีค่าที่ซ้ำกันใด ๆ (คีย์)

!($0 in a)ตอนนี้เราอยู่ในไฟล์ถัดไป inเป็นการทดสอบที่มีที่นี่มันจะตรวจสอบว่าบรรทัดปัจจุบันอยู่ในชุดที่เราบรรจุในขั้นตอนแรกจากไฟล์แรก!ปฏิเสธเงื่อนไข อะไรคือสิ่งที่หายไปที่นี่คือการกระทำที่เป็นค่าเริ่มต้นเป็น{print}และมักจะไม่ได้เขียนอย่างชัดเจน

โปรดทราบว่าสามารถใช้คำนี้เพื่อลบคำที่ไม่อนุญาต

$ awk '...' badwords allwords > goodwords

ที่มีการเปลี่ยนแปลงเล็กน้อยก็สามารถทำความสะอาดหลายรายการและสร้างรุ่นทำความสะอาด

$ awk 'NR==FNR{a[$0];next} !($0 in a){print > FILENAME".clean"}' bad file1 file2 file3 ...

เครื่องหมายเต็มในนี้ หากต้องการใช้สิ่งนี้บนบรรทัดคำสั่งใน GnuWin32 ใน Windows ให้แทนที่ไส้ปากกาเดียวด้วยเครื่องหมายคำพูดคู่ ทำงานรักษา ขอบคุณมาก.
twobob

ใช้งานได้ แต่ฉันจะเปลี่ยนเส้นทางเอาต์พุตไปยัง fileA ในรูปแบบของ A (ด้วยบรรทัดใหม่) B ได้อย่างไร
Anand Builders

ฉันเดาว่าคุณหมายถึงA\nCเขียนไฟล์ temp ก่อนและเขียนทับไฟล์ต้นฉบับ... > tmp && mv tmp fileA
karakfa

คะแนนเต็มจากฉันเช่นกัน awk นี้ใช้เวลาทั้งหมด 1 วินาทีในการประมวลผลไฟล์ที่มี 104,000 รายการ: +1:
MitchellK

เมื่อใช้ในสคริปต์ให้แน่ใจว่าการตรวจสอบครั้งแรกที่fileBไม่ว่างเปล่า (0 ไบต์ยาว) fileAเพราะถ้ามันคือคุณจะได้รับผลที่ว่างแทนของเนื้อหาที่คาดหวังของ (สาเหตุ: FNR==NRจะใช้กับfileAตอนนั้น)
Peter Nowee


7

คุณสามารถทำได้เว้นแต่ไฟล์ของคุณจะถูกจัดเรียง

diff file-a file-b --new-line-format="" --old-line-format="%L" --unchanged-line-format="" > file-a

--new-line-formatสำหรับสายที่อยู่ในแฟ้มข แต่ไม่ได้อยู่ใน --old-..สำหรับสายที่อยู่ในไฟล์ แต่ไม่ได้อยู่ในข --unchanged-..สำหรับสายที่อยู่ในทั้งสอง %Lทำให้มันถูกพิมพ์ออกมาอย่างแน่นอน

man diff

สำหรับรายละเอียดเพิ่มเติม


1
คุณบอกว่ามันใช้งานได้เว้นแต่ไฟล์จะถูกเรียงลำดับ ปัญหาอะไรจะเกิดขึ้นหากมีการเรียงลำดับ? เกิดอะไรขึ้นถ้าพวกเขาจะถูกจัดเรียงบางส่วน?
Carlos Macasaet

1
นั่นคือการตอบสนองต่อการแก้ปัญหาข้างต้นที่แนะนำการใช้งานของcommคำสั่ง commต้องการไฟล์ที่จะเรียงลำดับดังนั้นหากเรียงลำดับคุณสามารถใช้โซลูชันนั้นได้เช่นกัน คุณสามารถใช้วิธีแก้ไขปัญหานี้ได้ไม่ว่าไฟล์จะถูกเรียงลำดับหรือไม่ก็ตาม
aec

7

การปรับแต่งคำตอบที่ดีของ @ karakfa นี้อาจเห็นได้ชัดว่าเร็วขึ้นสำหรับไฟล์ที่มีขนาดใหญ่มาก เช่นเดียวกับคำตอบนั้นไฟล์ไม่จำเป็นต้องเรียงลำดับ แต่ความเร็วสามารถมั่นใจได้โดยอาศัยอาเรย์เชื่อมโยงของ awk มีเพียงไฟล์การค้นหาเท่านั้นที่อยู่ในหน่วยความจำ

การกำหนดนี้ยังอนุญาตให้มีความเป็นไปได้ที่จะใช้เฉพาะหนึ่งฟิลด์ ($ N) ในไฟล์อินพุตในการเปรียบเทียบ

# Print lines in the input unless the value in column $N
# appears in a lookup file, $LOOKUP;
# if $N is 0, then the entire line is used for comparison.

awk -v N=$N -v lookup="$LOOKUP" '
  BEGIN { while ( getline < lookup ) { dictionary[$0]=$0 } }
  !($N in dictionary) {print}'

(ข้อดีของวิธีนี้ก็คือว่ามันเป็นเรื่องง่ายที่จะปรับเปลี่ยนเกณฑ์การเปรียบเทียบเช่นการตัดชั้นนำและลากพื้นที่สีขาว.)


สิ่งนี้ยากกว่าที่จะใช้ในสถานการณ์จำลองข้ามแพลตฟอร์มแบบตัวพิมพ์มุม อย่างไรก็ตามปิดสำหรับความพยายามประสิทธิภาพ
twobob

2

คุณสามารถใช้ Python:

python -c '
lines_to_remove = set()
with open("file B", "r") as f:
    for line in f.readlines():
        lines_to_remove.add(line.strip())

with open("file A", "r") as f:
    for line in [line.strip() for line in f.readlines()]:
        if line not in lines_to_remove:
            print(line)
'

2

คุณสามารถใช้ได้ - diff fileA fileB | grep "^>" | cut -c3- > fileA

สิ่งนี้จะทำงานกับไฟล์ที่ไม่เรียงลำดับเช่นกัน


-1

ในการลบบรรทัดทั่วไประหว่างสองไฟล์คุณสามารถใช้คำสั่ง grep, comm หรือ join

grep ใช้ได้กับไฟล์ขนาดเล็กเท่านั้น ใช้ -v พร้อมกับ -f

grep -vf file2 file1 

สิ่งนี้แสดงบรรทัดจาก file1 ที่ไม่ตรงกับบรรทัดใด ๆ ใน file2

comm เป็นคำสั่งยูทิลิตี้ที่ทำงานกับไฟล์ที่เรียงลำดับ ใช้สองไฟล์เป็นอินพุตและสร้างคอลัมน์ข้อความสามคอลัมน์เป็นเอาต์พุต: บรรทัดในไฟล์แรกเท่านั้น บรรทัดในไฟล์ที่สองเท่านั้น; และบรรทัดในทั้งสองไฟล์ คุณสามารถระงับการพิมพ์คอลัมน์ใด ๆ โดยใช้ตัวเลือก -1, -2 หรือ -3

comm -1 -3 file2 file1

สิ่งนี้แสดงบรรทัดจาก file1 ที่ไม่ตรงกับบรรทัดใด ๆ ใน file2

ในที่สุดก็มีการเข้าร่วมคำสั่งยูทิลิตี้ที่ดำเนินการเข้าร่วมเท่าเทียมกันในไฟล์ที่ระบุ ตัวเลือก -v ยังอนุญาตให้ลบบรรทัดทั่วไประหว่างสองไฟล์

join -v1 -v2 file1 file2

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