เปรียบเทียบสองคอลัมน์ของไฟล์ต่างกันและพิมพ์ถ้ามันตรงกัน


16

ฉันใช้ Solaris 10 และตัวเลือก grep ที่เกี่ยวข้องกับ -f ไม่ทำงาน

ฉันมีไฟล์สองไฟล์ที่คั่นด้วย pipe:

file1:

abc|123|BNY|apple|
cab|234|cyx|orange|
def|kumar|pki|bird|

ไฟล์ 2:

abc|123|
kumar|pki|
cab|234

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

ผลลัพธ์ที่คาดหวัง:

abc|123|BNY|apple|
cab|234|cyx|orange|

ไฟล์ที่ฉันมีมีขนาดใหญ่มากมีประมาณ 400,000 บรรทัดดังนั้นฉันต้องการทำให้การดำเนินการเป็นไปอย่างรวดเร็ว


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

ลองใช้รุ่น GNU ของมันอยู่ภายใต้grep stackoverflow.com/questions/15259882/…/usr/sfw/bin/ggrep
slm

คำตอบ:


21

นี่คือสิ่งที่ awk ถูกออกแบบมาสำหรับ:

$ awk -F'|' 'NR==FNR{c[$1$2]++;next};c[$1$2] > 0' file2 file1
abc|123|BNY|apple|
cab|234|cyx|orange|

คำอธิบาย

  • -F'|': |ชุดคั่นฟิลด์
  • NR==FNR: NR คือหมายเลขบรรทัดอินพุตปัจจุบันและ FNR หมายเลขบรรทัดของไฟล์ปัจจุบัน ทั้งสองจะเท่ากันในขณะที่กำลังอ่านไฟล์ที่ 1
  • c[$1$2]++; next: ถ้านี่คือไฟล์ที่ 1 ให้บันทึกสองฟิลด์แรกในcอาร์เรย์ จากนั้นข้ามไปยังบรรทัดถัดไปเพื่อให้สิ่งนี้ใช้ได้กับไฟล์ที่ 1 เท่านั้น

  • c[$1$2]>0: บล็อกอื่นจะถูกดำเนินการหากไฟล์นี้เป็นไฟล์ที่สองดังนั้นเราจะตรวจสอบว่าฟิลด์ 1 และ 2 ของไฟล์นี้ถูกเห็นแล้วหรือไม่ ( c[$1$2]>0) และหากเป็นเช่นนั้นเราจะพิมพ์บรรทัดนั้น ในawkการดำเนินการเริ่มต้นคือการพิมพ์บรรทัดดังนั้นหากc[$1$2]>0เป็นจริงสายจะถูกพิมพ์


อีกทางเลือกหนึ่งเนื่องจากคุณติดแท็กด้วย Perl:

perl -e 'open(A, "file2"); while(<A>){/.+?\|[^|]+/ && $k{$&}++};
         while(<>){/.+?\|[^|]+/ && do{print if defined($k{$&})}}' file1

คำอธิบาย

บรรทัดแรกจะเปิดfile2ขึ้นอ่านทุกอย่างจนถึงอันดับ 2 |( .+?\|[^|]+) และบันทึกไว้ ( $&เป็นผลลัพธ์ของผู้ดำเนินการจับคู่ล่าสุด) ใน%kแฮช

บรรทัดที่สองประมวลผล file1 ใช้ regex เดียวกันเพื่อแยกคอลัมน์แรกที่สองและพิมพ์บรรทัดหากคอลัมน์เหล่านั้นถูกกำหนดใน%kแฮช


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

cut -d'|' -f 1,2 file2 | while read pat; do grep "^$pat" file1; done

แต่นั่นจะช้าลง


แต่จะไม่โหลดทั้งหมด (สองคอลัมน์แรก) ของfile2ในหน่วยความจำใช่ไหม
โจเซฟอาร์.

@terdon: awk -F'|' 'NR==FNR{c[$1$2]++;next};c[$1$2] > 0'เป็นรุ่นที่สั้นกว่า
cuonglm

มันใช้งานไม่ได้ ..
user68365

@ user68365: file2มีแถวที่ซ้ำกันหรือไม่
cuonglm

ไม่ไม่มีแถวที่ซ้ำกัน
user68365

1

ฉันคิด

grep -Ff file2 file1

คือสิ่งที่คุณกำลังมองหา ควรมีประสิทธิภาพ แต่ฉันไม่แน่ใจว่าจะแม่นยำเท่าที่คุณต้องการ หากพบabc|123(ตัวอย่าง) ในบรรทัดในfile1คอลัมน์ต่าง ๆ บรรทัดนั้นจะถูกพิมพ์เช่นกัน หากคุณสามารถรับประกันได้ว่าสิ่งนี้จะไม่เกิดขึ้น


Grep คงไม่เพียงพอเนื่องจาก abc | 123 อาจปรากฏที่ใดที่หนึ่งในไฟล์ th ยิ่งกว่านั้นฉันกำลังใช้ Solaris 10 และฉันไม่สามารถใช้ตัวเลือก grep นั้นได้เช่นกัน
user68365

2
@ user68365 โปรดอธิบายทั้งหมดนี้ในคำถามของคุณ คุณต้องบอกระบบปฏิบัติการของคุณกับเราและระบุว่าคุณต้องการให้ตรงกับ 2 คอลัมน์แรกเท่านั้น
terdon

1

หากคุณต้องการคิดว่าปัญหาในรูปแบบ SQL คุณควรลองใช้เครื่องมือที่ชื่อว่า ' q ':

$ q -d '|' "select f1.* from file1 f1 join file2 f2 on (f1.c1 = f2.c1 and f1.c2 = f2.c2)"

มีความชัดเจนและเข้าใจได้ง่ายขึ้นหากคุณคุ้นเคยกับการสืบค้น SQL


ขอบคุณสำหรับหนึ่งในโซลูชั่นที่ลึกลับที่สุด นั่นคือสิ่งที่ฉันต้องการ. แต่ฉันมีปัญหาในการหา"เครื่องมือ q" นี้
Rolf

เครื่องมือที่มีประโยชน์มาก
ghilesZ

0
$  sed 's/^/\^/' 2.txt > temp.txt ; grep 1.txt -f temp.txt
abc|123|BNY|apple|
cab|234|cyx|orange|

1
ตามที่ฉันได้แก้ไขและพูดถึงในคำถามตัวเลือก grep -f ไม่ทำงานในระบบของฉัน
user68365

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