2009年2月20日
PHPの正規表現と文字列
PHPで正規表現を扱う上で、正規表現パターンの記述で迷ってしまっている人が多い気がする。
- PHPが文字列を生成する
- その文字列を正規表現エンジンに渡す
と言う2段階の手順があり、そこがやや複雑なので混乱を招く部分があるのだと思う。
混乱を防ぐためには、その手順をしっかり追って、正規表現リテラルとして指定している文字列をechoしたらPHPがどのように出力するかを考えてみると良い。
例えば以下のようなコードがあったとする。
preg_match('/\\d/','3d');
まず、preg_match()の第一引数になっている正規表現パターンをechoで出力するとどうなるかを考える。
echo '/\\d/'; #/\d/
なので、/\d/が正規表現エンジンに渡されると分かる。なので、上記のパターンはマッチする。
さらに混乱を招きやすくしているのが、PHPの文字列の仕様で、特にバックスラッシュのエスケープに関する仕様だ。
PHPマニュアルの文字列の項より抜粋。
引用符をリテラルとして指定するには、多くの他の言語と同様にバックスラッシュ (\) でエスケープする必要があります。 バックスラッシュを引用符の前または文字列の最後に置きたい場合は、 二重にする必要があります。この他の文字をエスケープする場合には、 バックスラッシュも出力されることに注意してください! このため、 通常はバックスラッシュ自体をエスケープする必要はありません。
つまり、エスケープが必要な文字が後に続かない場合、バックスラッシュはそのまま出力される。ただし、バックスラッシュをエスケープしても構わない(エスケープしないと正しく表示されないケースもある)。このエスケープするときのみバックスラッシュを意識すればよいという一見親切な設計が、混乱を招いている。実にPHPらしい。以下実例。
echo '\a' ; # \a そのまま出力される
echo '\\a' ; # \a エスケープされて出力
echo '\\' ; # \
echo '\' ; # !Parse Error 「'」がエスケープされ、文字列が閉じてないとみなされる。
echo '\\\a' ; # \\a 一方のみエスケープされるので2つ出力
echo '\\\' ; # !Parse Error 文字列が閉じていない
echo '\\\'' ; # \'
echo '\\\\' ; # \\
echo '\\\\a'; # \\a
解決策としては、バックスラッシュを書くときは、問答無用でエスケープしてしまうのが正解だと思う。1つ書きたいなら2つ重ねて。2つ書きたいなら4つ重ねて。
1つだけでもちゃんと出力してくれるときがあるので、逆に良く分からなくなっているのだと思う。3つ重ねると、2つ出力とか分かりづらい。そんなありがた迷惑な挙動はお断りしてしまいましょう。
また、ダブルクオート文字列内でエスケープ文字を記述する際にも混乱が起きやすい。PHPの文字列としてマッチさせようとしているのか、正規表現エンジンにマッチさせようとしているのかで混乱してしまうからだ。以下は、何れもタブ文字を削除するコードになる。
$str = str_replace("\t",'',$str);
$str = preg_replace("/\\t/",'',$str);
正規表現を使ったpreg_replaceだと、バックスラッシュを重ねる必要がある。さっきも述べたように、"/\\t/"をechoすると、/\t/になるので、それが正規表現エンジンに渡っているからだ。
このように、PHPで正規表現を扱う際には、一種のメタプログラミング状態になってしまっているので理解が難しい。