本文へスキップ
AZ Tools

実務のための正規表現

正規表現には「書き捨て専用」という評判があります。作るのは簡単、読み返すのは難しく、実データでだけ静かに間違う。その大半は、エンジンが何をしているかについての少数の誤解から来ています。このガイドではそれらを順に解きほぐし、最後にパターンをしまうべき場面を扱います。

エンジンが実際にしていること

正規表現は「欲しい文字列の説明」ではなく、入力の各位置で左から順に実行される小さなプログラムです。インデックス0でマッチを試み、失敗すれば1へ移って再試行し、何かがマッチするか入力が尽きるまで続けます。意外な結果のほとんどはこの走査から説明できます。

だからこそ `\d{3}` は "abc1234def" で平然とマッチします。真ん中に "123" を見つけただけで、パターンのどこにもそれを禁じる記述がなかったからです。「数字を3つ含む」と「ちょうど数字3つである」は別の問いで、パターンは尋ねた方にしか答えません。

アンカーと単語境界

`^` と `$` はパターンを入力の先頭と末尾に結び付けます。マルチラインフラグを付けると各「行」の先頭と末尾になり、これがバリデータを静かに緩くする原因になります。`\b` は単語文字と非単語文字の間にあるゼロ幅の位置なので、`\bcat\b` は "the cat sat" の猫を見つけますが "concatenate" の中の文字は拾いません。

検証目的ならパターン全体をアンカーで囲み、通したい値だけでなく「弾きたい」値でも試してください。良い値をすべて通しつつゴミも通すバリデータは、無いより悪いものです。原因の追跡がはるかに難しい下流へ失敗を押しやるだけだからです。

貪欲、非貪欲、そして破滅的バックトラッキング

量指定子は既定で貪欲です。`.*` は取れるだけ取り、パターンの残りが失敗したときにだけ文字を返します。`?` を付ければ非貪欲になります。`<b>one</b> and <b>two</b>` に対して `<.*>` は行全体を飲み込み、`<.*?>` は最初の `>` で止まります。意図は同じでも結果はまるで違います。

その「返す」動作こそ性能が崩れる場所です。重なり合う文字集合の上で量指定子を入れ子にすると——教科書的な例が `(a+)+b`——エンジンはマッチ不能を認めるまでに指数的な数の分割を試すことがあります。攻撃者が与えるテキストではこれはサービス拒否の脆弱性(ReDoS)です。量指定子付きのグループ同士を重ねないこと、そして可能な限り `.` ではなく具体的な文字クラスを使うことです。

文字クラス、ドット、そしてUnicode

`.` は dotall フラグを立てない限り「改行以外の任意の1文字」であり、`\d`・`\w`・`\s` は多くの実装でASCII中心です。`\w` はアクセント付き文字を除外し、`\d` が他の文字体系の数字を含むかは実装によります。純粋なASCIIでないテキストにはUnicodeモードを有効にし、`\p{L}`(任意の文字)や `\p{N}`(任意の数字)といったプロパティエスケープを使いましょう。

クラス内の範囲はコードポイントの範囲なので、`[A-z]` は `Z` と `a` の間にある6つの記号を黙って含みます。意図したクラスをそのまま書いてください。そして見た目が同じテキストでも並びが違いうることも忘れずに。単一コードポイントの "é" は、"e" と結合アクセントを前提としたパターンにはマッチしません。比較の前に正規化しましょう。

正規表現が誤った道具になるとき

入れ子と引用符のある形式——HTML、JSON、CSV、ソースコード——は正規言語ではありません。パターンで少しずつ齧ることはできますが、引用符付き区切りひとつ、入れ子ひと段ごとに例外が増え、やがて読めないうえにまだ見ぬ入力では依然として誤る式になります。本物のパーサーを使いましょう。CSVなら引用を守るもの、HTMLならDOMです。

メールアドレスはもうひとつの定番の罠です。仕様上の文法はネットから貼り付けたパターンよりはるかに大きいため、厳格に見える正規表現は有効なアドレスを日常的に弾きます——プラス記号入りや長い新しいトップレベルドメインなど、ごく普通のものまで。`@` の両側にそれらしい何かがあるかだけを確認し、あとは唯一有効な方法で証明してください。そのアドレスにメールを送るのです。

  • 意図した位置でマッチさせる。検証なら `^`…`$` で囲み、検索なら囲まない。
  • 区切りが分かっているなら `.*?` より `[^"]*`。明確でバックトラックもしません。
  • 通したい入力だけでなく、弾きたい入力でテストする。
  • 形式に入れ子や引用符があるならパーサーを使う。

関連ツール