hnwの日記

PHPのPCRE関数での\Q,\EはPerlと同一ではない

Perl正規表現で利用できる便利なエスケープシーケンスとして、\Qと\Eがあります。これはメタ文字の解釈を止めたり、その解釈を再開したりするものです。「man perlre」には次のような例が出てきます。

/$unquoted\Q$quoted\E$unquoted/


この正規表現は変数$unquotedと$quotedの値を利用しますが、利用のされかたが異なります。$unquotedの値はそのまま正規表現として解釈されますが、$quotedの値は文字列一致となります。

#!/usr/bin/perl -w
use strict;
use Data::Dumper;
my $quoted = '.?';
my $unquoted = '.';
my $subject = "a.?a";
print $subject =~ /^$unquoted\Q$quoted\E$unquoted$/; # 1


また、\Qや\Eの解釈は変数展開と別のタイミングで行われるので、$quotedの値に\Eを入れてもメタ文字の解釈が停止したりはしません。単に「\E」の2バイトにマッチするだけです。


文字列の一部をユーザーの入力値そのものとマッチさせたい場合など、Perl正規表現では重宝する書き方です。

PCRE関数での\Qと\E

一方、PHPにもPCRE(Perl互換正規表現ライブラリ)を利用する関数群があり、\Qや\Eを利用することができます。実際、次のような場合はPerlと同じように動作します。

<?php
var_dump(preg_match('/^a\Q.?\Ea$/', 'a.?a')); // int(1)


しかし、変数値の周りを\Qと\Eで囲むような使い方には注意が必要です。Perlと異なり、PHPでは変数展開後に\Qや\Eを解釈するので、変数値の文字の一部が悪さをすることがあります。

<?php
$quoted = '/';
$subject = "a/a";
var_dump(preg_match('/^a\Q'.$quoted.'\Ea$/', $subject)); # まずい例


上のように変数値にデリミタ文字が入ってきた場合、そこで正規表現の終了とみなされてしまうので、次のようなエラーが出てしまいます。

PHP Warning:  preg_match(): Unknown modifier '\' in preg-quote.php on line 6


つまり、PCRE関数では変数値を\Qと\Eで囲むような書き方はできません。Perl経験者がPHPプログラムを書く場合の注意点と言えるでしょう。


デリミタ文字以外にも、ヌル文字、バックスラッシュなども悪さをする可能性があり、コードの書き方によっては任意のコードを実行されてしまう脆弱性に発展しかねません(参考:「preg_replaceによるコード実行 - T.Teradaの日記」)。Perlの\Qや\Eは正規表現リテラルがあるからこそ機能するのであって、正規表現を文字列として扱うPHPでは同じようにいかないのです。

PCRE関数で変数値そのものを正規表現マッチさせる方法

では、PCRE関数でPerlの\Q,\Eと同じことをしたい場合はどうすればいいのでしょうか。


実は、PHPでは変数値そのものを正規表現としてマッチさせたい場合のために、preg_quoteという関数が用意されています。

preg_quote ― 正規表現文字をクオートする


説明


string preg_quote ( string $str [, string $delimiter = NULL ] )


preg_quote() は、str を引数とし、正規表現構文の特殊文字の前にバックスラッシュを挿入します。この関数は、実行時に生成される文字列をパターンとしてマッチングを行う必要があり、その文字列には正規表現特殊文字が含まれているかも知れない場合に有用です。


PHP: preg_quote


この関数はデリミタ文字やヌル文字・バックスラッシュもエスケープしますので、\Qのときのような事故は起こりません。先ほどのプログラムをpreg_quoteで書き直すと次のようになります。

<?php
$quoted = '/';
$subject = 'a/a';
var_dump(preg_match('/^a'.preg_quote($quoted, '/').'a$/', $subject));

まとめ