PHPでPerl相互の正規表現を使ってみる 1日目

参考: http://www.php.net/manual/ja/book.pcre.php

初めての正規表現

文字列中に特定のパターンが含まれるか確認するにはpreg_match関数を使用する
この関数は第一引数に正規表現を取り、第二引数に対象の文字列を取る
そして、対象の文字列にパターンが含まれる場合は1を返し、含まれない場合は0を返す


PHP正規表現はデリミタで囲われたパターンとその後の修飾子で構成される
例えば /fo+/im という文字列の場合 / がデリミタ、 fo+ がパターン、 im が修飾子である
デリミタには英数字、バックスラッシュ、空白文字以外の任意の文字を使うことができる
開始デリミタが開きかっこの場合は対になる閉じかっこが終了デリミタになる


単純に文字列を検索するには検索対象をデリミタで囲うだけで良い
サンプル: 文字列fooの検索

<?php
if ( preg_match('/foo/', "foobarbaz") ) {
	echo "fooが含まれる\n";
} else {
	echo "fooが含まれない\n";
}


正規表現には特別な意味のあるメタ文字があり
これらを単純な文字として扱うにはバックスラッシュでエスケープする必要がある

\ ^ $ . [ ] | ( ) ? * + { }


サンプル: 文字列C++の検索

<?php
if ( preg_match('/C\+\+/', "Perl PHP C++ Java") ) {
	echo "C++が含まれる\n";
} else {
	echo "C++が含まれない\n";
}

文字を表すメタ文字列

1種類以上の1文字を表すメタ文字列を以下に示す

.         LF(0x0A)以外の1文字にマッチ
\0        ヌル文字にマッチ
\a        アラート(0x07)にマッチ
\C        1バイトにマッチ
\d        数字にマッチ
\D        数字以外にマッチ
\e        エスケープ文字(0x1B)にマッチ
\f        改ページ(0x0C)にマッチ
\h        水平方向の空白(0x09, 0x20)にマッチ
\H        水平方向の空白文字(0x09, 0x20)以外にマッチ
\n        LF(0x0A)にマッチ、普通改行を示すにはこれを使う
\N        LF(0x0A)以外にマッチ、{}が続くとエラーになる
\r        CR(0x0D)にマッチ
\R        改行(0x0A-0x0D)にマッチ
\s        空白(0x09, 0x0A, 0x0C, 0x0D, 0x20)にマッチ
\S        空白(0x09, 0x0A, 0x0C, 0x0D, 0x20)以外にマッチ
\t        タブ文字(0x09)にマッチ
\v        垂直方向の空白(0x0A-0x0D)にマッチ
\V        垂直方向の空白(0x0A-0x0D)以外にマッチ
\w        単語構成文字(0-9, A-Z, a-z, _)にマッチ
\W        単語構成文字(0-9, A-Z, a-z, _)以外にマッチ


サンプル: 数字の検索

<?php
if ( preg_match('/\d/', "foo012bar") ) {
	echo "数字が含まれる\n";
} else {
	echo "数字が含まれない\n";
}

正規表現を使う際に注意することその1

正規表現として評価される前に文字列として評価されることを忘れてはならない


"/\n/" という文字列は \n がダブルクォート中でエスケープシーケンスなので
/LF/ となり LF 1文字にマッチする
"/\\n/" という文字列は \\ がダブルクォート中でエスケープシーケンスなので
/\n/ となり \n は正規表現のメタ文字列なので LF という意味を持ち
結果的に前者と同じく LF 1文字にマッチする


"/\v/" という文字列は \v がダブルクォート中でエスケープシーケンスなので
/VT/ となり VT 1文字にマッチする
"/\\v/" という文字列は \\ がダブルクォート中でエスケープシーケンスなので
/\v/ となり \v は正規表現のメタ文字列なのでアスキーコードの 0x0A-0x0D のいずれかという意味を持ち
前者と異なる結果になる


"/\w/" などのエスケープシーケンスでないものはそのまま /\w/ となるが、将来もそうであるとは限らない
これらを意識しなくて済むように正規表現を書くときにはシングルクォートを使った方が良い

量指定子

量を指定するメタ文字列を以下に示す

