grep:メモリを使い果たしました


42

私は非常に簡単な検索を行っていました:

grep -R Milledgeville ~/Documents

そして、しばらくしてからこのエラーが現れました:

grep: memory exhausted

どうすればこれを回避できますか?

私のシステムには10GBのRAMがあり、いくつかのアプリケーションが実行されているので、単純なgrepでメモリが不足していることに本当に驚いています。~/Documents約100GBで、あらゆる種類のファイルが含まれています。

grep -RI この問題はないかもしれませんが、バイナリファイルも検索したいです。

回答:


46

2つの潜在的な問題:

  • grep -RgrepOS / X 10.8以降で見つかった変更されたGNUを除く)はシンボリックリンクに従うため、に100GBのファイルしかない場合~/Documentsでも/、たとえばへのシンボリックリンクが残っている可能性があり、ファイルを含むファイルシステム全体をスキャンすることになりますのような/dev/zero。使用するgrep -r新しいGNUでgrep、または標準の構文を使用します。

    find ~/Documents -type f -exec grep Milledgeville /dev/null {} +
    

    (ただし、終了ステータスはパターンが一致するかどうかを反映しないことに注意してください)。

  • grepパターンに一致する行を見つけます。そのためには、メモリに一度に1行をロードする必要があります。grep他の多くのgrep実装とは対照的に、GNU は読み込む行のサイズに制限がなく、バイナリファイルでの検索をサポートしています。そのため、非常に大きな行(つまり、2つの改行文字が非常に遠い)のファイルがあり、使用可能なメモリよりも大きい場合、失敗します。

    これは通常、スパースファイルで発生します。次の方法で再現できます。

    truncate -s200G some-file
    grep foo some-file
    

    それを回避するのは難しいです。あなたはそれを(まだGNUでgrep)することができます:

    find ~/Documents -type f -exec sh -c 'for i do
      tr -s "\0" "\n" < "$i" | grep --label="$i" -He "$0"
      done' Milledgeville {} +
    

    これは、入力をに送る前に、NUL文字のシーケンスを1つの改行文字に変換しgrepます。これは、問題の原因がスパースファイルである場合に当てはまります。

    大きなファイルに対してのみ行うことで最適化できます:

    find ~/Documents -type f \( -size -100M -exec \
      grep -He Milledgeville {} + -o -exec sh -c 'for i do
      tr -s "\0" "\n" < "$i" | grep --label="$i" -He "$0"
      done' Milledgeville {} + \)
    

    ファイルがスパースではなく、のgrep前のバージョンのGNU 2.6を使用している--mmap場合、このオプションを使用できます。行はメモリにコピーされるのではなく、メモリにマップされます。つまり、システムは、ページをファイルにページアウトすることにより、常にメモリを再利用できます。そのオプションはGNU grep2.6で削除されました


実際、GNU grepは1行の読み取りを気にしません。ファイルの大部分を単一のバッファーに読み取ります。「さらに、GNU grepは入力を行に入力しないようにします。」ソース:lists.freebsd.org/pipermail/freebsd-current/2010-August/...
Godricシーア

4
@GodricSeer、まだファイルの大部分を単一のバッファに読み込むことができますが、文字列が見つからず、改行文字も見つからない場合、その単一のバッファをメモリに保持するのが最善です一致が見つかった場合に表示する必要があるため、次のバッファを読み込みます。そのため、問題は依然として同じです。実際には、200GBのスパースファイルでのgrepはOOMで失敗します。
ステファンシャゼル

1
@GodricSeer、よくない。行がすべて小さい場合、grepそれまでに処理したバッファを破棄できます。数キロバイト以上のメモリを使用せずgrepyes無期限に出力できます。問題、行のサイズです。
ステファンシャゼル

3
ここでは、GNU grep --null-dataオプションも役立ちます。入力行ターミネータとして改行の代わりにNULの使用を強制します。
iruvar

1
@ 1_CR、良い点ですが、それはまた出力行終端文字をNULに設定します。
ステファンシャゼラス

5

私は通常やる

find ~/Documents | xargs grep -ne 'expression'

たくさんの方法を試しましたが、これが最速であることがわかりました。これは、ファイル名にスペースを含むファイルをうまく処理しないことに注意してください。これが事実であり、grepのGNUバージョンを持っていることがわかっている場合は、次を使用できます。

find ~/Documents -print0 | xargs -0 grep -ne 'expression'

使用できない場合:

 find ~/Documents -exec grep -ne 'expression' "{}" \;

これはexec、すべてのファイルのgrepになります。


これは、スペースを含むファイルで中断します。
クリスダウン

うーん、それは本当です。
コッテ

あなたはそれで周りを取得することができますfind -print0 | xargs -0 grep -ne 'expression'
Dravスローン

@ChrisDownは、壊れたポータブルソリューションというよりも、むしろ非プロタブルソリューションです。
レト

@ChrisDown主要な大学のほとんどが採用されてfind -print0おりxargs -0、現在では、3つのBSD、MINIX 3、Solaris 11、…
Gillesが「悪を止めろ」

4

これを回避するいくつかの方法を考えることができます。

  • すべてのファイルを一度にgrepする代わりに、一度に1つのファイルを実行します。例:

    find /Documents -type f -exec grep -H Milledgeville "{}" \;
    
  • どのファイルに単語が含まれているかだけを知る必要がある場合は、grep -l代わりに行います。grepは最初のヒット後に検索を停止するため、巨大なファイルを読み続ける必要はありません。

  • 実際のテキストも必要な場合は、2つの別々のgrepsを次のように並べることができます。

    for file in $( grep -Rl Milledgeville /Documents ); do grep -H Milledgeville "$file"; done
    

最後の例は有効な構文ではありません-コマンド置換を実行する必要があります(grep出力はファイル名で有効な区切り文字を使用しているため、実行しないでください)。引用する必要もあります$file
クリスダウン

後者の例では、ファイル名に改行または空白が含まれるという問題が発生forします(ファイルを2つの引数として処理します)
Drav Sloan

@DravSloan編集は改善されますが、依然として有効なファイル名に違反します。
クリスダウン

1
ええ、それは彼女の答えの一部だったので、それを残しました、それが実行されるようにそれを改善しようとしました(ファイルにスペース/改行などがない場合)。
ドラフスローン

彼の訂正->彼女、私の謝罪ジェニー:/
Drav Sloan

1

失われたデータを検索するために6TBのディスクをgrepしていて、メモリを使い果たしました-エラー。これは他のファイルでも機能するはずです。

私たちが思いついた解決策は、ddを使用してチャンクでディスクを読み取り、チャンクをgrepすることでした。これはコード(big-grep.sh)です:

#problem: grep gives "memory exhausted" error on 6TB disks
#solution: read it on parts
if [ -z $2 ] || ! [ -e $1 ]; then echo "$0 file string|less -S # greps in chunks"; exit; fi

FILE="$1"
MATCH="$2"

SIZE=`ls -l $1|cut -d\  -f5`
CHUNKSIZE=$(( 1024 * 1024 * 1 )) 
CHUNKS=100 # greps in (100 + 1) x 1MB = 101MB chunks
COUNT=$(( $SIZE / $CHUNKSIZE * CHUNKS ))

for I in `seq 0 $COUNT`; do
  dd bs=$CHUNKSIZE skip=$(($I*$CHUNKS)) count=$(( $CHUNKS+1)) if=$FILE status=none|grep -UF -a --context 6 "$MATCH"
done

1
重複するチャンクを読み取らない限り、チャンク境界での一致を見逃す可能性があります。オーバーラップは、少なくとも一致すると予想される文字列と同じ大きさでなければなりません。
クサラナンダ

それぞれ100MBのチャンクに1MBの余分を検索するために更新...安いハック
Dagelf
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.