あるファイル(3.2Gb)のパターンが別のファイル(4.8Gb)と一致するgrep

あるファイル(3.2Gb)のパターンが別のファイル(4.8Gb)と一致するgrep

2つのテキストファイルがあります。 1つは、名前、電子メールアドレス、およびその他のフィールドを含むテキストファイルです。一部の行は以下で提供されますfile1

John:[email protected]:johnson123:22hey
Erik:[email protected]:johnson133:22hey
Robert:[email protected]:johnson123:21hey
Johnnny:[email protected]:johnson123:22hey

もう一方にはメールアドレスのみが含まれています。次の例file2:

[email protected]
[email protected]
[email protected]
[email protected]

file1私はすべての行に電子メールアドレスを持つ出力が欲しいですfile2。たとえば[email protected]is infile2なので、ここで次の行を見たいと思いますfile1

John:[email protected]:johnson123:22hey

file1「Eメールアドレス一覧」に一致する行を検索して出力する簡単な方法はありますかfile2

私は何時間も検索してきましたが、これまでGoogle検索(およびStackOverflow検索)とコマンドラインの努力は成果がありませんでした。

私が試した結果、動作するコマンドは次のとおりです。

fgrep -f file2.txt file1.txt > matched.txt
grep -F -f ....
grep -F -x -f file1 file2 > common 

ちょっと待って、しかし両方を見つけましたgrep memory exhausted。私が一致するファイルは4.8 GB(file1)と3.2 GB(file2Eメールアドレスのみを含む)でした。私の考えでは、このコマンドはメモリを使い果たすと思います。コマンドをよりスムーズに実行する方法を見つけましたfindが、うまくいきませんでした。

概要;一致項目が必要ですfile2file1行の 1 つがfile2の行と一致するとfile1出力します。ファイルが大きいため、すべてのメモリを使用しない安全な方法が必要です。

ありがとうございます。一日中これを探して実験し、あきらめたくなかった(5時間以上)。

答え1

大容量ファイルを操作するのは非常に困難ですが、次の3つの手順で処理できます。

  1. タイプファイル12番目のフィールドをタップ

    sort -k2,2 -t: file1 >file1.sorted
    
  2. タイプファイル2

    sort file2 >file2.sorted
    
  3. 電子メールフィールドを介して2つのファイルを結合する

    join -t: -2 2 file2.sorted file1.sorted -o 2.1,0,2.3,2.4 >matched.txt
    

答え2

私はこの質問に対する2番目の答えを提出したいと思います(興味深い質問です)。これは私のSQLiteソリューションとはまったく異なりますsortjoin

元のアプローチを使用しますgrep -fが、実際には問題を少し減らします。 .txtを使って「クエリファイル」をfile2管理可能な塊に分割しましょうsplit

このsplitユーティリティは、行数に応じてファイルを複数の小さなファイルに分割できます。

以下を含む3.2GBファイル平均行の長さは20文字です。約172,000,000行があります(数学的な間違いをしない限り)。 2000個のファイルに分けてファイルあたり85000ラインが可能です。

だから、

$ mkdir testing
$ cd testing
$ split -l 85000 -a 4 ../file2

この-a 4オプションは、split最初の文字の後に4つの文字を使用してx新しいファイルに名前を付けるように指示します。これらのファイルは背中で呼び出されますxaaaaxaaab

次に、次の元のバージョンを実行しますgrep -f

for f in x????; do
  grep -F -f "$f" ../file1
done

これ可能grepこれで、メモリにはるかに小さいクエリパターンのセットを保持できます。

修正するsplit -l 72000 -a 4:約2000個のファイルを生成するために145,526,885行が使用されます。

testing新しいパーティションファイルセットを作成しようとするたびに、このディレクトリを消去する必要があります。

この回答の分割ファイルは、この質問に対する他の回答への入力として単独で使用できます。

答え3

あなたの具体的な質問を考えると、100%一致するフィールドがあるので、Costaの答えが最も適しています。

しかし、あなたの質問が実際にある場合以前は数十億行にわたって数百万の正規表現を把握しており、GNU Parallelはこれを行う方法を説明しています。https://www.gnu.org/software/parallel/man.html#例: -Grepping-n-lines-for-m-regular-expressions

正規表現の多い大容量ファイルをgrepする最も簡単な解決策は次のとおりです。

grep -f regexps.txt bigfile

または正規表現が固定文字列の場合:

grep -F -f regexps.txt bigfile

CPU、RAM、ディスクI/Oという3つの制限要因があります。

