Pilih baris dari file teks yang memiliki id terdaftar di file lain

13

Saya menggunakan banyak grep awk sort di shell unix saya untuk bekerja dengan file teks kolom yang dipisahkan tab berukuran sedang (sekitar 10M-100M). Dalam hal ini shell unix adalah spreadsheet saya.

Tapi saya punya satu masalah besar, yaitu memilih catatan yang diberikan daftar ID.

Memiliki table.csvfile dengan format id\tfoo\tbar...dan ids.csvfile dengan daftar id, hanya pilih catatan table.csvdengan id yang ada di ids.csv.

jenis /programming/13732295/extract-all-lines-from-text-file-based-on-a-given-list-of-ids tetapi dengan shell, bukan perl.

grep -Fjelas menghasilkan positif palsu jika id lebar variabel. joinadalah utilitas saya tidak pernah tahu. Pertama-tama, ini membutuhkan pengurutan alfabet (file saya biasanya diurutkan secara numerik), tetapi bahkan kemudian saya tidak bisa membuatnya bekerja tanpa mengeluh tentang urutan yang salah dan melewatkan beberapa catatan. Jadi saya tidak suka itu. grep -f terhadap file dengan ^id\t-s sangat lambat ketika jumlah id besar. awkrumit.

Apakah ada solusi bagus untuk ini? Adakah alat khusus untuk file yang dipisahkan tab? Fungsionalitas ekstra akan sangat disambut juga.

UPD: Dikoreksi sort->join

alamar
sumber
Jika grep -fterlalu lambat, mempertahankan strategi ini kedengarannya lebih banyak masalah daripada nilainya - variasi kemungkinan akan menjadi mangsa masalah kinerja O (N * M) yang sama. Mungkin waktu Anda akan lebih baik dihabiskan untuk belajar bagaimana menggunakan SQL DB yang dinormalisasi ...
goldilocks
1
Mengapa tidak menggunakan skrip Perl dari pertanyaan yang Anda tautkan? Atau, mungkin saja menulis skrip yang serupa di awk.
cjm
Bash 4 memiliki susunan asosiatif, yang Anda butuhkan untuk menghindari loop bersarang seperti contoh perl.
goldilocks
1
sortdapat melakukan segala macam penyortiran, numerik, alfabet dan lainnya Lihat man sort.
terdon
Saya punya pertanyaan di sini, bagaimana kita melakukan hal yang sama jika file sumber dari mana kita ingin mengekstraksi data adalah file yang tidak dibatasi

Jawaban:

19

Saya kira Anda grep -ftidak bermaksud grep -Ftetapi Anda sebenarnya membutuhkan kombinasi keduanya dan -w:

grep -Fwf ids.csv table.csv

Alasan Anda mendapatkan false positive adalah (saya kira, Anda tidak menjelaskan) karena jika sebuah id dapat dimuat di yang lain, maka keduanya akan dicetak. -wmenghapus masalah ini dan -Fmemastikan pola Anda diperlakukan sebagai string, bukan ekspresi reguler. Dari 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.)

Jika positif palsu Anda adalah karena ID dapat hadir di bidang non-ID, gantilah file Anda:

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

atau, lebih cepat:

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

Secara pribadi, saya akan melakukan ini perl:

perl -lane 'BEGIN{open(A,"ids.csv"); while(<A>){chomp; $k{$_}++}} 
            print $_ if defined($k{$F[0]}); ' table.csv
terdon
sumber
1
+1 Tetapi: Bagaimana jika ada potensi positif palsu yang cocok dengan id dengan tepat kata-kata bijak, hanya saja tidak di kolom id? Jika Anda tidak dapat menggunakan ^dengan -F, Anda tidak dapat menargetkan kolom pertama secara khusus.
goldilocks
@goldilocks jika benar-benar cocok, itu bukan positif palsu. Saya mengerti maksud Anda, tetapi dalam hal ini, OP harus menunjukkan file input mereka.
terdon
The ^id\tbit dari OP menyiratkan idmungkin terjadi di kolom lain. Jika tidak, ini tidak masalah.
goldilocks
@goldilocks titik adil, jawaban diedit.
terdon
Cara yang biasa kami lakukan adalah membuat file sementara (menggunakan awk atau sed) yang menambahkan karakter unik (katakanlah, kontrol-A) membatasi bidang yang ingin kami cari, kemudian gunakan grep -F -f temppatternfile temptargetfile | tr -d '\ 001'
Mark Plotnick
7

The joinutilitas adalah apa yang Anda inginkan. Itu memang membutuhkan file input untuk diurutkan secara leksikal.

Dengan asumsi shell Anda adalah bash atau ksh:

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

Tanpa perlu menyortir, solusi awk yang biasa adalah

awk -F '\t' 'NR==FNR {id[$1]; next} $1 in id' ids.csv table.csv
glenn jackman
sumber
Ketika saya mencoba tetapi akhirnya gagal untuk menyampaikan, bergabung adalah kludge. Tidak bekerja dengan baik untuk saya.
alamar
1
joinbukan kludge: kata-kata Anda itu Anda tidak bisa mengetahuinya. Buka pikiran Anda dan pelajari. Output apa yang Anda dapatkan, dan apa bedanya dengan apa yang Anda harapkan?
glenn jackman
+1, ini adalah pekerjaan untuk join.
don_crissti
The awksolusi di sini adalah sangat cepat dan efisien untuk tujuan saya (saya mengeluarkan himpunan bagian dari beberapa ratus dari file dengan garis-garis ~ 100M)
Lukas
2

Jawaban untuk pertanyaan SO ini membantu saya menyiasati orang-orang yang canggung dengan bergabung. Pada dasarnya, ketika Anda mengurutkan file dalam persiapan untuk mengirimnya untuk bergabung, Anda perlu memastikan Anda mengurutkan berdasarkan kolom tempat Anda bergabung. Jadi jika itu yang pertama, Anda perlu memberi tahu apa karakter pemisah dalam file dan bahwa Anda ingin mengurutkannya di bidang pertama (dan hanya bidang pertama). Kalau tidak, jika bidang pertama memiliki lebar variabel (misalnya), pemisah Anda dan mungkin bidang lain mungkin mulai mempengaruhi urutan pengurutan.

Jadi, gunakan opsi -t penyortiran untuk menentukan karakter pemisah Anda, dan gunakan opsi -k untuk menentukan bidang (mengingat bahwa Anda memerlukan bidang awal dan akhir - bahkan jika itu sama - atau itu akan mengurutkan dari karakter itu ke akhir baris).

Jadi untuk file yang dipisahkan tab seperti dalam pertanyaan ini, berikut ini akan berfungsi (dengan terima kasih atas jawaban glenn untuk struktur):

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

(Untuk referensi, flag -d berarti semacam kamus. Anda mungkin juga ingin menggunakan flag -b untuk mengabaikan spasi putih terkemuka, lihat man sortdan man join).

Sebagai contoh yang lebih umum, anggap Anda bergabung dengan dua file yang dipisahkan koma - input1.csvpada kolom ketiga dan input2.csvkeempat. Anda bisa menggunakannya

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

Di sini opsi -1dan -2menentukan bidang mana yang akan digabung dalam file input pertama dan kedua masing-masing.

LangeHaare
sumber
0

Anda juga dapat menggunakan ruby ​​untuk melakukan hal serupa:

ruby -pe 'File.open("id.csv").each { |i| puts i if i =~ /\$\_/ }' table.csv
Jay
sumber