*        0個以上の連続にマッチ
+        1個以上の連続にマッチ
?        0個または1個の連続にマッチ
{n}      n個の連続にマッチ
{n,}     n個以上の連続にマッチ
{n,m}    n個以上m個以下の連続にマッチ


サンプル: 2個以上連続した o の検索

<?php
if ( preg_match('/o{2,}/', "foobarbaz") ) {
	echo "連続したoが含まれる\n";
} else {
	echo "連続したoが含まれない\n";
}

位置を表すメタ文字列

特定の位置を表すメタ文字を以下に示す

^         文字列の先頭にマッチ
$         文字列の最後にマッチ
\A        文字列の先頭にマッチ
\b        単語境界にマッチ
\B        単語境界以外にマッチ
\G        開始位置にマッチ
\z        行の最後にマッチ
\Z        文字列の最後にマッチ

\B は文字にマッチするメタ文字列だが \b と対比するためここに含めた


サンプル: 末尾にある数字を検索する

<?php
if ( preg_match('/\d$/', "foobarbaz0123") ) {
	echo "最後の文字は数字である\n";
} else {
	echo "最後の文字は数字ではない\n";
}

文字クラス

文字クラスとは文字の集合で1文字を表すものである


任意の文字群の集合は [ ] で囲って表す

[abc]    a, b, c のいずれか1文字にマッチ


任意の文字群以外の集合は [^ ] で囲って表す

[^abc]    a, b, c 以外の1文字にマッチ


任意の文字群を範囲で指定するには - を使う

[a-z]    a から z のいずれか1文字にマッチ


文字クラスには文字を表すメタ文字列も使用できる
\b は文字クラス中に限りバックスペース(0x08)を表す
特別な意味がある \ ^ - ] を集合に含めるにはバックスラッシュでエスケープする必要がある
ただし、先頭以外の ^ や先頭または最後の - はその必要がない
その他のメタ文字は文字クラス中で特別な意味を持たない


サンプル: 0xから始まる2桁の16進数を検索する

<?php
if ( preg_match('/0x[0-9A-Fa-f]{2}/', "<0x5F>") ) {
	echo "16進数が含まれる\n";
} else {
	echo "16進数が含まれない\n";
}

分岐

複数のパターンで検索するには | による分岐を使用する


サンプル: .php か .pl を検索する

<?php
if ( preg_match('/\.php|\.pl/', "public_html/app/index.php") ) {
	echo ".phpか.plが含まれる\n";
} else {
	echo ".phpも.plも含まれない\n";
}

グループ化

パターンをグループとして表すには () で囲う

foo(bar|baz)    foobar か foobaz にマッチ


グループに対して量指定子を使用することができる

(foo){3}        foofoofoo にマッチ


サンプル: 8桁の2進数の連続か確認する

<?php
if ( preg_match('/\A([01]{8})+\Z/', "001111110001110100110011") ) {
	echo "8桁の2進数の連続である\n";
} else {
	echo "8桁の2進数の連続ではない\n";
}

修飾子

正規表現の振る舞いを変更する修飾子を以下に示す

i        大文字と小文字を区別しない
m        複数行として扱う
s        単一行として扱う
x        正規表現のフリーフォーマットを許す
e        preg_replaceの第二引数がPHPのコードとして実行される
A        マッチを開始位置に限定する
D        $が文字列の最後のみにマッチするようになる、m修飾子がある場合この修飾子は無視される
S        正規表現の最適化を行う
U        最大量指定子と最小量指定子を入れ替える
X        Perl非互換なPCREの機能を有効にする、特別な意味が与えられていないバックスラッシュと任意の文字の組み合わせがエラーになる
J        同じ名前の名前付きキャプチャを許す
u        文字列をUTF-8として扱う


i修飾子に関しては説明の通りである

/foo/i   foo, Foo, fOo, foO, FOo, fOO, FoO, FOO にマッチ


m修飾子は ^ が文字列の先頭に加えてLF(0x0A)の直後にもマッチするようになり
$ が文字列の最後に加えてLFの直前にもマッチするようになる


s修飾子は . がLFにもマッチするようになる


