web-dev-qa-db-ja.com

巨大なファイルパフォーマンスをgrepする

300K行を超えるFILE_Aと3000万行を超えるFILE_Bがあります。 FILE_Aの各行をFILE_Bで取得し、grepの結果を新しいファイルに書き込むbashスクリプトを作成しました。

このプロセス全体には5時間以上かかります。

スクリプトのパフォーマンスを向上させる方法があるかどうかについての提案を探しています。

Grepコマンドとしてgrep-F -m1を使用しています。 FILE_Aは次のようになります。

123456789 
123455321

fILE_Bは次のようになります。

123456789,123456789,730025400149993,
123455321,123455321,730025400126097,

したがって、bashを使用すると、FILE_Aの次の行を選択し、FILE_Bでそれを取得するwhileループがあります。パターンがFILE_Bで見つかったら、result.txtに書き込みます。

while read -r line; do
   grep -F -m1 $line 30MFile
done < 300KFile

よろしくお願いします。

4
marcio_rogerio

パフォーマンスの鍵は、巨大なファイルを1回だけ読み取ることです。

複数のパターンを別々の行に配置することで、grepに渡すことができます。これは通常、ファイルからパターンを読み取るようにgrepに指示することによって行われます。

grep -F -f 300KFile 30MFile

これにより、大きなファイルの順序で一致が出力され、複数のパターンに一致する行が1回だけ出力されます。さらに、これは行のどこでもパターンを探します。たとえば、パターンファイルに1234が含まれている場合、123456,345678,2348962342478912,1211138,1234などの行が一致します。

パターンを前処理することにより、完全な列の一致に制限できます。たとえば、パターンに特殊文字()?*+\|[]{}が含まれていない場合:

<300KFile sed -e 's/^/(^|,)/' -e 's/$/($|,)/' |
grep -E -f - 30MFile

各パターンの最初の一致のみを保持することが重要な場合は、最初のパスを作成して上記のように関連する行のみを抽出してから、awkまたはPerlで2番目のパスを実行して、すでに表示されているパターンを追跡します。

<300KFile sed -e 's/^/(^|,)/' -e 's/$/($|,)/' |
grep -E -f - 30MFile |
Perl -l -F, -ape '
    BEGIN {
        open P, "300KFile" or die;
        %patterns = map {chomp; $_=>1} <P>;
        close P;
    }
    foreach $c (@F) {
        if ($patterns{$c}) {
            print;
            delete $patterns{$c};
        }
    }
'

次を実行できますか?

grep -Ff FILE_A FILE_B > FILE_C

これで、ファイルAとCでのみスクリプトを実行できます。

更新:待機...順序は保持されますか?

別の更新:順序を維持するには、さらに処理が必要です。これにより、元のスクリプトと同じ結果が得られます。 FILE_Aの300K回線とFILE_Bの300K回線のみでテスト、125分対14秒。

#! /bin/bash
grep -Ff FILE_A FILE_B > FILE_B_TMP
grep -oFf FILE_A FILE_B_TMP > FILE_A_SHUFF
grep -Ff FILE_A_SHUFF FILE_A > FILE_A_TMP

while read -r line; do
   grep -F -m1 "$line" FILE_B_TMP
done < FILE_A_TMP > result.txt
4
choroba

私はcommがより良いパフォーマンスを作ることができると信じています:

comm -12 300KFile <(sed 's/,.*//' 30MFile)

ps。文字列123123 fromm300KFileが30Mファイルの文字列gdwyedg,123123,hfsjdkfhと一致するかどうかはわかりません。スクリプトと同じように一致しますが、私のスクリプトでは一致しません。

0
rush

Grepベースのソリューションでは、FILE_Aの各レコードをFILE_Bの各レコードと比較する必要があると思います。 FILE_Aの少なくともN-1レコードはFILE_Bの特定のレコードと一致しないため、このアプローチには多くの冗長性があります。一方、ファイルがソートされている場合は、比較ごとに多数のテストを破棄することができます。したがって、次のようなものです。

#!/bin/bash

# NB a faster solution would be to sort the smaller file in a seperate process
# you might also want to set a buffer size for large files
sort $1 > /tmp/$$.a
sort $2 > /tmp/$$.b

join -j1 -t',' /tmp/$$.a /tmp/$$.b

rm -f /tmp/$$.?

(未検証)

ただし、エントリの順序が変更されることに注意してください。これは、FILE_Bのデータの特定の列で一致させる必要があり、並べ替えによってオーバーヘッドが発生することを前提としています。ただし、これらのファイルサイズでは結果が速くなるはずです。

0
symcbean