正規表現とデリミタとエスケープ

思いこみ禁物 - Note @ Temporary-Depotで触れられていた、Perl・PHP・JavaScriptそれぞれにおける正規表現マッチの動作の違いが気になったので、自分でも確認してみました。

PHPでPerlと同じ動作にならなかったのは、例えば $v = 'http://localhost/' だった時に、"/^$v/" で展開された正規表現パターンが /^http://localhost// となってしまうからだと思います。正規表現を囲うための文字(デリミタ)が、パターンの中にもそのまま現れているので、正規表現のパターンとして正しく読み取れなくなっているようです。

※ 自分が確認に使った環境(PHP 4.3.6)では、"Warning: Unknown modifier '/' in ..." というメッセージが出ていました。

PHPのマニュアルによると、スラッシュ(/)以外の記号や括弧類もデリミタとして使用できるそうなので、例えば以下のように変更することで、Perlと同じ結果が得られるかと思います。

    if (preg_match("{^$v}", $referer)) echo("match! : " . $v . "<br />");

正規表現のデリミタに括弧を使うのは、「デリミタと同じ文字をパターンの中で使う場合はエスケープしなければ」という気づかいが要らないので、便利だと思います。個人的にも、Perlで正規表現マッチするときには /pattern/ よりも m{pattern} という書き方をよく使います。

* * *

JavaScriptでPerlと同じ結果にならないのは、文字列のエスケープの違いによるもののようです。PerlやPHPでは\を含む文字列をダブルクォート(")で囲ったときとシングルクォート(')で囲ったときとでは扱いが異なりますが、JavaScriptではどちらで囲っても、\を常にエスケープ文字として扱うようです。

手っ取り早い確認方法: 以下のコードをブラウザのURL欄にコピー&ペーストしてEnterを押すと、true とalert表示されます。

javascript:alert('\/' === '/');

なので、配列変数check_urlsを以下のように設定しておけば、Perlと同様の結果を得られるかと思います。

var check_urls = [
    'http://localhost/',
    'http:\\/\\/localhost\\/',
    'http://local\\w*?/'
];

追記: check_urlsの要素を予め正規表現リテラルにする手もありかも。

var check_urls = [
    /^http:\/\/localhost\//,
    /^http:\/\/local\w*?\//
];

  (…中略…)

    if(referer.match(check_urls[i])) document.write("match! : " + check_urls[i] + "<br />");