別のファイルにリストされているIDを持つテキストファイルから行を選択します


13

UNIXシェルで多くのgrep awkソートを使用して、中サイズ(約10M〜100M行)のタブ区切りの列テキストファイルを処理します。この点で、UNIXシェルは私のスプレッドシートです。

しかし、私には1つの大きな問題があります。それは、IDのリストを指定してレコードを選択することです。

持つtable.csv形式のファイルid\tfoo\tbar...ids.csvIDのリストを含むファイルを、のみからレコードを選択するtable.csvのID存在とids.csv

一種の/programming/13732295/extract-all-lines-from-text-file-based-on-a-given-list-of-idsですが、perlではなくシェルを使用しています。

grep -Fidが可変幅の場合、明らかに偽陽性を生成します。 join私が理解できなかったユーティリティです。まず、アルファベット順の並べ替えが必要です(通常、ファイルは数値順に並べ替えられます)が、それでも正しくない順序について文句を言わず、一部のレコードをスキップせずに機能させることはできません。だから私はそれが好きではありません。^id\tIDの数が多い場合、-sを使用したファイルに対するgrep -f は非常に遅くなります。 awk面倒です。

これに対する良い解決策はありますか?タブ区切りファイル用の特定のツールはありますか?追加機能も大歓迎です。

UPD:修正済みsort->join


grep -f遅すぎる場合、この戦略を維持することは、価値があるよりもトラブルのように聞こえます。バリエーションは、おそらく同じO(N * M)パフォーマンス問題の犠牲になります。多分あなたの時間は正規化された SQL DBの使い方を学ぶことに費やしたほうがいいでしょう...
goldilocks

1
リンクした質問のPerlスクリプトを使用してみませんか?または、同様のスクリプトをで作成できるようにする必要がありますawk
cjm 2014年

Bash 4には連想配列があります。これは、perlの例のようにネストされたループを回避するために必要な配列です。
goldilocks 2014年

1
sortあらゆる種類のソート、数値、アルファベット順などを実行できます。を参照してくださいman sort
terdon

ここにクエリがあります。データの抽出元のソースファイルが区切られていないファイルである場合、同じようにするにはどうすればよいですか

回答:


19

あなたはgrep -fそうではなかっgrep -Fたと思いますが、実際にはとの組み合わせが必要です-w

grep -Fwf ids.csv table.csv

誤検知が発生した理由は(おそらく、説明しなかったと思います)、IDが別のIDに含まれている可能性がある場合は、両方が出力されます。-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列ではなく、単語単位でidと完全に一致する潜在的な誤検知がある場合はどうでしょうか。^-Fと一緒に使用できない場合、最初の列を具体的にターゲットにすることはできません。
goldilocks 2014年

@goldilocksは、完全に一致する場合、誤検知ではありません。私はあなたの意味を理解しますが、その場合、OPは入力ファイルを表示するはずです。
terdon

^id\tOPからのビットは、id別の列で発生する可能性があることを意味します。そうでない場合、これは問題ではありません。
ゴルディロックス2014年

@goldilocksフェアポイント、回答を編集しました。
terdon

これを行うために使用した方法は、検索するフィールドを区切る一意の文字(たとえば、control-A)を追加する一時ファイルを(awkまたはsedを使用して)作成し、次にgrep -F -f temppatternfile temptargetfile | tr -d '\ 001'
Mark 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

私が試しましたが、最終的に伝えることができなかったので、参加は泥沼です。私にはうまくいきません。
アラマー2014年

1
joinあなたの言葉はあなたがそれを理解することができなかった場合、それはクラッジではありません。あなたの心を開き、学びます。どのような出力が得られましたか、それは予想とどのように異なりますか?
グレン

+1、これはの仕事ですjoin
don_crissti

awkここでの解決策は、私の目的にとって非常に迅速で効率的です(私は〜100M行のファイルから数百のサブセットを抽出しています)
Luke

2

このSOの質問への回答は、参加することで問題を回避するのに役立ちました。基本的に、結合に送信する準備としてファイルをソートするときは、結合している列に基づいてソートしていることを確認する必要があります。したがって、それが最初のものである場合は、ファイル内の区切り文字が何であるかを伝え、最初のフィールド(および最初のフィールドのみ)でソートするように指定する必要があります。そうしないと、最初のフィールドの幅が変化する場合(たとえば)、セパレーターや他のフィールドがソート順に影響を与える可能性があります。

したがって、sortの-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.csv3番目の列とinput2.csv4 番目の列にある2つのコンマ区切りファイルを結合するとします 。あなたは使うことができます

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

ここで-1-2オプションは、最初の入力ファイルと2番目の入力ファイルで結合するフィールドをそれぞれ指定します。


0

rubyを使用して同様のことを行うこともできます。

ruby -pe 'File.open("id.csv").each { |i| puts i if i =~ /\$\_/ }' table.csv
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.