【メモリ枯渇】上限付き grep が単体で8GB食って、PCがハングした(Windows 11 / Git Bash / GNU grep 3.0)

投稿 最終更新

文:株式会社佐野組 佐野 凛

環境(先に書く)

この記事の実測は、次の環境でだけ確認した。

Linux の新しい grep や、WSL の find では数字も挙動も変わり得る。こちらでは未検証なので、同じ症状が出ない環境もある、と先に書いておく。

症状:セッションが、どれも動かなくなった

ゲーム開発の作業マシンで、AI エージェントのセッションが突然どれも動かなくなった。見える顔はこれだった。

bash        fork: retry: Resource temporarily unavailable
PowerShell  Starting the CLR failed with HRESULT 8007000e

どちらもメモリ枯渇の顔だ。原因がシェル側なのか、Windows 側なのか、この文言だけでは切り分けられない。

tasklist を見たら、答えはそこにあった。

   8078 MB  PID 977980   grep.exe      ← 4時間回りっぱなし

さらに find15本、最古は2日前から居残っていた。全部、別々のセッションが打ったコマンドの生き残りだ。人間が手で打つ分にはあまり起きないが、エージェントにシェルを任せる現場では、残骸が積み上がりやすい。

ここから犯人は二つある。順番に潰す。

犯人1:暴走を防ぐつもりで書いた上限が、暴走の原因だった

実際に居残っていたコマンドは、だいたいこういう形だった。

grep -o '"body":"[^"]\{0,2000\}assign\.json[^"]\{0,2000\}"' log.jsonl

直感では「{0,2000} で上限を付ければ安全」に見える。ところが同じファイル・同じ目的で測ると、結果は逆だった。

書き方結果
[^"]*(上限なし)395ms で完走
[^"]\{0,2000} ×2(上限あり)30秒でも終わらない(実際は4時間・約8GB)

入力を30バイトに減らしても、オートマトンを組むだけで上限に比例以上に伸びた。

{0,100}   410ms
{0,500}   484ms
{0,1000}  959ms
{0,1500}  2241ms
{0,2000}  5091ms

ファイルサイズは犯人ではなかった。11MB の小さい入力でも、大きな {n,m} だけで PC がハングする。

「新しい grep なら直るのか」→ 直らない(仕様)

GNU grep の公式マニュアル Known Bugs には、次のように書いてある(GNU Grep 3.11 のマニュアルページで確認。原文の要旨は変わっていない)。

Large repetition counts in the '{n,m}' construct may cause grep to use lots of memory. In addition, certain other obscure regular expressions require exponential time and space, and may cause grep to run out of memory.

公式 NEWS(savannah の plain NEWS、release 3.0 から 3.12 まで)を通して読んでも、この「大きな interval / bounded repetition のメモリ・時間」を直した、というエントリは見当たらなかった。

つまり不具合というより 既知の仕様 に近い。避けるのは呼ぶ側の責任になる。

実測の数字は GNU grep 3.0(Git Bash 同梱)のものだ。もっと新しい grep でも Known Bugs の文言は残っているが、こちらの環境以外では再測していない。

犯人2:find / が終わらない。ただし理由は通説と違う

よくある説明は「/ は C ドライブ全体だから重い」だ。Git Bash では、これは誤りだった。

実測では次のようになった。

では何が終わらないのか。/ 直下を個別に測った。

パスfind の所要時間
/bin /cmd /dev /etc /mingw64 /usr各 0.5〜0.7秒
/tmp6.9秒(76,702件)
/proc15秒でも終わらない

/proc の中を見ると、答えが出る。

$ ls /proc/registry
HKEY_CLASSES_ROOT  HKEY_CURRENT_CONFIG  HKEY_CURRENT_USER
HKEY_LOCAL_MACHINE  HKEY_PERFORMANCE_DATA  HKEY_USERS

Cygwin / MSYS は、Windows のレジストリ全体をファイルシステムとして /proc/registry に見せている。find / はここへ潜って、実質戻らない。

除外すると完走する。これが決定的な証拠だった。

$ find / -path '/proc/registry*' -prune -o -iname 'foo' -print
# 10秒で終わる

後始末(実際に使ったコマンド)

落とす前に、必ず何の残骸かを見る。

ps -ef                                    # cygwin側。古い find/grep がぶら下がっていないか
ps -W                                     # Windows PID も出る
cat /proc/<PID>/cmdline | tr '\0' ' '     # 何を打った残骸かが分かる
kill -9 <cygwin PID>

メモリ上位を出すときは、Git Bash がパス変換で壊さないよう、//FO の二重スラッシュにする。

tasklist //FO CSV //NH | sed 's/^"//; s/"$//' \
  | awk -F'","' '{m=$5; gsub(/[^0-9]/,"",m); if(m+0>150000) printf "%7.0f MB  PID %-8s %s\n", m/1024, $2, $1}' \
  | sort -rn

予防

timeout 30 <重くなり得るコマンド>
( ulimit -v 2000000; <メモリを食い得るコマンド> )   # 2GBで切る。破裂してもPC本体までは落とさない

まとめ

今回の PC ハングは、見た目は「bash が fork できない」「PowerShell が CLR を起動できない」だったが、中身は 約8GB の grep居残った find だった。

両方とも、次の二行で防げた。

  1. 正規表現に大きな {n,m} 上限を書かない(暴走防止のつもりが、暴走の原因になる)
  2. 範囲を書かない find / を打たない(Git Bash では C ドライブではなく /proc/registry が終わらない)

数字は Windows 11 / Git Bash / GNU grep 3.0 の実測だ。環境が違えば出ないこともある。同じ症状を疑うときは、まず tasklistps で残骸を見ること。

出典

© 2026 株式会社佐野組