基礎●●○○○

正規表現

Regular Expressionせいきひょうげん

文字列のパターンを小さな記法で表現し、検索・抽出・置換を行う道具。ほぼすべての言語とツールに載っている。

概要

正規表現は、「数字4桁、ハイフン、数字2桁…」のような文字列のパターンを、記号を組み合わせた小さな式で表現する記法です。たとえば \d{4}-\d{2}-\d{2} と書けば「2026-07-18 のような日付らしき文字列」にマッチします。この式ひとつで、検索(含まれているか)、検証(形式が正しいか)、抽出(該当部分を取り出す)、置換(別の文字列に書き換える)ができます。

活躍の場は驚くほど広く、エディタの検索置換、grep などのコマンドラインツール、フォームの入力検証、ログの解析、ルーティングの URL パターンまで、文字列を扱うほぼすべての場面に顔を出します。JavaScript・Python・Java など主要言語のすべてに標準装備されており、プログラミング言語をまたいで通用する数少ない「共通語」のひとつです。

一方で、記号の羅列は初見では呪文のように見え、書けても読めない・読めても保守できないコードの温床にもなりがちです。部品ごとの意味と、マッチングの裏側で何が起きているかを一度押さえてしまえば、呪文は「小さなプログラミング言語」として読み解けるようになります。

なぜ生まれたか

正規表現がなかった時代、文字列のパターン検索や形式チェックは、1文字ずつ読み進めるループと条件分岐を毎回手書きするしかありませんでした。「数字が4つ続いてハイフンが来て…」を素朴に書くとそれだけで数十行になり、パターンが少し変わるたびにループを書き直すことになります。処理の本質は「どんなパターンか」なのに、コードの大半は「どう照合するか」の手続きで埋まってしまう — この乖離が問題でした。正規表現は、パターンそのものを宣言的に(手順ではなく形として)記述する小言語を用意し、照合の手続きはエンジンに任せる、という分業で解決します。

系譜をたどると出発点は意外にも純粋数学で、1951年に数学者スティーヴン・クリーネが「正規言語」という文字列集合のクラスを定義したことに始まります。1968年にケン・トンプソンがこの理論をテキストエディタの検索機能として実装し、そこから生まれた UNIX の grep が実用ツールとしての地位を確立しました。1980年代には Perl が後方参照などの拡張を加えて実務の道具として磨き上げ、その方言(PCRE: Perl互換正規表現)が事実上の標準として各言語に広がっていきました。

1951 クリーネの正規言語理論1968 トンプソンによるエディタ実装1973 UNIX の grep1980年代 Perl による拡張と普及現在 ほぼ全言語に標準装備
理論から共通語へ — 正規表現の系譜

詳細

基本部品 — 5種類覚えれば読める

正規表現の記法は多く見えますが、部品は5種類に整理できます。(1)リテラル: abc はそのまま文字列 abc にマッチします。(2)文字クラス: [0-9] は数字1文字、\d はその略記、. は任意の1文字です。(3)量指定子: 直前の部品の繰り返し回数で、* は0回以上、+ は1回以上、? は0か1回、{4} はちょうど4回です。(4)アンカー: ^ は行頭、$ は行末という「位置」にマッチし、文字を消費しません。検証用途で ^...$ と囲み忘れると「一部だけ一致」を通してしまう定番のミスがあります。(5)グループと選択: (...) はまとまりを作って抽出(キャプチャ)に使い、| は「または」を表します。

対象例: 2026-07-18^\d{4}-\d{2}-\d{2}$アンカー行頭の位置文字クラス + 量指定子数字がちょうど4回リテラルハイフンそのもの数字がちょうど2回月の部分リテラル + 繰り返し日の部分アンカー行末の位置^と$で囲むと全体一致の検証に
パターンの分解 — 日付形式を検証する正規表現の部品と役割

実際にパターンを書いて、どこにマッチするかを手元で確かめてみましょう。

⚡ 体験: パターンがどこにマッチするかを見る
プリセット:
リリース日は 2026-07-18、次回は 2026-9-1 の予定です。
#12026-07-18index: 7

パターンを書き換えると、マッチ箇所が即座に更新されます。プリセットで典型パターンも試せます。

貪欲と怠惰 — 「マッチしすぎる」問題

量指定子は既定で「貪欲(greedy)」、つまり可能な限り長くマッチしようとします。"a".*" のようなパターンを say "hi" and "bye" に適用すると、.* は最初の " から最後の " までを一気に飲み込み、"hi" and "bye" 全体にマッチしてしまいます。「最初の閉じ引用符まで」で止めたいときは .*? のように ? を付けて「怠惰(lazy)」にするか、[^"]*(引用符以外の繰り返し)と書きます。「思ったより広くマッチする」トラブルの大半はこの貪欲さが原因で、後者の否定文字クラスを使う書き方のほうが意図も明確で性能も安定するため、実務ではまずこちらを検討します。

バックトラッキングと ReDoS

この「マッチしすぎたら戻ってやり直す」動きをバックトラッキングと呼び、Perl 系エンジンのマッチングの基本戦略です。理論上、正規表現は有限オートマトン(状態遷移機械)に変換して文字列を1回走査するだけで照合できます。この理論はコンパイラの字句解析(ソースコードを単語に切り出す工程)の基礎でもあります。しかし後方参照などの拡張機能はオートマトンでは表現できないため、多くの言語のエンジンは「試してだめなら戻る」試行錯誤方式を採っています。

この方式には落とし穴があります。(a+)+$ のように繰り返しを入れ子にしたパターンに aaaa...b のようなマッチしない入力を与えると、「どこで区切るか」の組み合わせを総当たりし、入力長に対して指数的な時間がかかります。これが破滅的バックトラッキング(catastrophic backtracking)で、これを突いて1本の文字列でサーバの CPU を占有させる攻撃を ReDoS(正規表現サービス拒否攻撃)と呼びます。皮肉なことに、入力検証のために置いたWAFやバリデーションの正規表現自体が攻撃対象になった実例が多数あります。対策は、繰り返しの入れ子を避ける、マッチ時間に上限を設ける、あるいは Go の RE2 のようにバックトラッキングを使わない線形時間エンジンを選ぶことです。

正規表現で解くべきでない問題

最後に、道具の限界を知っておくことが重要です。正規表現が扱えるのは本来「正規言語」、直感的には入れ子の対応を数えなくてよいパターンまでです。HTML や JSON のようにタグや括弧が任意の深さで入れ子になる構造は原理的に守備範囲外で、「HTML を正規表現でパースしようとするな、パーサを使え」は先人たちの傷跡が刻まれた格言です。また、メールアドレスの完全な検証のように仕様が複雑すぎるものも、簡易チェックに留めて確認メール送信など別の手段と組み合わせるのが現実解です。1行で書けそうに見えても、読み手が解読に10分かかる正規表現は負債になります。長くなったらコメント付きの冗長モードで書く、複数の単純な式に分割する、いっそ普通のコードで書く — 「書けるか」ではなく「保守できるか」で使いどころを判断するのが、この強力な小言語との正しい付き合い方です。