เลือกบรรทัดจากไฟล์ข้อความที่มีรหัสอยู่ในไฟล์อื่น


13

ฉันใช้ grep awk sort ในเชลล์ unix จำนวนมากเพื่อทำงานกับไฟล์ข้อความคอลัมน์แบบแยกแท็บขนาดกลาง (ประมาณ 10M-100M) ในแง่นี้ยูนิกซ์เชลล์คือสเปรดชีตของฉัน

แต่ฉันมีปัญหาใหญ่หนึ่งอย่างนั่นคือการเลือกระเบียนที่ระบุรายการรหัส

มีtable.csvไฟล์ที่มีรูปแบบid\tfoo\tbar...และids.csvไฟล์ที่มีรายชื่อของรหัสเพียงเลือกระเบียนจากtable.csvที่มี ID ids.csvอยู่ใน

ชนิดของ/programming/13732295/extract-all-lines-from-text-file-based-on-a-given-list-of-idsแต่มีเชลล์ไม่ใช่ perl

grep -Fเห็นได้ชัดว่าสร้างผลบวกที่ผิดพลาดถ้ารหัสเป็นความกว้างของตัวแปร joinเป็นเครื่องมือที่ฉันไม่สามารถหาได้ ก่อนอื่นก็ต้องเรียงลำดับตัวอักษร (ไฟล์ของฉันมักจะเรียงลำดับตัวเลข) แต่ถึงอย่างนั้นฉันก็ไม่สามารถทำงานได้โดยไม่บ่นเกี่ยวกับลำดับที่ไม่ถูกต้องและข้ามบางระเบียน ดังนั้นฉันไม่ชอบมัน grep -f เทียบกับไฟล์ที่มี^id\t-s ช้ามากเมื่อจำนวนรหัสมีขนาดใหญ่ awkยุ่งยาก

มีวิธีแก้ปัญหาที่ดีสำหรับเรื่องนี้หรือไม่? มีเครื่องมือเฉพาะสำหรับไฟล์ที่คั่นด้วยแท็บหรือไม่ ฟังก์ชั่นพิเศษจะได้รับการต้อนรับมากที่สุดเช่นกัน

UPD: แก้ไขแล้วsort->join


หากgrep -fช้าเกินไปการคงไว้ซึ่งกลยุทธ์นี้ดูเหมือนจะเป็นปัญหามากกว่าที่ควรจะเป็น - การแปรผันจะตกอยู่ในปัญหาประสิทธิภาพ O (N * M) เดียวกัน บางทีเวลาของคุณจะใช้ดีกว่าการเรียนรู้วิธีการใช้ปกติ SQL DB ...
Goldilocks

1
ทำไมไม่ใช้สคริปต์ Perl จากคำถามที่คุณเชื่อมโยง awkอีกทางเลือกหนึ่งที่ควรจะเป็นไปได้ที่จะเขียนสคริปต์ที่คล้ายกันใน
cjm

Bash 4 มีอาเรย์เชื่อมโยงซึ่งเป็นสิ่งที่คุณต้องการหลีกเลี่ยงลูปซ้อนกันเป็นตัวอย่าง
goldilocks

1
sortสามารถทำการเรียงลำดับตัวเลขตัวอักษรและอื่น ๆ ทุกชนิด man sortดู
terdon

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

คำตอบ:


19

ฉันเดาว่าคุณgrep -fไม่ได้ตั้งใจgrep -Fแต่คุณต้องการทั้งสองอย่างและ-w:

grep -Fwf ids.csv table.csv

เหตุผลที่คุณได้รับผลบวกที่ผิดพลาดคือ (ฉันเดาว่าคุณไม่ได้อธิบาย) เพราะถ้าหากสามารถใส่รหัสในอีกรายการได้ทั้งคู่จะถูกพิมพ์ -wลบปัญหานี้และ-Fทำให้แน่ใจว่ารูปแบบของคุณจะถือว่าเป็นสตริงไม่ใช่นิพจน์ทั่วไป จากman grep:

   -F, --fixed-strings
          Interpret PATTERN as a  list  of  fixed  strings,  separated  by
          newlines,  any  of  which is to be matched.  (-F is specified by
          POSIX.)
   -w, --word-regexp
          Select  only  those  lines  containing  matches  that form whole
          words.  The test is that the matching substring must  either  be
          at  the  beginning  of  the  line,  or  preceded  by  a non-word
          constituent character.  Similarly, it must be either at the  end
          of  the  line  or  followed by a non-word constituent character.
          Word-constituent  characters  are  letters,  digits,   and   the
          underscore.

   -f FILE, --file=FILE
          Obtain  patterns  from  FILE,  one  per  line.   The  empty file
          contains zero patterns, and therefore matches nothing.   (-f  is
          specified by POSIX.)

หากผลบวกปลอมของคุณเป็นเพราะ ID สามารถแสดงในฟิลด์ที่ไม่ใช่ ID ให้วนซ้ำไฟล์ของคุณแทน:

