正規表現の基本10パターンとReDoSの防ぎ方
2026.10.03 更新 · 5分で読めます
よく使う正規表現の基本パターン10個を、実際に実行した例とあわせて整理します。(a+)+のような入れ子の繰り返しでバックトラックが爆発する仕組み(ReDoS)と、パターンの直し方も解説します。
正規表現チェッカーをすぐに使う日常的な正規表現は、ドット、3つの省略クラス、文字集合、アンカー、量指定子、グループ、最短一致の10個でほとんど書けます。避けたい書き方は1つだけです。(a+)+のように繰り返しの中に繰り返しを入れると、ほぼ一致するのに最後で一致しない文字列で、実行時間が指数的に増えることがあります。この記事の例はすべて正規表現チェッカーでそのまま試せます。文法はJavaScript(ECMAScript)です。
基本の10パターン
例はすべて、一致する箇所をすべて探すgフラグで実行した結果です。
| # | パターン | 意味 | 例 | 一致 |
|---|---|---|---|---|
| 1 | . |
改行以外の任意の1文字 | c.t → cat cot c-t ct |
cat, cot, c-t |
| 2 | \d |
数字(0から9) | \d+ → v2.10 build 345 |
2, 10, 345 |
| 3 | \w |
英字・数字・アンダースコア | \w+ → user_id=42; name=kim |
user_id, 42, name, kim |
| 4 | \s |
空白・タブ・改行 | a\s+b → a b, a b, ab |
a b, a b |
| 5 | [abc] [^abc] |
並べた文字のどれか、またはそれ以外 | [aeiou] → zekilo |
e, i, o |
| 6 | ^ $ |
文字列の先頭と末尾 | \.json$ → data.json |
.json |
| 7 | * + ? |
0回以上、1回以上、0回または1回 | colou?r → color colour colr |
color, colour |
| 8 | {n} {n,m} |
ちょうどn回、n回からm回 | a{2,3} → a aa aaa aaaa |
aa, aaa, aaa |
| 9 | (…) | |
グループ、複数の候補のどれか | cat|dog → cat, dog, cow |
cat, dog |
| 10 | *? +? |
最短一致(できるだけ短く) | <.+?> → <b>bold</b> |
<b>, </b> |
最初に知っておくとよい点です。
- 特殊文字はエスケープします。 ドットはほとんどの文字に一致するので、本物のドットは6番のように
\.と書きます。 - 量指定子は既定で最長一致です。 10番から
?を外すと、<.+>は<b>bold</b>全体を1つの一致として返します。 \dと\wは全角文字や日本語に一致しません。123 456に\d+を使うと一致するのは半角の456だけです。日本語はuフラグを付けて[\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Han}]+と書くと、ZEKILO ゼキロ dev 東京からゼキロと東京が見つかります。- フラグで動作が変わります。
iは大文字と小文字を区別せず、mは^と$を行ごとに働かせ、sはドットを改行にも一致させます。
グループと置換
かっこは一致した部分を記憶するので、置換で再利用できます。次の例の「연락처」は韓国語で「連絡先」の意味です。
パターン (\d{3})-(\d{4})-(\d{4}) フラグ g
対象 연락처: 010-1234-5678, 02-123-4567, 010-9876-5432
一致 1 010-1234-5678 インデックス 5 グループ: 010, 1234, 5678
一致 2 010-9876-5432 インデックス 33 グループ: 010, 9876, 5432
真ん中の番号は最初の部分が2桁なので一致しません。置換では$1や$3がグループを指すため、置換後の文字列を$1-****-$3にすると中央の4桁を伏せられます。
연락처: 010-****-5678, 02-123-4567, 010-****-5432
グループが増えたら番号の代わりに名前を付けます。(?<year>\d{4})-(?<m>\d{2})を2026-10に適用するとyearは2026、mは10になり、置換では$<year>のように書きます。
バックトラックの爆発とは
バックトラック型のエンジンは、ある方法で照合して失敗すると、戻って次の方法を試します。ふつうは方法が数通りしかありませんが、(a+)+$は違います。内側のa+と外側の+が連続するaを分ける組み合わせが非常に多く、末尾に一致を妨げる文字があると、エンジンはその組み合わせをすべて調べてからようやく「一致なし」と答えます。
OWASPは^(a+)+$を例に挙げています。入力aaaaXでは通り得る経路が16通り、aが16個のあとにXが続くと65,536通りで、aが1つ増えるたびに2倍になります。実行時間も同じ形になります。連続するaのあとにbを付けた文字列に(a+)+$を実行した結果です(Node.js 24.14、デスクトップPC 1台、3回の中央値)。
aの数 |
かかった時間 |
|---|---|
| 20 | 20 ms |
| 22 | 85 ms |
| 24 | 300 ms |
| 26 | 1,311 ms |
数値は端末やエンジンによって変わりますが、2文字増えるごとに約4倍になる傾向は同じです。同じ26文字に対して、入れ子をなくしたa+$は0.1 msもかかりませんでした。
この問題はReDoS(正規表現によるサービス拒否)と呼ばれます。訪問者が送った文字列にこうしたパターンを適用するサーバーは、1つのリクエストでCPUを長時間占有されるおそれがあります。OWASPは危険なパターンの条件として、繰り返しの付いたグループと、その中にある別の繰り返しまたは重なり合う選択肢を挙げ、(a+)+$、([a-zA-Z]+)*$、(a|aa)+$、(a|a?)+$を例示しています。
防ぎ方
- 量指定子を入れ子にしません。
(a+)+はa+と、(\d+)*は\d*と同じ文字列に一致します。 - 選択肢が重ならないようにします。
(a|aa)+は2つの候補が同じ位置から始まります。1つの位置を1通りにしか読めないよう、a+に直します。 .*の代わりに狭い文字集合を使います。 引用符で囲まれた文字列は"[^"]*"と書くと、"a" and "b"から"a"と"b"を別々に見つけます。".*"は最初の引用符から最後の引用符までをまとめて取ります。- 入力の長さを制限し、アンカーを付けます。
a+$も非常に長い文字列では遅くなります。先頭のアンカーがないと位置ごとに探し直すため、aが10万個のあとにbが続く文字列では約7秒かかり、^a+$は1 msもかかりませんでした(同じ環境)。 - 時間制限を設けます。 正規表現チェッカーはパターンを常に別のWorkerで実行し、1,000msで止めます。
(a+)+$にa30個とbを入れると、画面が固まる代わりに「パターンの実行に時間がかかりすぎます」と表示されます。 - ユーザー入力からパターンを組み立てません。 利用者にパターンを入力させる必要があるなら、RE2のように入力の長さに比例する時間を保証するエンジンを検討してください。そのかわりRE2は後方参照と先読み・後読みに対応していません。
PCREやJava向けの解説で勧められるアトミックグループ(?>…)と強欲な量指定子a++は、2026年10月時点でECMAScriptの正規表現の文法になく、Node.js 24ではどちらも構文エラーになります。JavaScriptではパターンを書き直すのが解決策です。
ZEKILO Devで確認する
- 正規表現チェッカーにパターンを入力し、対象のテキストを貼り付けます。
- フラグ(
gimsuyd、対応ブラウザーではv)とモード(検索・置換・分割)を選びます。 - 一致数と、一致ごとの位置・グループの表を確認します。一致した箇所は対象テキスト上でも強調されます。
公開する前に「ほぼ一致する」文字列、たとえば正しい長い値の末尾に誤った文字を1つ付けた入力を試してください。タイムアウトになるならパターンを直す必要があります。パターンと対象テキストはブラウザ内だけで処理します。
まとめ
- ドット、
\d・\w・\s、文字集合、アンカー、量指定子、回数指定、グループと選択、最短一致を知っていれば、ほとんどのパターンを読めます。 (a+)+のような入れ子や重なりのある繰り返しは、ほぼ一致する文字列で時間が指数的に増えます。- パターンを書き直し、入力の長さを制限し、時間制限を設けて実行してください。