web-dev-qa-db-ja.com

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

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

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

形式table.csvid\tfoo\tbar...ファイルとIDのリストを含むids.csvファイルがある場合、table.csvから存在するIDを含むレコードのみをids.csvから選択します。

一種の https://stackoverflow.com/questions/13732295/extract-all-lines-from-text-file-based-on-a-given-list-of-ids が、シェルでは、 Perlではありません。

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

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

UPD:修正済みsort-> join

13
alamar

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
19
terdon

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
7
glenn jackman

this SO question への回答は、結合で微笑みを回避するのに役立ちました。基本的に、結合に送信する準備としてファイルを並べ替えるときは、結合している列に基づいて並べ替えていることを確認してください。それが最初の列である場合は、ファイル内の区切り文字が何であるか、最初のフィールド(および最初のフィールド)。それ以外の場合、最初のフィールドの幅が可変である場合(たとえば)、セパレーターや他のフィールドがソート順に影響を与える可能性があります。

したがって、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_を参照してください)。

より一般的な例として、2つのコンマ区切りファイルを結合するとします。3番目の列は_input1.csv_で、4番目の列は_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_オプションは、最初の入力ファイルと2番目の入力ファイルで結合するフィールドをそれぞれ指定します。

2
LangeHaare

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

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