RAMは測定が簡単です。 grepプロセスが利用可能なメモリの大部分を占める場合(たとえば、トップ実行時)、RAMは制限要因です。

CPUは測定も簡単です。 grepがCPUの90%を超える場合、CPUは制限要因であるため、並列化がスピードアップします。

ディスクI / Oが制限要因であることを確認するのは難しく、ディスクシステムによっては並列化が速くなったり遅くなったりする可能性があります。確かに知ることができる唯一の方法は、テストして測定することです。

制限要素:メモリ

大きなファイルの通常のgrep -f regexs.txtはサイズに関係なく動作しますが、regexps.txtが大きすぎてメモリに収まらない場合は分割する必要があります。

grep -Fには約100バイトのRAMが必要ですが、grepには正規表現1バイトあたり約500バイトのRAMが必要です。したがって、regexps.txtがRAMの1%を占める場合は、おそらく大きすぎます。

正規表現を固定文字列に変換できる場合は、そうします。たとえば、大容量ファイルですべて次の行を探している場合:

ID1 foo bar baz Identifier1 quux
fubar ID2 foo bar baz Identifier2

その後、regexps.txtを次から変換できます。

ID1.*Identifier1
ID2.*Identifier2

入力する:

ID1 foo bar baz Identifier1
ID2 foo bar baz Identifier2

これにより、約80%少ないメモリを使用し、より高速のgrep -Fを使用できます。

それでもメモリに収まらない場合は、次のことができます。

parallel --pipepart -a regexps.txt --block 1M grep -F -f - -n bigfile |
sort -un | perl -pe 's/^\d+://'

1Mは、使用可能なメモリをコア数で割った値です。 grep -Fの場合は200、通常のgrepの場合は1000です。 GNU/Linux では、次のことができます。

free=$(awk '/^((Swap)?Cached|MemFree|Buffers):/ { sum += $2 }
          END { print sum }' /proc/meminfo)
percpu=$((free / 200 / $(parallel --number-of-cores)))k

parallel --pipepart -a regexps.txt --block $percpu --compress grep -F -f - -n bigfile |
sort -un | perl -pe 's/^\d+://'

重複した行と誤った順序を許可できる場合は、次の操作を実行する方が高速です。

parallel --pipepart -a regexps.txt --block $percpu --compress grep -F -f - bigfile

制限要因:CPU

CPUが制限要素の場合は、正規表現を並列化する必要があります。

cat regexp.txt | parallel --pipe -L1000 --round-robin --compress grep -f - -n bigfile |
sort -un | perl -pe 's/^\d+://'

このコマンドはCPUごとにgrepを起動し、CPUごとに1回大きなファイルを読み取ります。ただし、これは並行して実行されるため、最初の読み取り以外のすべての読み取りはRAMにキャッシュされます。 regexp.txtのサイズによっては、-L1000の代わりに--block 10mを使用する方が速いかもしれません。

一部のストレージシステムは、複数のブロックを並列に読み取る場合にパフォーマンスが向上します。これは、いくつかのRAIDシステムといくつかのネットワークファイルシステムに当てはまります。大容量ファイルを並列に読み取る:

parallel --pipepart --block 100M -a bigfile -k --compress grep -f regexp.txt

これにより、ビッグファイルが100 MBのチャンクに分割され、各チャンクに対してgrepが実行されます。 bigfile と regexp.txt を並列に読み取るには、--fifo を使って 2 つを結合します。

parallel --pipepart --block 100M -a bigfile --fifo cat regexp.txt \
\| parallel --pipe -L1000 --round-robin grep -f - {}

複数の正規表現と一致すると、1行が重複する可能性があります。

大きな問題

問題が大きすぎて解決できない場合は、おそらくLuceneを使用する準備が整いました。

答え4

joinデータベースソリューションの使用を避ける必要がある場合(理由はわかりませんが、私にとっては最高の考えだと思います)、電子メールアドレスで両方のファイルを並べ替えてから、データベースの機能を概説するこのコマンドを使用して実行できます。

これが私がしたことです:

sort -t: +1 file1 -o file1
sort file2 -o file2
join -t: -o 1.1,1.2,1.3,1.4 -1 2 file1 file2

これはサンプルデータに適しているようです。ファイルを並べ替える所定の位置に。これをしたくない場合は、s-oのオプションをsort一時ファイル名に変更して結合に使用してください。また、最初のファイルに実際に4つ以上のフィールドがある場合は、-oオプションでこれを考慮する必要がありますjoin

詳しくはマニュアルページをご覧ください。

関連情報