Metin dosyasından başka bir dosyada kimlikleri listelenmiş satırlar seçin


13

Orta büyüklükte (yaklaşık 10M-100M satır) sekmeyle ayrılmış sütun metin dosyalarıyla çalışmak için unix kabuğumda çok fazla grep awk sıralama kullanıyorum. Bu açıdan unix kabuğu benim e-tablom.

Ama büyük bir sorunum var, yani kimlik listesi verilen kayıtları seçmek.

table.csvBiçime sahip dosya id\tfoo\tbar...ve kimlik ids.csvlistesi içeren dosyaya sahip olmak için yalnızca table.csvkimliğin bulunduğu kayıtları seçin ids.csv.

bir çeşit /programming/13732295/extract-all-lines-from-text-file-based-on-a-given-list-of-ids ama kabuklu, perl değil.

grep -FAçıkçası kimlikleri değişken genişlikse yanlış pozitif üretir. joinasla çözemediğim bir yardımcı program. Her şeyden önce, alfabetik sıralama gerektirir (dosyalarım genellikle sayısal olarak sıralanır), ancak o zaman bile yanlış siparişten şikayet etmeden ve bazı kayıtları atlamadan çalışamıyorum. Bu yüzden hoşuma gitmedi. id ^id\tsayısı çok olduğunda grep -f, -s ile dosyaya karşı çok yavaştır. awkhantal.

Bunun için iyi çözümler var mı? Sekmeyle ayrılmış dosyalar için belirli bir araç var mı? Ekstra işlevsellik de memnuniyetle karşılanacaktır.

UPD: Düzeltildi sort->join


Eğer grep -fbuna değer daha fazla sorun gibi bu strateji sesleri koruyarak, çok yavaş - varyasyonlar olasılıkla aynı O (N * M) performans sorunları pençesine düşecek. Belki de zaman normalleştirilmiş bir SQL DB kullanmayı öğrenmek için daha iyi harcanan olurdu ...
goldilocks

1
Bağladığınız sorudan neden Perl komut dosyasını kullanmıyorsunuz? Alternatif olarak, içine benzer bir komut dosyası yazmak da mümkün olmalıdır awk.
cjm

Bash 4, perl örneğiyle iç içe ilmekleri atlatmak için ihtiyacınız olan ilişkilendirilebilir dizilere sahiptir.
goldilocks

1
sortsayısal, alfabetik ve diğer her türlü sıralama yapabilir. Bkz man sort.
terdon

Burada bir sorgu var, biz verileri ayıklamak istediğiniz kaynak dosya sınırlandırılmış olmayan bir dosya ise nasıl aynı

Yanıtlar:


19

Ne demek sanırım grep -fdeğil grep -Fama aslında ikisi ve bir arada gerek -w:

grep -Fwf ids.csv table.csv

Yanlış pozitifler almanızın nedeni (sanırım açıklamamışsınızdır) çünkü başka bir kimlik içerilebiliyorsa, her ikisi de yazdırılacaktır. -wbu sorunu ortadan kaldırır ve -Fdesenlerinizin normal ifadeler değil, dize olarak ele alınmasını sağlar. Gönderen 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.)

Yanlış pozitifleriniz ID olmayan bir alanda bir ID olabileceğinden kaynaklanıyorsa, bunun yerine dosyanızda dolaşın:

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

veya daha hızlı:

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

Şahsen ben bunu perlolsa yaparım :

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

1
+1 Ama: Kimlik sütununda değil, kimliğe tam olarak uyan potansiyel yanlış pozitifler varsa ne olur? ^-F ile kullanamazsanız , ilk sütunu özellikle hedefleyemezsiniz.
goldilocks

@goldilocks tam olarak eşleşirlerse yanlış pozitif değillerdir. Ne demek istediğini anlıyorum, ancak bu durumda OP girdi dosyalarını göstermelidir.
terdon

^id\tOP biraz ima idbaşka bir sütuna oluşabilir. Değilse, bu önemli değil.
goldilocks

@ goldilocks fair point, cevap düzenlendi.
terdon

Bunu yapmak için kullandığımız yol, aramak istediğimiz alanı sınırlandıran benzersiz bir karakter (örneğin, kontrol-A) ekleyen geçici dosyalar (awk veya sed kullanarak) oluşturmaktı, sonra grep -F -f temppatternfile temptargetfile | tr -d '\ 001'
Mark Plotnick

7

Yardımcı joinprogramı ne istiyorsun. Girdi dosyalarının sözlüksel olarak sıralanmasını gerektirir.

Kabuğunuzun bash veya ksh olduğunu varsayarsak:

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

Sıralamaya gerek kalmadan, normal awk çözümü

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

Denediğim gibi ama sonuçta iletemediğimde, birleşim bir çamurdur. Benim için çok iyi çalışmıyor.
alamar

1
joinbir çamur değil: sözlerin, çözemediğin bir şeydi. Aklını aç ve öğren. Hangi çıktıyı aldınız ve bu beklediğinizden nasıl farklı?
glenn jackman

+1, bu bir iş join.
don_crissti

Buradaki awkçözüm benim amacım için çok hızlı ve etkili (~ 100M satırlı dosyalardan birkaç yüzün alt kümelerini
Luke

2

Bu SO sorunun cevapları katılmak ile niggles dolaşmak yardımcı oldu. Esasen, katılmaya göndermek için dosyayı hazırlarken sıraladığınızda, katıldığınız sütuna göre sıralama yaptığınızdan emin olmanız gerekir. Yani bu ilk karakterse, dosyada ayırıcı karakterin ne olduğunu ve ilk alanda (ve yalnızca ilk alanda) sıralanmasını istediğinizi söylemeniz gerekir. Aksi takdirde, ilk alanın değişken genişlikleri varsa (örneğin), ayırıcılarınız ve muhtemelen diğer alanlar sıralama düzenini etkilemeye başlayabilir.

Bu nedenle, ayırma karakterinizi belirtmek için -t seçeneğini kullanın ve alanı belirtmek için -k seçeneğini kullanın (aynı olsa bile bir başlangıç ​​ve bitiş alanına ihtiyacınız olduğunu hatırlayın - yoksa bu karakterden sıralanır satır sonuna kadar).

Bu nedenle, bu sorudaki gibi sekmeyle ayrılmış bir dosya için aşağıdakiler çalışmalıdır ( glenn'in yapıya verdiği cevap sayesinde ):

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

(Referans olarak, -d bayrağı sözlük sıralaması anlamına gelir. Ayrıca, önde gelen boşlukları yok saymak için -b bayrağını kullanmak isteyebilirsiniz, bkz. man sortVe man join).

Daha genel bir örnek input1.csvolarak, üçüncü sütunda ve dördünde virgülle ayrılmış iki dosyaya katıldığınızı varsayalım input2.csv. Kullanabilirsin

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

Burada -1ve -2seçenekleri, sırasıyla birinci ve ikinci girdi dosyalarında hangi alanlara katılacağını belirtir.


0

Benzer bir şey yapmak için yakut kullanabilirsiniz:

ruby -pe 'File.open("id.csv").each { |i| puts i if i =~ /\$\_/ }' table.csv
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.