正規表現とは?
正規表現(regular expression=regexとよく略されます)は文字列のマッチングをするための機能です。数字や記号を組み合わせで様々なパターンを表現をして、文字列をマッチングさせ抽出します。
正規表現には慣れが必要です。最初はわからなくてもめげずに繰り返し読み返してみてください。
正規表現は開発を進めるうえで、かなり強力なツールになるのですが使いこなせる人はそう多くありません。
プロのエンジニアを名乗っていても、このページの内容を全部理解している人はそう多くはいません。1%もいないのではないでしょうか?
この機会に正規表現を理解して開発を効率化して周りをあっと言わせちゃいましょう!
例) 2021年1月1日~31日の日付をマッチングさせる正規表現
まず例を見てみましょう。
/2021年[0]*1月(0*[1-9]|[1-2][0-9]|3[0-1])日/
この正規表現だけで2021年1月1日、2021年01月10日、2021年01月31日などの文字列すべてにマッチさせることができます。
正規表現は現在ではコンピュータのコマンドやプログラミング言語などの命令処理を使う上で欠かすことのない機能になっています。
PHPでの正規表現は多くはpreg_match()/preg_replace()などのpreg_から始まる関数上で扱うことになるでしょう。
正規表現を使って何ができる?
正規表現を使うと例えば以下のようなことができます
- 電話番号などのパラメータの整合性チェック
- 文章の中からメールアドレスだけを抽出する
- テキストの中のURLをhtmlのリンク付(aタグ)に置換する
最後にサンプルソースコードを複数掲載しているので参考にしてみてください。
正規表現の使い方
PHPの正規表現パターンの記述方法
文字列なのでクォートで囲む
PHP上では文字列として正規表現パターンを表現します。
ダブルクォートかシングルクォートで囲みます。なるべくシングルクォートで囲む癖をつけておいたほうがよいです。
$pattern = '/XYZ/'; //XYZという文字列にマッチ
なぜならダブルクォートは$xのような変数や\tや\nなどのエスケープシーケンス色々と解釈されるものが多いからです。
変換やエスケープが文字列としてしているのか?正規表現としてしているのか?混乱しやすいです。
パターンの囲み文字
文字列の中にパターン本体を記述します。
最初と最後に囲み文字を指定する必要があります。
$pattern = '/XYZ/';
/ の部分です。
標準的に/が使われることが多いですが、実はこの文字は実はなんでもよいです。以下の例も全く同じです。
$pattern = '#XYZ#';
$pattern = '|XYZ|';
$pattern = '_XYZ_';
$pattern = '<XYZ>';
$pattern = '@XYZ@';
正規表現のパターンの中にその囲み文字自体を使いたいときは\でエスケープする必要があります。多用すると見づらくなってくるのでその時の都合で囲み文字は変更してください。
パターン記号まとめ表
記号 | 記号の説明 | パターン例 |
. | 任意の1文字にマッチ | 「abc」に/a.c/→マッチ 「abc」に/a..c/→マッチしない |
? | 直前の文字0回または1回の出現にマッチ | 「https://trios.pro」に|http?://|→マッチ 「https://trios.pro」に|https?://|→マッチ |
* | 直前の文字0回以上の出現にマッチ | 「ab」に/abc*/→マッチ /a*/→何にでもマッチ |
+ | 直前の文字1回以上の出現にマッチ | 「ab」に/abc+/→マッチしない 「abc」に/abc+/→マッチ |
{n} | 直前の文字n回の出現にマッチ | 「abbbc」に/ab{3}c/→マッチ |
{n,} | 直前の文字n回以上の出現にマッチ | 「abbbc」に/ab{2,}c/→マッチ |
{n,m} | 直前の文字n回以上m回以下の出現にマッチ | 「abbbc」に/ab{2,3}c/→マッチ 「abbbbc」に/ab{2,3}c/→マッチしない |
^ | 文字列の先頭を表す | 「abc」に/^a/→マッチ 「123abc」に/^a/→マッチしない |
$ | 文字列の末尾を表す | 「abc」に/c$/→マッチ 「abc123」に/c$/→マッチしない |
\ | 直後の正規表現文字をエスケープ(無効化)し本来の文字として扱う | 「先頭の^」に/先頭の\^/→マッチ 「先頭の^」に/先頭の^/→マッチしない |
| | OR | 「abcdef」に/(abc|bcd|cde|def)/→マッチ |
¥t | タブ | |
¥r | 改行。CR(Carriage Return) | |
¥n | 改行。LF(Line Feed) | |
\d | 任意の数字 | 「123」に/\d{3}/→マッチ |
\D | 数字以外の文字 | 「abc」に/\D{3}/→マッチ |
\s | 空白文字。半角スペースかタブか改行。 | 「a b c」に/\s/→マッチ |
\S | 空白文字以外 | 「a b c」に/\S\s\S\s\S/→マッチ |
\w | 任意の英数字とアンダーバー(_) | 「a」に/\w/→マッチ 「0」に/\w/→マッチ 「-」に/\w/→マッチしない |
\W | 英数字とアンダーバー(_)以外の文字 | 「a」に/\W/→マッチしない 「0」に/\W/→マッチしない 「-」に/\W/→マッチ |
\b | 単語境界(文頭、スペース、文末、や記号)。単語の先頭か末尾 | 「apple pen」に/^.+\b.+$/→マッチ 「apple-pen」に/^.+\b.+$/→マッチ |
\B | 単語境界(単語の先頭か末尾)ではない | 「applepen」に/apple\Bpen/→マッチ 「apple-pen」に/apple\Bpen/→マッチしない |
x(?=y) | 先読み言明。xにyに続く時,xにマッチ。yはマッチしない。 | 「apple pen」に/apple (?=pen)/→マッチ 「apple pineapple」に/apple (?=pen|pineapple)/→マッチ |
x(?!y) | 否定先読み言明。xにyが続かない時、xにマッチ。yはマッチしない。 | 「pinecones」に/pine(?!apple)/→マッチ |
文字クラス
[]で囲まれたものを文字クラスといい、その中に書かれたいずれか1文字を表します。
文字クラスの中で使える表現
記号 | 説明 | パターン記述例 | 意味 |
[] | []の中のいずれかの1文字にマッチ | [ABC] | ABCのいずれか1文字 |
– | 範囲を指定。[]の先頭で使った場合は通常文字 | [A-C] | ABCのいずれか1文字 |
^ | []の先頭で使ったら否定 | [^ABC] | ABCのいずれでもない |
範囲指定の使用例
パターン | 意味 |
[ABC] | 文字列ABCのいずれか1文字にマッチ |
[^ABC] | 文字列ABCのいずれでもない1文字にマッチ |
[a-z] | 文字列a~zのいずれか1文字にマッチ |
[A-Z] | 文字列A~Zのいずれか1文字にマッチ |
[0-9] | 文字列0~9のいずれか1文字にマッチ |
[ぁ-んー] | ひらなが1文字にマッチ |
[ァ-ヶー] | カタカナ1文字にマッチ |
[一-龠] | 漢字1文字にマッチ |
エスケープ(無効化)
正規表現では様々な文字を使った表現ができます。
例えば「$」は文字列の終端を表しますが、この$自体を文字としてマッチさせたいときは\を使ってエスケープ(正規表現機能の無効化)します。
パターン | 結果 |
/123$abc/ | 文字列「123$abc」にマッチしない |
/123\$abc/ | 文字列「123$abc」にマッチ |
最短マッチ・最長マッチ
正規表現で繰り返し出現を検索する場合、最長のマッチを適用します。
*と+のあとに?をつけることで最短マッチに変えることができます。
?を使わない最長マッチさせる場合
文字列「abccccccc」に正規表現/abc+/で検索するとマッチする部分は最も長い場合を取るので「abccccccc」となります。
?を使って最短マッチさせる場合
一方で、今度は「abccccccc」に/abc+?/で検索すると、マッチする部分は最短の「abc」となります。
グループ化(グループキャプチャ・サブマッチ)
正規表現にはグループ化という機能があり、パターンをいくつものグループに分けてそれぞれ変数に格納して取り出すことができます。
機能自体の呼び方も様々あり、まったく統一されていないのが辛いところです。グループ化、グループキャプチャ、サブマッチ、サブパターンなどあり過ぎて混乱してしまいますね。
あんまりややこしい用語は使いたくないので、ここでは「正規表現のグループ化」と「グループ変数」と呼ぶことにします。
グループ化の例
まずは実際にPCRE関数(preg_**の関数)でグループ化してみましょう。
今は関数など詳しくは理解せずに、なんとなくグループ化ってこんな感じと思えるくらいで大丈夫です。
例1) preg_matchでグループ化された値を取り出す
$str = '123456789';
$regex = '/(...)(...)(...)/'; //3文字ずつ3グループ化する
preg_match($regex, $str, $matches); //マッチしたグループ配列が$matchesに格納される
print_r($matches);
Array
(
[0] => 123456789 ←マッチした全文字列
[1] => 123 ←1番目の3文字
[2] => 456 ←2番目の3文字
[3] => 789 ←3番目の3文字
)
こんな具合にpreg_match関数ではマッチしてグループ化されたものが配列で格納されます。
例2) 階層構造にグループ化して値を取り出すpreg_match_all
次はpreg_match_allを使ってみます。
先ほどのpreg_matchは最初の1番目のグループでマッチングの検索が終わってしまいますが、preg_match_allは全てのマッチングを最後まで行うという違いがあるので配列の格納方法が違います。
多次元配列で格納されます。
$str = '数字は0123456789です';
$regex = '/(([0-9])([0-9])([0-9]))/'; //3文字ずつをさらに1文字で区切りグループ化する
preg_match_all($regex, $str, $matches); //マッチしたグループ配列が$matchesに格納される
print_r($matches);
Array
(
[0] => Array ←正規表現全体のマッチ
(
[0] => 012
[1] => 345
[2] => 678
)
[1] => Array ←パターンで1番目に出現する()にマッチした部分の配列
(
[0] => 012
[1] => 345
[2] => 678
)
[2] => Array ←パターンで2番目に出現する()にマッチした部分の配列※3分割した中の1文字目
(
[0] => 0
[1] => 3
[2] => 6
)
[3] => Array ←パターンで3番目に出現する()にマッチした部分の配列※3分割した中の2文字目
(
[0] => 1
[1] => 4
[2] => 7
)
[4] => Array ←パターンで4番目に出現する()にマッチした部分の配列※3分割した中の3文字目
(
[0] => 2
[1] => 5
[2] => 8
)
)
例3) preg_replaceでグループ化された値に変換する
preg_replaceによる置換の際にも、マッチングしてグループ化された値を参照して変換することができます。$1, ${1}, \1などのように表現することで取り出せます。
//マッチ下部分をグループ化変数だけに変換
echo preg_replace($regex, '${0}', $str) . PHP_EOL; //TARO YAMADA
echo preg_replace($regex, "$1", $str) . PHP_EOL; //TARO
echo preg_replace($regex, '\\2', $str) . PHP_EOL; //YAMADA
//一応、配列も見てみる
preg_match($regex, $str, $matches); //マッチしたグループ配列が$matchesに格納される
print_r($matches);
私の名前はTARO YAMADAです
私の名前はTAROです
私の名前はYAMADAです
Array
(
[0] => TARO YAMADA
[1] => TARO
[2] => YAMADA
)
パターン修飾子
パターン修飾子はマッチングの仕様を変更するフラグです。
「/ABC/i」の「i」部分がパターン修飾子です。
「/ABC/mi」 のように複数組み合わせることができます。
修飾子 | 説明 | 例 |
なし | 大文字小文字の区別→あり 検索最小単位→文字列全体 「.」の改行へのマッチ→しない | 後述 |
s | シングルラインモード。 検索最小単位→文字列全体 「.」の改行へのマッチ→する | 後述 |
m | マルチラインモード。 検索最小単位→1行ごと 「.」の改行へのマッチ→しない | 後述 |
i | 大文字小文字の区別→しない | 「ABC」に/abc/→マッチしない 「ABC」に/abc/i→マッチ |
u | 「パターン」をUTF-8として扱う。パターン自体にマルチバイト文字を含む場合は忘れずに! |
デフォルトモード、マルチライン、シングルラインモードの違い
正規表現にはマルチラインモードとシングルラインモード、そして標準のデフォルトモードというものがあります。
デフォルトモード(修飾子なし)
まずは何も修飾子を指定しないデフォルトモードの例です。
- 検索最小単位→文字列全体
- 「.」の改行へのマッチ→しない
//デフォルトモード。.が改行を含まないので改行以外が最後まで続いていないマッチなし
$str = "abc\r\ndef";
$regex = '/^.+$/';
preg_match_all($regex, $str, $matches);
print_r($matches);
Array
(
[0] => Array
(
)
)
途中に改行が含まれているので「.」では検出できませんでした。
シングルラインモード(修飾子s)
デフォルトモードは「.」で改行にマッチしてくれませんでしたが、修飾子「s」でこの仕様を変更してマッチできるようにします。
- 検索最小単位→文字列全体
- 「.」の改行へのマッチ→する
//シングルラインモード。.が改行を含むのでマッチする
$str = "abc\r\ndef";
$regex = '/^.+$/s';
preg_match_all($regex, $str, $matches);
print_r($matches);
Array
(
[0] => Array
(
[0] => abc
def
)
)
マルチラインモード(修飾子m)
マルチラインモードは修飾子「m」で、文字列全体を1行ずつ検索していくモードです。
エディタソフトなどで正規表現を使う場合はこのモードで動作することが多いので混乱してしまいがちです。
- 検索最小単位→1行ごと
- 「.」の改行へのマッチ→しない
//マルチラインモード
$str = "abc\r\ndef";
$regex = '/^.+$/m'; //複数行扱いになる
preg_match_all($regex, $str, $matches);
print_r($matches);
Array
(
[0] => Array
(
[0] => abc
[1] => def
)
)
よく使う例
文字列の中からURLだけを抽出する
文章の中からURLを抽出してみます。
$str =<<<EOF
URL①→https://trios.pro/0000/1111/2222/
URL②→https://trios.pro/search?a=b#xyz
URL③→https://user:password@trios.pro:8080/search?a=b#xyz
EOF;
$regex = '|https?://[-_.!~*\'()a-zA-Z0-9;/?:@&=+$,%#]+|'; //URLのパターンマッチ
preg_match_all($regex, $str, $matches);
print_r($matches);
Array
(
[0] => Array
(
[0] => https://trios.pro/0000/1111/2222/
[1] => https://trios.pro/search?a=b#xyz
[2] => https://user:password@trios.pro:8080/search?a=b#xyz
)
)
文字列の中から電話番号だけを抽出する
様々な電話番号表記を抜き出してみます。
$str =<<<EOF
TEL①→03-4773-2434(固定電話)
TEL②→+813-1234-5678(国際表記)
TEL③→090-1234-5678(携帯電話)
EOF;
$regex = '|\+*\d{2,5}-?\d{1,4}-?\d{4}|'; //電話番号のパターンマッチ
preg_match_all($regex, $str, $matches);
print_r($matches);
Array
(
[0] => Array
(
[0] => 03-4773-2434
[1] => +813-1234-5678
[2] => 090-1234-5678
)
)
文字列の中からメールアドレスだけを抽出する
文章の中からメールアドレスを抽出してみます。
$str =<<<EOF
メール①→abc@trios.pro(山田さん)
メール②→TEST_1@gmail.com(佐藤さん)
メール③→test+1@yahoo.co.jp(田中さん)
メール③→php-daisuki.234@docomo.jp(鈴木さん)
EOF;
$regex = '/([a-z0-9+_\-.]+)@([a-z0-9\-.]+\.[a-z]{2,6})/i'; //URLのパターンマッチ
preg_match_all($regex, $str, $matches);
print_r($matches);
Array
(
[0] => Array
(
[0] => abc@trios.pro
[1] => TEST_1@gmail.com
[2] => test+1@yahoo.co.jp
[3] => php-daisuki.234@docomo.jp
)
[1] => Array
(
[0] => abc
[1] => TEST_1
[2] => test+1
[3] => php-daisuki.234
)
[2] => Array
(
[0] => trios.pro
[1] => gmail.com
[2] => yahoo.co.jp
[3] => docomo.jp
)
)
もし、メールアドレスの表記が正しいかチェックしたいときはいったん抜き出してから詳細のチェックを行いましょう。
メールアドレスはプロバイダがルール通りに発行しているとは限らないので様々なパターンを想定する必要があるためとても煩雑です。
正規表現だけでやろうとするとかなり複雑な表記になり、他人が読みにくくメンテナンス性の低いコードになりがちです。
PHPの正規表現が使える関数一覧(PCRE関数)
preg_***の「preg」の名前の由来
preg_からはじまる関数はPCRE関数といい、PCREというのは正規表現の仕様の種類の一つです。PCREは「Perl Compatible Regular Expressions」の略で、perl互換の正規表現という意味です。
すなわち、preg_***関数のpregとは「PCRE + Regular + Expression」の略です。
(PHPのPだと思ったらPerlのPだったんですね・・・)
preg_match/preg_match_all
正規表現パターンによる検索全般。マッチするか確認したり、マッチした数や文字列を取得する。
preg_split
文字列を正規表現によって分割して配列に変換する。explodeのイメージ。
preg_replace
正規表現パターンにマッチした文字列を指定文字列に変換する。マッチしないと元の文字列が返る。空を返したいならpreg_filter
preg_replace_callback
preg_replaceにコールバックを渡してより柔軟な変換ができる。
preg_filter
正規表現パターンにマッチした文字列を指定文字列に変換する。マッチしないと空の文字列が返る。元の文字列を返したいならpreg_replace
preg_last_error
直近の正規表現エラーを返す
preg_quote
ある文字列を正規表現のエスケープ処理する。正規表現パターン作成時に「この文字列はこのまま扱いたいので正規表現として機能してほしくない」という場合に使う。
コメント