x修飾子はパターン中の空白や改行が無視され、#から改行までがコメントになる
ただしコメントにデリミタを含めることはできない

正規表現を使う際に注意することその2

D修飾子が出てきたのでついでに触れておくと
デフォルトで $ は文字列の最後、または最後の改行の直前にマッチする
なので /^[0-9]+$/ は 数字だけの文字列にマッチするが "0123\n" のように改行が含まれていてもマッチする

正規表現によるキャプチャ

グループ化したパターンはマッチに成功したあとで利用することができる(キャプチャまたは捕捉などと言う)
preg_matchの第三引数にはキャプチャを代入する変数を指定することができる

<?php
if ( preg_match('/(\d+)/', "foo 123 bar", $matches) ) {
	echo $matches[1], PHP_EOL; // 123
}


キャプチャするグループは複数あっても構わない
左から順に1から始まる番号がふられ、ネストされた場合は外側が先にくる
また、キャプチャの有無に関係なく添字0にはマッチ全体が代入される

<?php
if ( preg_match('/ ([^:\s]+) \s*? : \s*? ([^\s].*) /x', "title: hello regex", $matches) ) {
	echo $matches[0], PHP_EOL; // title: hello regex
	echo $matches[1], PHP_EOL; // title
	echo $matches[2], PHP_EOL; // hello regex
}

最小量指定子

前述した量指定子は、いわゆる最大量指定子で、なるべく長くマッチしようとする
それに対し最小量指定子はなるべく短くマッチしようとする


例えば次のような文字列に

  

最大量指定子を使った次のパターンでは

/ < (.*) > /x

次のようなキャプチャになるが

foo>  

最小量指定子を使った次のパターンでは

/ < (.*?) > /x

次のようなキャプチャになる

foo


最小量指定子を以下に示す

*?       0個以上の連続にマッチ
+?       1個以上の連続にマッチ
??       0個または1個の連続にマッチ
{n,}?    n個以上の連続にマッチ
{n,m}?   n個以上m個以下の連続にマッチ

後方参照

キャプチャを正規表現のなかで利用するには後方参照を使う
後方参照は \ とその後に対応するキャプチャの番号を並べて表す

/(foo)\1/            foofooにマッチ
/(.)(.)(.)\3\2\1/    左右対称の6文字(abccbaなど)にマッチ

PHPでは99個までキャプチャと後方参照が可能である

正規表現でマルチバイト文字を扱う

正規表現はマルチバイト文字だろうとなんだろうと1バイトを1文字として扱う
シングルバイト文字もマルチバイト文字も同じ1文字として扱うために u修飾子 が使用できる
もし正規表現か対象の文字列にマルチバイト文字が含まれる可能性がある場合は迷わずこの修飾子を使用するべきである
ただしエンコーディングUTF-8でなくてはならない


u修飾子の効果は次の通り

.        1バイトではなく文字単位でマッチするようになる
\w       マルチバイト文字の一部がマッチするようになる
\W       マルチバイト文字の一部がにマッチしないようになる

Unicode文字プロパティが使えるようになる(\p \P \X)詳しくはhttp://www.php.net/manual/ja/regexp.reference.unicode.php
量指定子でマルチバイト文字の連続を表せるようになる

文字コードの指定

16進数を使った書式は次の通り(Fは任意の16進数)

\xF
\xFF
\x{F*}

2桁までは {} で囲っても囲わなくても良い、3桁以上は囲わなければならない


8進数を使った書式は次の通り(7は任意の8進数)

\07
\77
\077
\777

ただし \10 から \99 は対応するキャプチャがないときに限る
ある場合は前述した後方参照になる


ここで指定する数値は u修飾子 の有無で意味が変わることに注意したい
u修飾子がないときは単にバイト列である
u修飾子があるときはUnicodeのコードポイントである
つまりUTF-8の "あ" を表すには
u修飾子がない場合

\x{E3}\x{81}\x{82}

u修飾子がある場合

\x{3042}

となる

1日目のまとめ

正規表現として使う文字列はシングルクォートすること
u修飾子を使うこと
デリミタを忘れないこと


ところで {0,m} や {,m} が最小マッチになるのは私だけだろうか、、、