while read pat; do grep -w "^$pat" table.csv; done < ids.csv

หรือเร็วกว่า:

xargs -I {} grep "^{}" table.csv < ids.csv

โดยส่วนตัวแล้วฉันจะทำสิ่งนี้ในperl:

perl -lane 'BEGIN{open(A,"ids.csv"); while(<A>){chomp; $k{$_}++}} 
            print $_ if defined($k{$F[0]}); ' table.csv

1
+1 แต่: จะเกิดอะไรขึ้นหากมีผลบวกปลอมที่อาจเกิดขึ้นซึ่งตรงกับรหัสตรงทั้งหมดด้วยคำพูดเพียงไม่อยู่ในคอลัมน์ id หากคุณไม่สามารถใช้^กับ -F คุณจะไม่สามารถกำหนดเป้าหมายคอลัมน์แรกโดยเฉพาะได้
goldilocks

@ goldilocks หากตรงกับที่ระบุพวกเขาไม่ได้เป็นผลบวกที่ผิดพลาด ฉันได้สิ่งที่คุณหมายถึง แต่ในกรณีนั้น OP ควรแสดงไฟล์อินพุตของพวกเขา
terdon

^id\tบิตจาก OP นัยidอาจเกิดขึ้นได้ในคอลัมน์อื่น ถ้าไม่อย่างนี้ก็ไม่สำคัญ
goldilocks

@goldilocks จุดยุติธรรมตอบแก้ไข
terdon

วิธีที่เราใช้ในการทำเช่นนี้คือการสร้างไฟล์ชั่วคราว (โดยใช้ awk หรือ sed) ที่เพิ่มอักขระที่ไม่ซ้ำกัน (เช่น control-A) เพื่อกำหนดเขตข้อมูลที่เราต้องการค้นหาจากนั้นใช้ grep -F -f temppatternfile temptargetfile | tr -d '\ 001'
มาร์ค Plotnick

7

joinยูทิลิตี้คือสิ่งที่คุณต้องการ มันต้องการไฟล์อินพุตที่จะถูกเรียงลำดับด้วยคำศัพท์

สมมติว่าเชลล์ของคุณเป็น bash หรือ ksh:

join -t $'\t' <(sort ids.csv) <(sort table.csv)

โซลูชัน awk ปกติคือโดยไม่จำเป็นต้องเรียงลำดับ

awk -F '\t' 'NR==FNR {id[$1]; next} $1 in id' ids.csv table.csv

ในขณะที่ฉันพยายาม แต่ล้มเหลวในที่สุดในการถ่ายทอดเข้าร่วมเป็นกระบอง มันใช้งานไม่ได้สำหรับฉัน
alamar

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

+1 joinนี้เป็นงานสำหรับ
don_crissti

awkวิธีการแก้ปัญหาที่นี่เป็นอย่างรวดเร็วและมีประสิทธิภาพเพื่อฉัน (ฉันแยกย่อยของไม่กี่ร้อยจากไฟล์ที่มีเส้น ~ 100M)
ลุค

2

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

ดังนั้นให้ใช้ตัวเลือก -t ของการเรียงลำดับเพื่อระบุอักขระการแยกของคุณและใช้ตัวเลือก -k เพื่อระบุฟิลด์ (จำได้ว่าคุณต้องการฟิลด์เริ่มต้นและสิ้นสุด - แม้ว่าจะเหมือนกัน - หรือมันจะเรียงลำดับจากอักขระนั้น ถึงจุดสิ้นสุดของบรรทัด)

ดังนั้นสำหรับไฟล์ที่คั่นด้วยแท็บเหมือนในคำถามนี้สิ่งต่อไปนี้ควรใช้งานได้ (ด้วยคำตอบของ glennสำหรับโครงสร้าง):

join -t$'\t' <(sort -d ids.csv) <(sort -d -t$'\t' -k1,1 table.csv) > output.csv

(สำหรับการอ้างอิงแฟล็ก -d หมายถึงการเรียงพจนานุกรมคุณอาจต้องการใช้แฟล็ก -b เพื่อละเว้นช่องว่างนำหน้าดูman sortและman join)

เป็นตัวอย่างทั่วไปมากขึ้นสมมติว่าคุณกำลังเข้าร่วมสองไฟล์ที่คั่นด้วยเครื่องหมายจุลภาค - input1.csvในคอลัมน์ที่สามและinput2.csvที่สี่ คุณสามารถใช้

join -t, -1 3 -2 4 <(sort -d -t, -k3,3 input2.csv) <(sort -d -t, -k4,4 input2.csv) > output.csv

ที่นี่-1และ-2ตัวเลือกระบุฟิลด์ที่จะเข้าร่วมในไฟล์แรกและไฟล์ที่สองตามลำดับ


0

คุณสามารถใช้ทับทิมเพื่อทำสิ่งที่คล้ายกัน:

ruby -pe 'File.open("id.csv").each { |i| puts i if i =~ /\$\_/ }' table.csv
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.