正規表現の先読み・後読みを5つのエンジンで試してわかったこと

(?=)、(?!)、(?<=)、(?<!) を Python・Perl・PHP・JavaScript(Dart)・.NET で同じパターンで実測。後読みの長さ制限の違い、貪欲マッチの罠、2021年の例の誤り。

2026年11月改訂: この記事は2021年に韓国語で書いた講座を、日本語で書き直したものです。すべての例を、Python 3.9 の re、Perl 5.34、PHP 8.5(PCRE2 10.47)、Dart 3.11(RegExpはJavaScriptと同じ文法)、.NET 10 の5つのエンジンで動かし直しました。元の記事の最後の例はどのエンジンでも説明どおりに動かず、後読みで許される範囲もエンジンごとに違いました。以下の結果はすべて実測です。

先読み・後読みとは

先読み・後読み(lookaround)は、今の位置の前(または後ろ)にあるパターンが存在するかを確かめるだけで、その部分はマッチ結果に含めない書き方です。幅ゼロの条件だと考えてください。

記号 名前 意味
(?=…) 肯定先読み 後ろに…がある
(?!…) 否定先読み 後ろに…がない
(?<=…) 肯定後読み 前に…がある
(?<!…) 否定後読み 前に…がない

先読み (?=)

.+(?=:) は「コロンの手前までの文字列」を探し、コロン自体は結果から外します。コロンをそのまま書く(.+:)と、結果は http: のようにコロンを含みます。

テキスト .+(?=:) の結果(5エンジン共通)
http://www.abc.com http
http://www.abc.com:8080 http://www.abc.com ← 罠

2行目が、2021年の記事で抜けていた罠です。.+ は貪欲(greedy)なので、できるだけ長く、つまり最後のコロンの手前まで取ります。最初のコロンまでにしたいなら次のどれかです。

パターン http://www.abc.com:8080 での結果
.+?(?=:)(最短一致) http、://www.abc.com(2件)
[^:]+(?=:)(コロン以外) http、//www.abc.com(2件)
^[^:]+(?=:)(先頭に固定) http(1件)

3つとも5つのエンジンで同じ結果でした。「先頭のスキームだけ」が欲しいなら、先頭に固定した最後のパターンがいちばん意図がはっきりします。

後読み (?<=)

後読みは逆に前を見ます。(?<=\$)[0-9.]+ は $ の直後の数字だけを取り出します。$ はメタ文字なので \$ とエスケープします。

テキスト 結果(5エンジン共通)
1: $600.4 2: $10.25 600.4、10.25

(?<=,)[a-z\s!]+ を Hello, world! に使うと、結果は world! ではなく先頭に空白の付いた world! です。文字クラスに \s が入っているので、カンマの後ろの空白も一緒に取れます。空白を外したいなら、(?<=,\s)[a-z!]+ のように後読みの中に空白を入れます。

否定後読み (?<!) と、2021年の例の訂正

2021年の記事では、\b(?<!\$)\d+ を $105$677$788 に使うと「$ のない数字だけを探す」と説明していました。実際に動かすと、5つのエンジンすべてで結果は0件です。このテキストの数字はすべて $ の直後にあるので、探す対象そのものがありません(記事を移行したときに空白が消えた可能性が高いです)。空白のあるテキストで測り直しました。

パターン $105 677 $788 での結果
\b(?<!\$)\d+ 677
(?<![\d$])\d+ 677

\b が必要なのは、それがないと $105 の 05 のように数字の途中からマッチが始まりうるからです。2つ目のパターンのように「前に数字も $ もない」を条件にすれば、\b なしで同じ結果になります。

エンジンで違うこと:後読みの長さ

この記事でいちばん大事な部分です。後読みの中のパターンの長さをどこまで許すかは、エンジンごとに違います。

  • パターン①:(?<=\$|USD )\d+。後読みの中の選択肢の長さが違う(1文字と4文字)
  • パターン②:(?<=\$\s*)\d+。後読みの中に長さの上限がない
パターン テキスト Python 3.9 re Perl 5.34 PHP 8.5(PCRE2 10.47) JavaScript(Dart 3.11) .NET 10
① $5 USD 7 エラー 5、7(実験的機能の警告) 5、7 5、7 5、7
② $ 42 エラー エラー エラー 42 42

実際のエラーメッセージです。

Python : look-behind requires fixed-width pattern
PHP    : Compilation failed: length of lookbehind assertion is not limited at offset 0
Perl   : Lookbehind longer than 255 not implemented
Perl   : Variable length lookbehind is experimental in regex   (警告。動作はする)

まとめると次のとおりです。

  • どこでも通るパターンが必要なら、後読みの中は長さが固定のパターンだけにする((?<=\$)、(?<=USD ))
  • 選択肢の長さが違うなら、(?:(?<=\$)|(?<=USD ))\d+ のように後読みを2つに分ける。Pythonを含む5つのエンジンすべてで 5、7 が返った
  • 長さに上限のない後読みがどうしても必要なら、キャプチャグループに置き換えるのが移植しやすい。\$\s*(\d+) は5つのエンジンすべてで $ 42 にマッチし、グループ1は 42 になる
  • Pythonでどうしても使うなら、サードパーティの regex モジュールは可変長の後読みに対応している(標準の re は非対応)

実務での使い道:パスワードの条件チェック

先読みをパターンの先頭に複数並べると、「すべての条件を同時に満たす」を表せます。

^(?=.*\d)(?=.*[A-Z]).{8,}$
入力 結果(5エンジン共通)
Passw0rdX マッチ(数字あり、大文字あり、8文字以上)
password1 マッチしない(大文字なし)

それぞれの (?=…) は位置を動かさずに条件だけを確かめるので、条件はいくつでもつなげられます。ただ、規則が複雑になったら、1つの正規表現より条件ごとに分けたコードのほうが、エラーメッセージも出しやすく保守もしやすくなります。

実験の方法

同じパターンとテキストを1つのTSVファイルに書き、エンジンごとに10行ほどの実行プログラムを作って、すべてのマッチを出力しました。

エンジン 使った呼び出し
Python re.findall
Perl while (/re/g)
PHP preg_match_all(/u フラグ)
Dart RegExp(..., unicode: true).allMatches
.NET Regex.Matches(.NET 10 の単一ファイル実行 dotnet run t.cs)

正規表現の「標準」は1つではありません。自分のコードが使うエンジンで直接確かめる習慣が、いちばん確実です。

よくある質問

後読みの中で * や + を使える?

エンジンによります。(?<=\$\s*) のような長さに上限のない後読みが動いたのは .NET と JavaScript(Dartで確認)だけで、Pythonのre、PHP(PCRE2 10.47)、Perl 5.34 ではコンパイルエラーになりました。

.+(?=:) がURLでおかしな結果になるのはなぜ?

.+ が貪欲(greedy)で、最後のコロンの手前まで取るからです。http://www.abc.com:8080 では http ではなく http://www.abc.com にマッチします。スキームだけ欲しいなら ^[^:]+(?=:) を使います。

先読みでパスワードの条件をチェックできる?

できます。^(?=.*\d)(?=.*[A-Z]).{8,}$ は、数字と大文字をそれぞれ1つ以上含む8文字以上の文字列にだけマッチします。5つのエンジンすべてで同じ結果でした。

  • #正規表現
  • #先読み
  • #後読み
  • #Python
  • #PHP
  • #.NET

t.laboratory

実際に作って運用しているアプリ・サーバー・サービスで起きた問題と、その解決に使ったコードを記録する技術ブログです。

運営者情報 · アプリ

↑