PHP の正規表現
更新日:2022年03月11日
作成日:2015年11月12日
正規表現とは
ある文字列に対して、「XXで始まって○○で終わる」や「○○または△△またはXXを含む」、「□の後に○△が続く」などの複雑な検索や置換を行うパターンをチェックする方法です。
例えば、日本の郵便番号にマッチするかどうかを調べるコードは、以下のようになります。
<?php $str = "123-4567"; if(preg_match('/^[0-9]{3}-[0-9]{4}$/', $str)) { echo "マッチします。"; }else{ echo "マッチしません。"; } ?>
正規表現はマッチさせる文字のパターンを表現します。
上記の例では「'/^[0-9]{3}-[0-9]{4}$/'」がパターン(正規表現)になります。
正規表現を利用することで、E-mail アドレスや URL、 HTML のタグなど、より複雑な文字列のパターンを抽出したり置換したりすることができます。
PHP には以下の 3種類の正規表現があります。
正規表現によるパターンマッチを行う関数として、preg_match() と mb_ereg_match() 、mb_ereg() があります。(ereg() もありますが、PHP 5.3.0 で 非推奨となっていて、使用しないことを強く推奨されています)。
日本語を扱う場合、文字エンコードが「UTF-8」であれば、どちらでも使えますが、「UTF-8」以外の場合は mb_ereg_match()、 mb_ereg() を使う必要があります。
以下は、preg_match() の基本的な構文です。(詳細は後述)
int preg_match ( $pattern , $subject )
- 正規表現によるマッチングを行います。
- $pattern(string) : 検索するパターンを表す文字列(正規表現パターン)
- $subject(string) : 検索対象の文字列
- 戻り値:pattern が指定した subject にマッチした場合に 1 を返し, マッチしなかった場合は 0、エラーが発生した場合は FALSE を返す。
パターン
PHP には正規表現リテラルがないので、正規表現パターンは文字列として記述します。
- 文字列なので「'」(シングルクォート)で囲みます。ダブルクォート「"」を使うと変数が展開されたり等して意図したように動作しない可能性があります。
- 正規表現パターンは通常、スラッシュ「/」(デリミタ)で囲みます。
'/正規表現パターン/'
デリミタ
デリミタは、英数字、バックスラッシュ、空白文字以外の任意の文字をデリミタとして使うことができます。
デリミタとしてよく使われる文字は、スラッシュ (/)、 ハッシュ記号 (#) およびチルダ (~) です。 次に示す例は、どれも正しいデリミタです。
- /foo bar/
- #^[^0-9]$#
- +php+
- %[a-zA-Z0-9_-]%
前述の preg_match() の第1パラメータの郵便番号を表す正規表現パターンは以下のようになっています。
'/^[0-9]{3}-[0-9]{4}$/'
- 先頭のデリミタに続く「^」は、文字列の最初にマッチすることを意味します。
- [0-9] は、半角数字の0から9までのいずれかにマッチするという意味です。
- 続く{3}は、それが3回続くという意味です。
- 続く「-」は、半角のハイフンそのものを意味します。
- [0-9]{4} は、0から9の半角数字が4回続くという意味です。
- 最後の「$」は文字列の最後にマッチすることを意味します。
つまり、この正規表現パターンは、「0から9の半角数字3文字で始まり、次にハイフンがあり、0から9の半角数字4文字で終わる文字列」にマッチすることになります。
メタ文字/メタキャラクタ
^ や [ ]、{ }、$ などは本来の文字とは異なる特殊な意味を持っています。このような特殊な意味を持つ文字を「メタ文字」と呼び、以下のようなものがあります。
メタ文字には「文字の集まりを表すもの」、「文字の繰り返しを表すもの」、「特定の文字の種類を表すもの」や「パターンにマッチした部分文字列を変数に格納する」という機能を表すもの等いろいろなものがあります。
また、同じメタ文字であっても、使用する位置や他の文字との関係で複数の意味を使い分けるものもあり、状況に応じて意味を解釈する必要があります。
バックスラッシュと円マーク
日本語環境でバックスラッシュ「\」を入力しても、ブラウザで表示されると(半角)円マーク「\」として表示されてしまう可能性が高いです。(これは、これらの文字の表示がフォントに依存しているためです)
このページでも本文中はほとんどの場合、バックスラッシュが(半角)円マークとして表示されていて、コードのサンプル中ではバックスラッシュとして表示されています。予めご了承ください。
メタ文字
メタ文字は、 その文字自体を表わさず、代わって特別な解釈が行われます。
メタ文字には、次の2種類があります。
- 角カッコ内を除き、 パターン中のどこででも使用できる文字
- 角カッコで括られた中(文字クラス)でだけ使用できる文字
角カッコ外で使用できるメタ文字には、次のものがあります。
メタ文字 | 説明 |
---|---|
\ | 多目的に使う一般的なエスケープ文字 |
^ | 検索対象(複数行モードでは行)の始まりを言明 |
$ | 検索対象の終わりあるいは終端の改行文字の前(複数行モードでは行の終わり)を言明 |
. | 改行を除くすべての文字にマッチ(デフォルト時) |
[ | 文字クラス定義の開始 |
] | 文字クラス定義の終了 |
| | 選択枝の開始 |
( | サブパターンの開始 |
) | サブパターンの終了 |
? | 0 または 1 回マッチ |
* | 0 回以上の繰り返し |
+ | 1 回以上の繰り返し |
{ | 最小/最大を指定する量指定子の開始 |
} | 最小/最大を指定する量指定子の終了 |
パターン中の角カッコで括まれた部分を「文字クラス」と言います。文字クラスで使えるメタ文字は、次のものだけです。
メタ文字 | 説明 |
---|---|
\ | 一般的なエスケープ文字 |
^ | クラスの否定。ただし、文字クラスの最初の文字に用いた場合のみ |
- | 文字の範囲の指定 |
エスケープシーケンス
特殊な文字を入力するために「バックスラッシュ+1文字」(例:\* や \n)などとバックスラッシュを使って記述することができます(バックスラッシュは言語環境によっては、円マークとして表示されます)。このような特殊な記述の仕方をエスケープシーケンスと言います。
バックスラッシュには、 いくつかの使用法があります。
続く文字の特別な意味を取り去る
1つ目の使用法は、続く文字が表す特別な意味を取り去ります。 このエスケープ文字としての使用法は、 文字クラスの内外部いずれでも可能です。
前述のメタ文字は特別な意味を持つので、これらを文字そのものとして使う場合は、その文字の前にバックスラッシュを置きます。
例えば正規表現の中で、* を文字そのものとして表すには、\* と記述します。
シングルクォートあるいはダブルクォートで囲まれた PHP の 文字列 の中では、バックスラッシュは特別な意味を表します。 そのため、正規表現を使用して \\ とマッチさせたい場合は PHP のコード内では \\\\ と記述する必要があります。
以下は preg_match() を使って、バックスラッシュのマッチを確認する例です。ここでは、値を二重否定を行うことで真偽値(Boolean)として取得して、var_dump() を使って結果を表示しています。
^ は、文字列の先頭に、 $ は文字列の最後にマッチするアンカーです。
<?php var_dump(!!preg_match('/^\\$/', '\\')); // bool(false) echo "<br>"; var_dump(!!preg_match('/^\\\\$/', '\\')); // bool(true) ?>
bool(true)
非表示文字
バックスラッシュの 2 番目の使用法は、非表示文字(制御コードなど)を パターン中に目に見える形で記述するための方法です。
例えば、改行文字は \n で表します。その他に、以下の表のようなエスケープシーケンスの形式で使用する文字があります。
文字 | 意味 |
---|---|
\t | タブ |
\n | 改行 (newline) |
\f | 改ページ (formfeed) |
\r | 復帰 (carriage return) |
\xhh | 16 進コードで hh の文字 |
\ddd | 8 進コードで ddd の文字、もしくは、後方参照 |
\cx | 制御文字 "control-x", ここで x は任意の文字 |
文字型の指定
バックスラッシュの第 3 の使用法は、包括的な文字型を指定する用途です。
文字 | 意味 |
---|---|
\w | 任意の単語。単語構成文字 (word character) 。 [a-zA-Z0-9]と同じ |
\W | 単語文字以外の文字。非単語構成文字 (non-word character) 。[^a-zA-Z0-9]と同じ |
\s | 任意の空白文字。 |
\S | 空白文字以外の文字。 |
\d | 任意の数字(10 進数字 )。[0-9]と同じ |
\D | 数字以外の文字(10 進数字でない文字)。[^0-9]と同じ |
文字クラス
文字を角括弧 [ ] で囲むことで、「文字クラス」というものにまとめることができます。文字クラスは、その中に含まれる任意の1文字に一致します。
角括弧 [ ] 内の先頭にキャレット(^)を指定することで、「否定文字クラス」も定義することができます。否定文字クラスは、角括弧 [ ] 内に含まれている文字以外の文字に一致します。
/[abc]/ はa,b,cのいずれか一文字に一致します。
/[^abc]/ はa,b,cのいずれでもない文字に一致します。
/gr[ea]y/ は grey, gray のいずれかに一致します。
上記は、まず g を探し、次に r を探して、その次に e または a を探して、その後に y を探すという意味です。
また、ハイフン(-)を使って文字の範囲を指定することができます。
/[a-z]/ はLatin文字の任意の小文字に一致します。
/[a-zA-Z0-9]/ は英数字に一致します。
/[a-z0-9A-Z]/ としても同じで、範囲を指定する順番は問いません。
/<h[123456]>/ は <h1>,<h2>,<h3>,<h4>,<h5>,<h6> にマッチします。
/<h[1-6]>/ は /<h[123456]>/ と同じ意味になります。
ハイフン(-)が文字クラス内に必要な場合は、バックスラッシュでエスケープします。もしくは、文字クラスの最初または最後など、 範囲を示すと解釈されない場所に記述します。
正規表現にはよく使われる文字クラスがあり、これらを特定の文字とエスケープシーケンスを組み合わせたもので指定することができます。 例えば、\sは空白文字やタブ文字などのUnicodeの空白文字を意味する文字クラスです。
角括弧の中でもこれらを使用することができます。例えば、/[\s\d]/ は空白文字1文字か数字1文字に一致します。
また、Perl は、POSIX 記法の文字クラスもサポートしています。これは、 [: と :] で囲んだ名前を角カッコ内で使うものです。 PCRE でもこの記法に対応しています。たとえば [01[:alpha:]%] は "0"、"1"、任意のアルファベット、あるいは "%" にマッチします。
クラス | 意味 |
---|---|
[:alnum:] | 半角英数字 [0-9a-zA-Z]→ [:alpha:] + [:digit:] |
[:alpha:] | 小文字大文字の半角英字 [a-zA-Z]→ [:lower:] + [:upper:] |
[:ascii:] | 7ビットのASCII文字 [\x01-\x7F] |
[:blank:] | スペースとタブ [ \t] |
[:cntrl:] | 制御文字 [\x01-\x20] |
[:digit:] | 数字 [0-9] |
[:graph:] | 可視文字(空白や制御文字以外の文字)[^\x01-\x20]→ [:alnum:] + [:punct:] |
[:lower:] | 小文字の半角英字 [a-z] |
[:print:] | 印字可能文字[\t\x20-\xFF]→ [:alnum:] + [:punct:] + スペース |
[:punct:] | 記号・句読点 [ -!”#$%&'()*+,./:;<=>?@[\\\]^_{|}~] |
[:space:] | 改行、キャリッジリターン、タブ、スペース、垂直タブ [\n\r\t \x0B] |
[:upper:] | 大文字の半角英文字 [A-Z] |
[:xdigit:] | 16進数 |
\d | 任意の数字。[0-9] |
\D | 数字以外の文字。[^0-9] |
\s | 空白文字 [\n\r\ \t]と同義 |
\S | 空白文字以外 [^\n\r\ \t] |
\w | 任意の単語。(word character)。 [a-zA-Z0-9] |
\W | 単語文字以外の文字。[^a-zA-Z0-9] |
ハイフン(-)は、文字クラスの中の先頭以外においてのみメタ文字となります。それ以外(文字クラスの外)の場所では、通常のハイフンの文字(-)とマッチします。
文字クラス内にある疑問符(?)やピリオド(.)、パイプ(|)等は通常はメタ文字ですが、これらが文字クラス内に含まれる場合は、メタ文字と見なされません。? や . または | のメタ文字としての機能が、文字クラスの中では意味を成さないからです。
キャレット(^)は文字クラス外では、行頭指定文字(行の先頭を表す文字)になりますが、文字クラス内の角括弧の直後にある場合は、否定を意味するメタ文字になります(それ以外の位置ではクラス内でも特殊な働きはしません)。
どのメタ文字がサポートされているかという規則は、文字クラスの内部と外部では異なることに注意が必要です。
また、文字クラスの外では、正規表現中の各リテラル文字は「かつ(and)」が間にあるものとして解釈されますが、文字クラス内での各リテラル文字は「または(or)」が間にあるものとして解釈されます。
繰り返し/量指定子
正規表現で2桁の数字は /\d\d/ と表現できますが、もっと桁数が増えたり、任意の桁数を指定する場合などで使える繰り返しを意味するメタ文字があります。
繰り返しは良く使われるので、以下のような繰り返しを指定するための文字が用意されています。
文字 | 意味 |
---|---|
? | 直前の項目をゼロ回または、1回だけ繰り返します。ゼロ回というのは、直前の項目がなくても良いという意味で{0,1}と同じ意味です。 |
+ | 直前の項目を1回以上繰り返します。{1,}と同じ意味です。 |
* | 直前の項目をゼロ回以上繰り返します。{0,}と同じ意味です。 |
{n,m} | 直前の項目を n 回から m 回繰り返します |
{n,} | 直前の項目を n 回以上繰り返します |
{n} | 直前の項目を n 回だけ繰り返します |
/colou?r/ は、color または colour にマッチします。
/July?/ は、July または Jul にマッチします。
/4(th)?/ は 4 または 4th にマッチします。
/\s+java\s+/ は javaの前後に1つ以上の空白がある文字列にマッチします。
/[^"]*/ は0個以上の引用符のない文字列にマッチします。
/\w{4}\d?/ は4個の単語文字の直後に0個または1個の数字が続く文字列にマッチします。
/\d{3,5}/ は3桁から5桁の数字にマッチします。
最短マッチ
基本的に正規表現は貪欲(greedy)でマッチしうる最大(最長)の文字列にマッチします。
* や + を使う場合に、最短でマッチさせるには、* や + の後に ? を付けます。
以下の例では、/<div class='inside'>.*<\/div>/ でマッチさせると、2つ目の div 要素の閉じタグまで含まれてしまいますが、/<div class='inside'>.*?<\/div>/ とすることで、最短のマッチを取得することができます。
<?php $foo = '<div class="outside"><div class="inside">some text or html</div></div>'; echo "Greedy : " .htmlspecialchars( preg_replace('/<div class="inside">.*<\/div>/', '', $foo))."<br>"; echo "Non-greedy : " .htmlspecialchars( preg_replace('/<div class="inside">.*?<\/div>/', '',$foo))."<br>"; ?>
Non-greedy : <div class="outside"></div>
選択
メタ文字の「|」は、「または」という意味を持ちます。これを使うことにより、構成要素の個々の表現のいずれかにマッチさせることができます。
例えば、Bob と Robert は異なる2つの表現ですが、 /Bob|Robert/ とするとそのどちらかにマッチする1つの表現になります。このように組み合わされた部分パターンを「選択(alternative)」と言います。
文字クラスを使った /gr[ea]y/ は、選択を使って /grey|gray/ や選択肢をくくる丸括弧を使って /gr(e|a)y/ と書くことができます。
但し、/gr[e|a]y/ とすると文字クラス内では、| は通常の文字と解釈されてしまうので、意味が異なってしまいます。
また、選択肢の数に制限はありません。空の選択肢も可能です (空の文字列にマッチします)。
/gr(e|a)y/ という表現では、丸括弧が必要です。丸括弧を使わずに /gra|ey/ とすると、gra または ey という意味になってしまいます。
丸括弧を使うことで、選択の対象範囲を制限(グループ化)することができます。
また、選択に関しては左から右に比較が行われるので、左側の代替表現が一致した場合は、 右側の代替表現は無視されます。このため、もし /a|ab/ というパターンで ab という文字列に比較を行うと、最初の文字の a だけに一致してしまいます。
丸括弧(サブパターン・参照)
サブパターン
パターンの一部を括弧で囲み、グループ化することによって、その箇所をひとつの単位として扱うことができます。
丸括弧 ( ) にはいくつかの意味がありますが、複数の項目を1つの部分表現(サブパターン)にまとめる(グループ化する)という意味があります。 まとめられた部分表現は、*、+、? などでも1つとして扱われます。
/(AB|CD)+|EF/ は「AB と CD のどちらか一方の1回以上の繰り返し」または、「EF」のどちらかという意味になります。
また、丸括弧でサブパターンを囲むことによって、その部分にマッチしたテキストを記憶することができます。
例えば、 1個以上の小文字とその後ろに1個以上の数字がくるパターンは、/[a-z]+\d+/ と記述できますが 最後の数字だけを後で使用したい場合、括弧で囲んで /[a-z]+(\d+)/ と書けば、一致したパターンから 数字だけを取り出すことができます。
(後方)参照
さらに、括弧には正規表現内の部分表現(サブパターン)を後で参照するという意味もあります。 これは、\ の後ろに数字を指定して行います。この数字は、正規表現内で括弧に囲まれた部分表現の位置を示すものです。例えば \1 は1番目の部分表現を指します。 なお、部分表現は入れ子にできるので、左側の括弧の位置で順番を数えます。
/([Java([Ss]cript)?)\sis\s(fun)/ では ([Ss]cript) は\2で参照できます。
部分表現を参照するというのは、部分表現に対応するパターンを参照するのではなく、そのパターンにマッチするテキストを参照するという意味になります。これにより、文字列の異なる部分で、同じ文字列が含まれる条件を指定することができます。
単一引用符または二重引用符に囲まれた任意の文字は以下のように記述できます。但し、この場合は開始と最後の引用符が違っていてもかまわないことになります。
/['"][^'"]*['"]/ (JavaScipt の例)
実際には、PHP ではパターンをシングルクォートで囲むので、エスケープする必要があり以下のようになります。
'/[\'\"][^\'\"]*[\'\"]/' (PHP の場合)
しかし、次のように記述すると、引用符も一致していなくてはならなくなります。\1は最初の括弧で囲まれた部分表現です。
/(['"])[^'"]*\1/ (JavaScipt の例)
'/([\'\"])[^\'\"]*\1/' (PHP の場合)
但し、文字クラス中から部分表現を参照することは文法上許されないので次のように記述することはできません。
/(['"])[^\1]*\1/
(?: ) を使ってグループ化すると、この丸括弧はその部分にマッチしたテキストを記憶しません(参照を生成させません)。グループ化のみで、一致する文字を記憶させないことができます。
/([Java(?:[Ss]cript)?)\sis\s(fun)/ では (fun) は\2で参照できます。
文字 | 意味 |
---|---|
| | 選択。この記号の左右どちらかに一致します。左から右に比較が行われるので、左側の代替表現が一致した場合は、 右側の代替表現は無視されます。 |
( ) | グループ化。複数の項目をまとめ、* , + , ? , | 等の対象とします。またこのグループに一致する文字を記憶しておき、後で参照できるようにします。 |
(?: ) | グループ化のみを行います。このグループに一致する文字は記憶しません(参照の対象になりません)。 |
\n | グループ番号 n で指定された部分表現に一致した文字列にマッチします。部分表現は丸括弧で囲まれたものになります。グループ番号は、左側の括弧が何番目かで判断します。但し、(?: ) でグループ化されたものは、数えません(対象になりません)。 |
位置の指定
正規表現の中には、個々の文字ではなく、文字間の位置にマッチするものがあり、アンカーと呼ばれます。
\b は単語境界(単語文字 \w と単語以外の文字 \W との境界)または、文字列の先頭や末尾と単語文字の境界を指します。言い換えると単語の区切りにマッチします。この時、マッチした単語の区切り(空白等)は、マッチした部分に含まれません。
但し、文字クラス中では \b はバックスペース文字に一致します。
よく使われるアンカーには、文字列の先頭にマッチする ^ や、文字列の最後にマッチする $ があります。
例えば、JavaScript という単語だけの行を探すには /^JavaScipt$/ とします。
Java という単語だけを探すには、 /\bJava\b/ とします。
\B は単語境界以外の位置にマッチするので、/\B[Ss]cript/ とすると JavaScript や shellscript にはマッチしますが、Script や scripting にはマッチしません。
また、\A, \Z, \z は、 ^ や $ とは 異なり、オプション設定によらず、文字列の始端または終端だけにマッチします。
\Z と \z との違いは、 \Z は文字列の末尾の改行の前の位置および文字列の終端にマッチするのに対し、\z は文字列の終端にのみマッチすることです。
また、任意の正規表現を位置指定に利用することもできます。
(?= と ) で囲むと先読み言明(lookahead assertion)になり、以降の文字が (?= と ) で囲まれた正規表現と一致する必要があります。但し、 (?= と ) で囲まれた部分は、正規表現にマッチした文字列には含まれません。
例えば、JavaScript の後にコロン(:)が続く場合のみ、JavaScript に一致させるには、/JavaScript(?=\:)/ とします。このパターンは JavaScript:Handbook の JavaScript には一致しますが、 JavaScript Handbook の JavaScript には一致しません(コロンがないから)。
(?= の代わりに (?! を使うと、否定先読み言明(negative lookahead assertion)となり、以降の文字はマッチしてはならないことを意味します。
例えば、/Java(?!Script)([A-Z]\w*)/ は Java の後に大文字が1文字続き、その後に任意の文字が続く文字列にマッチします。但し、Java の直後に Script という文字列が続く場合はマッチしません。 JavaUser にはマッチしますが、 JavaScript, JavaScriptor, Javacoffee には一致しません。
文字 | 意味 |
---|---|
^ | 文字列の先頭。マルチラインモードでは、行の先頭。 |
$ | 文字列の末尾。マルチラインモードでは、行の末尾。 |
\A | 文字列の先頭。マルチラインモードの影響は受けない。 |
\z | 文字列の末尾。マルチラインモードの影響は受けない。 |
\Z | 文字列の末尾と文字列の末尾の改行の前の位置。マルチラインモードの影響は受けない。 |
\b | 単語境界。\w文字と\W文字との間の位置、または、 \w文字と文字列の先頭または末尾の間の位置。 文字クラスの中で、[\b]とするとバックスペースを意味する。 |
\B | 単語境界ではない位置。 |
(?=p) | 先読み言明。後に続く文字がパターン p に一致することが必要条件。但し、パターン p に一致した部分の文字列は、 比較結果に含まれない。 |
(?!p) | 否定先読み言明。後に続く文字がパターン p に一致しないことが必要条件 |
バリデーションでは ^ と $ ではなく \A と \z を使う
正規表現によるバリデーション等で、完全一致を示す目的で ^ と $ を用いる方法が一般的ですが、(これは間違いであり)正しくは \A と \z を用いる必要があります。
PHPやPerlの正規表現のデフォルトは単一行モードであり、文字列の途中の改行の前後で ^ や $ がマッチすることはありませんが、行の末尾に改行がある場合にも $ は(改行の直前に)マッチしてしまいます。
\A と \z はマルチラインモードの影響を受けません。(参考:エスケープシーケンス)
以下のPHPスクリプトは 1 (マッチした)を返します。
<?php echo preg_match('/^[0-9]+$/', "123\n"); echo "<br>"; echo preg_match('/^[0-9]+$/m', "\n123"); //マルチラインモード ?>
1
一方、以下は 0 (マッチしない)を返します。
<?php echo preg_match('/\A[0-9]+\z/', "1234\n"); echo "<br>"; echo preg_match('/\A[0-9]+\z/m', "\n1234"); //マルチラインモード echo "<br>"; echo preg_match('/\A[0-9]+\z/m', "1234\n"); //マルチラインモード ?>
0
0
このように、^ と $で完全一致のチェックをしているつもりでも、データ末尾に改行が含まれている場合を見逃してしまうという問題があるので注意が必要です。
参考にさせていただいたサイト:
正規表現によるバリデーションでは ^ と $ ではなく \A と \z を使おう
PHP マニュアル「エスケープシーケンス」
パターン修飾子
Perl互換の正規表現では、パターン修飾子というマッチングの際のオプションを指定することができます。(JavaScript ではフラグと呼んでいます)
フラグを指定する位置は2番目のスラッシュの後に指定します。フラグは組み合わせて指定することが可能です。
preg_match("/abc.*/i",$str)
- i というフラグを指定すると、大文字と小文字を区別しません。(i は ignoreCase から)
- m というフラグを指定すると、マルチラインモードで検索が行われます。
- s というフラグを指定すると、パターン中のドットメタ文字(.)は 改行を含む全ての文字にマッチします。 これを設定しない場合は、改行にはマッチしません。
- D というフラグを指定すると、パターン内のドルメタ文字($)は、検索対象文字列の終わりにのみマッチします。この修飾子を設定しない場合、ドル記号は、 検索対象文字列の最後の文字が改行文字であれば、その直前にもマッチします。 この修飾子は、m を設定している場合に無視されます。
- u というフラグを指定すると、UTF-8として処理されます。日本語(漢字)を使う場合に指定するとうまくいく場合があります。
- U(大文字) というフラグを指定すると、量指定子の「貪欲さ」が反転します。 つまり、このフラグを指定すると、量指定子は、貪欲でなくなり、 疑問符を後ろに付けてはじめて貪欲になるようになります。(「最短マッチ」参照)
- JavaScript では一致する文字列を全て検索したい場合は、global を意味する g というフラグを指定しますが、PHP(preg系)では「g」は使いません。代わりに検索については「preg_match_all()」を使います。 置換については「preg_replace」を使い、「g」を指定しなくても複数回の検索をすることになっています。
マルチラインモードが指定され、検索対象の文字列に改行が含まれている場合、^ と $ のアンカー文字は、検索文字列の任意の行の先頭と最後を指します。
文字 | 意味 |
---|---|
i | 大文字と小文字を区別しない。(ignoreCase の i) |
m | マルチラインモードにする。^は文字列の先頭と行の先頭に、$は文字列の末尾と行の末尾に 一致するようになります。 |
D | パターン内のドル記号($)は、検索対象文字列の終わりにのみマッチします。(文末に改行を含んでいるとマッチしません) |
s | ドット(.)を改行文字にもマッチさせる。 |
u | パターン文字列を UTF-8 として扱う。 |
U | 量指定子の「貪欲さ」を反転させます。 |
パターンマッチ
正規表現によるパターンマッチを行う関数として、preg_match() と mb_ereg() 、mb_ereg_match() があり、日本語を扱う場合、文字エンコードが「UTF-8」であれば、どちらでも使えますが、「UTF-8」以外の場合は mb_ereg()、mb_ereg_match() を使います。
preg_match()
以下は preg_match() の構文です。
int preg_match ( $pattern, $subject [, &$matches [, $flags [, $offset ]]] )
preg_match() は正規表現によるマッチングを行う関数で、pattern で指定した正規表現により subject を検索します。
パラメータ
- pattern(string):検索するパターンを表す文字列。(正規表現パターン)
- subject(string):検索対象の文字列。
- matches(array):結果の配列。matches を指定した場合、検索結果が代入されます。 $matches[0] にはパターン全体にマッチしたテキストが代入され、 $matches[1] には 1 番目のキャプチャ用サブパターンにマッチした 文字列が代入され、といったようになります。
- flags(int):flags には以下のフラグを指定できます。
PREG_OFFSET_CAPTURE:このフラグを設定した場合、各マッチに対応する文字列のオフセットも返されます。 これにより matches の値は配列となり、 配列の要素 0 はマッチした文字列、 要素 1は対象文字列中におけるマッチした文字列のオフセット値 となることに注意してください。 - offset(int):検索の開始位置。通常、検索は対象文字列の先頭から開始されます。 オプションのパラメータ offset を使用して 検索の開始位置を (バイト単位で) 指定することも可能です。
戻り値
pattern が指定した subject にマッチした場合に 1 を返します。 マッチしなかった場合は 0、エラーが発生した場合は FALSE を返します。
注意
ある文字列が他の文字列内に含まれているかどうかを調べるためだけに preg_match() を使うのは避けた方が良いでしょう。 strpos() 関数を使うほうが速くなります。
以下はサンプルです。
文字列 "php" を探す例です。パターン '/php/i' のデリミタの後の「i」は大小文字を区別しない検索を示す「パターン修飾子」です。
<?php if (preg_match('/php/i', "PHP is the web scripting language.")) { echo "マッチしました。"; } else { echo "マッチしませんでした。"; } ?>
単語 "web" を探す例です。
パターン内の \b は単語の境界を示すため、独立した単語の "web" にのみマッチし、"website" のような単語の一部にはマッチしません。(「位置の指定」参照)
<?php if (preg_match("/\bweb\b/i", "PHP is the web scripting language.")) { echo "マッチしました。"; } else { echo "マッチしませんでした。"; } echo "<br>"; if (preg_match("/\bweb\b/i", "PHP is the website scripting.")) { echo "マッチしました。"; } else { echo "マッチしませんでした。"; } ?>
マッチしませんでした。
URL からドメイン名を得る例です。
<?php // URL からホスト名の取得 preg_match('@^(?:http://)?([^/]+)@i', "http://www.php.net/index.html", $matches); $host = $matches[1]; echo "ホスト名: ". $host. "<br>"; // ホスト名のドットで区切られた最後の2つ(ドメイン名)を取得 preg_match('/[^.]+\.[^.]+$/', $host, $matches); echo "ドメイン名: {$matches[0]}\n"; ?>
ドメイン名: php.net
少し詳しく見てみます。まずは最初の正規表現パターン。
'@^(?:http://)?([^/]+)@i'
- URL やパスを扱う場合は、スラッシュが出てくるので、デリミタをスラッシュ以外(この例では @)にしています。
- 先頭のキャレット(^)は「文字列の先頭にマッチする」を意味します。(「位置の指定」を参照)
- 括弧を使って、後から参照できるようにしています。また、最初の括弧は(?:)として、マッチしたテキストを記憶しないようにしています。(「後方参照」を参照)
- 中ほどの「?」は、「直前の項目をゼロ回または、1回だけ繰り返す」と言う意味です。(繰り返し/量指定子」を参照)
- [^/] は文字クラスで、スラッシュ以外の1文字を意味し、その後の「+」は直前の項目を1回以上繰り返すと言う意味です。(繰り返し/量指定子」を参照)
- 「i」は大小文字を区別しない検索を示す「パターン修飾子」です。
オプションの第3パラメータ($matches)を指定しているので、 $matches[1] には 1 番目のキャプチャ用サブパターンにマッチした 文字列が代入されます。結果として、$host には ([^/]+)のサブパターンに該当する「www.php.net」が代入されます。(最初の括弧は(?:)として、マッチしたテキストを記憶しないようにしています)
$matches[0] には「http://www.php.net」が入っています。
2番目の preg_match() を見てみます。
preg_match('/[^.]+\.[^.]+$/', $host, $matches);
- 検索対象の文字列($host)には「www.php.net」が入っています。
- パターン '/[^.]+\.[^.]+$/' は、[^.]がドット(.)以外の文字を表します。文字クラスの中ではドットは特別な意味を持ちません。(文字クラスの記述を参照)
- その後の「+」は直前の項目を1回以上繰り返すと言う意味です。(繰り返し/量指定子」を参照)
- \. はバックスラッシュ(環境によっては円マーク)でドットをエスケープしているので、ドット文字を意味します。
- 最後の $ はアンカーで文字列の最後にマッチします。(「位置の指定」を参照)
パターン '/[^.]+\.[^.]+$/' は、「ドット(.)以外の文字が1回以上繰り返され、その後にドットが続き、更にドット以外の文字が1回以上繰り返されて終了する文字列」というような意味になります。
また、最後の echo では、変数と文字列の間に空白がないと、変数の展開に失敗する可能性があるので、{$matches[0]} と中括弧{ }で囲んでいます。(「文字列の出力」を参照)
preg 系で日本語を使う場合
正規表現の中で日本語(マルチバイト文字)を使ってマッチさせるには、パターン修飾子「u」を指定した方が安全です。
<?php $inputstr = '近所には白猫や黒猫の野良猫がいます。'; preg_match('/([^白黒]{2}猫)/', $inputstr, $data1); preg_match('/([^白黒]{2}猫)/u', $inputstr, $data2); echo $data1[0] ."<br>"; echo $data2[0] ."<br>"; ?>
野良猫
preg_match_all()
JavaScript では一致する文字列を全て検索したい場合は、global を意味する g というフラグ(パターン修飾子)を指定しますが、PHP(preg系)では「g」は使いません。代わりに「preg_match_all()」を使って繰り返し検索を行います。
以下が構文です。
int preg_match_all($pattern, $subject [,&$matches [,$flags [,$offset ]]])
subject を検索し、 pattern に指定した正規表現にマッチした すべての文字列を、flags で指定した 順番で、matches に代入します。
正規表現にマッチすると、そのマッチした文字列の後から 検索が続行されます。
パラメータ
- pattern(string):検索するパターンを表す文字列。(正規表現パターン)
- subject(string):検索対象の文字列。
- matches(array):マッチしたすべての内容を含む flagsで指定した形式の多次元配列。
- flags(int):以下のフラグを指定可能です。
PREG_PATTERN_ORDER(デフォルト)、PREG_SET_ORDER、PREG_OFFSET_CAPTURE - offset(int):通常、検索は対象文字列の先頭から開始されます。 オプションのパラメータ offset を使用して 検索の開始位置を (バイト単位で) 指定することも可能です。
- PREG_PATTERN_ORDER(デフォルト)
- $matches[0] はパターン全体にマッチした文字列の配列
多次元配列なので、$matches[0][0] や $matches[0][1] ...のような形で取得します。 - $matches[1] は第 1 のキャプチャ用サブパターンにマッチした文字列の配列
多次元配列なので、$matches[1][0] や $matches[1][1] ...のような形で取得します。
- $matches[0] はパターン全体にマッチした文字列の配列
- PREG_SET_ORDER
- $matches[0] は 1 回目のマッチングでキャプチャした値の配列
多次元配列なので、$matches[0][0] や $matches[0][1] ...のような形で取得します。 - $matches[1] は 2 回目のマッチングでキャプチャした値の配列
多次元配列なので、$matches[1][0] や $matches[1][1] ... のような形で取得します。
- $matches[0] は 1 回目のマッチングでキャプチャした値の配列
- PREG_OFFSET_CAPTURE
- このフラグを設定した場合、各マッチに対応する文字列のオフセットも返されます。 これにより、返り値は配列となり、配列の要素 0 はマッチした文字列、 要素 1 は subject における マッチした文字列のオフセット値となることに注意してください。
戻り値
パターンがマッチした総数を返します(ゼロとなる可能性もあります)。 または、エラーが発生した場合に FALSE を返します。
以下は、デフォルトの flag (PREG_PATTERN_ORDER)の場合のサンプルです。
パターン '/<[^>]+>(.*)<\/[^>]+>/U' は以下のような意味になります。
- < があって、その後に < 以外の文字([^>])が1文字以上あり、>が続きます。
- その後に、任意の文字がゼロ回以上あり、この部分を括弧でサブパターンとしています。
- そして、</ があり(デリミタとしてスラッシュを使っているのでエスケープしています)、その後に > 以外の文字([^>])が1文字以上あり、>が続きます。
- パターン修飾子 U は量指定子の「貪欲さ」が反転します。
$out[0] はパターン全体にマッチした文字列の配列、 $out[1] は第 1 のキャプチャ用サブパターンにマッチした文字列の配列、 といった順番となります。
出力の際は、htmlspecialchars() でエスケープしています。
<?php preg_match_all('/<[^>]+>(.*)<\/[^>]+>/U', "<strong>sub_pattern 1</strong><span class='x'>sub_pattern 2</span>", $out); echo htmlspecialchars($out[0][0]).", ".htmlspecialchars($out[0][1])."<br>"; echo htmlspecialchars($out[1][0]).", ".htmlspecialchars($out[1][1])."<br>"; ?>
sub_pattern 1, sub_pattern 2
以下は、flag に PREG_SET_ORDER を指定した場合のサンプルです。
<?php preg_match_all('/<[^>]+>(.*)<\/[^>]+>/U', "<strong>sub_pattern 1</strong><span class='x'>sub_pattern 2</span>", $out, PREG_SET_ORDER); echo htmlspecialchars($out[0][0]).", ".htmlspecialchars($out[0][1])."<br>"; echo htmlspecialchars($out[1][0]).", ".htmlspecialchars($out[1][1])."<br>"; ?>
<span class='x'>sub_pattern 2</span>, sub_pattern 2
以下は、foreach を使っての出力の例です。多次元配列なので、foreach をネストしています。
<?php preg_match_all('/<[^>]+>(.*)<\/[^>]+>/U', "<strong>sub_pattern 1</strong><span class='x'>sub_pattern 2</span>", $out); foreach($out as $k1 => $v1) { foreach($v1 as $k2 => $v2) { echo htmlspecialchars("[{$k1}][{$k2}] : $v2"). "<br>"; } } ?>
[0][1] : <span class='x'>sub_pattern 2</span>
[1][0] : sub_pattern 1
[1][1] : sub_pattern 2
以下も、foreach を使っての出力の例です。ネストせずに、それぞれの要素を指定して出力するサンプルです。
閉じタグを表すパターン <\/\\2> は、後方参照です。正規表現中の括弧の 2 番目の組、つまりこの場合は ([\w]+)、にマッチします。このため、([\w]+)と対になったタグを表しています。
<?php $html = "<strong>sub_pattern 1</strong><span class='x'>sub_pattern 2</span>"; preg_match_all("/(<([\w]+)[^>]*>)(.*?)(<\/\\2>)/", $html, $matches, PREG_SET_ORDER); foreach ($matches as $val) { echo htmlspecialchars("matched: " .$val[0]) . "<br>"; echo htmlspecialchars("(<([\w]+)[^>]*>) --> " . $val[1]). "<br>"; echo htmlspecialchars("([\w]+) --> " . $val[2]). "<br>"; echo htmlspecialchars("(.*?) --> " . $val[3]). "<br>"; echo htmlspecialchars("(<\/\\2>) --> " . $val[4]). "<br><br>"; } ?>
(<([\w]+)[^>]*>) --> <strong>
([\w]+) --> strong
(.*?) --> sub_pattern 1
(<\/\2>) --> </strong>
matched: <span class='x'>sub_pattern 2</span>
(<([\w]+)[^>]*>) --> <span class='x'>
([\w]+) --> span
(.*?) --> sub_pattern 2
(<\/\2>) --> </span>
以下は、HTML タグが入れ子になっているサンプルです。
<?php $html = "<p><a href='exmple.com#1'>sample1</a></p><p><a href='exmple.com#2'>sample2</a></p>"; preg_match_all("/(<([\w]+)[^>]*>)(<([\w]+)[^>]*>)(.*?)(<\/\\4>)(<\/\\2>)/", $html, $matches, PREG_SET_ORDER); foreach ($matches as $val) { echo htmlspecialchars("matched: " .$val[0]) . "<br>"; echo htmlspecialchars($val[1]). "<br>"; echo htmlspecialchars($val[2]). "<br>"; echo htmlspecialchars($val[3]). "<br>"; echo htmlspecialchars($val[4]). "<br>"; echo htmlspecialchars($val[5]). "<br>"; echo htmlspecialchars($val[6]). "<br>"; echo htmlspecialchars($val[7]). "<br><br>"; } ?>
<p>
p
<a href='exmple.com#1'>
a
sample1
</a>
</p>
matched: <p><a href='exmple.com#2'>sample2</a></p>
<p>
p
<a href='exmple.com#2'>
a
sample2
</a>
</p>
mb_ereg()
UTF-8 以外の日本語などのマルチバイト文字列でパターンマッチを行うには、mb_ereg() 、mb_ereg_match() を使います。
但し、mb_ereg_match() は文字列の最初からマッチしているかどうかを判定するのと、サブパターンのオプションがないので、mb_ereg() の方が使いやすいと思います。よって mb_ereg_match() については省略します。
また、mb_ereg 系正規表現 (mbregex)はデリミタは不要で、ドット(.)はデフォルトで改行にマッチします。
以下は mb_ereg() の構文です。
int mb_ereg ( $pattern , $string [, $regs ] )
マルチバイト文字列に正規表現マッチを行います。
パラメータ
- pattern(string):検索パターン。preg_match() と異なり、デリミタは不要。
- string(string):検索文字列。
- regs(array):マッチした string の部分文字列。$regs[0] にはパターン全体にマッチしたテキストが代入され、 $regs[1] には 1 番目のキャプチャ用サブパターンにマッチした 文字列が代入され、といったようになります。
戻り値
マルチバイト対応の正規表現マッチを行い、 一致した場合は 1 を返します。オプションの 3 番目の引数を指定した場合は、一致した部分のバイト数を返し、一致した部分文字列が 配列 regs に格納されます。空文字に 一致した場合は 1 が返されます。正規表現に一致しないか、 エラーを発生した場合に FALSE を返します。
注意
内部エンコーディングあるいは mb_regex_encoding() で指定した文字エンコーディングを、 この関数の文字エンコーディングとして使用します。
以下はサンプルです。
<?php mb_regex_encoding("UTF-8"); $str = "この村の猫はほとんどが半野良です。"; $return = mb_ereg('半野良', $str); if ($return == 1) { echo('パターンが見つかりました。<br>'); } else { echo('パターンは見つかりませんでした。<br>'); } echo "戻り値:{$return}<br>"; echo "mb_regex_encoding():".mb_regex_encoding() ."<br>"; echo "内部エンコーディング mb_internal_encoding():".mb_internal_encoding() ."<br>"; ?>
戻り値:1
mb_regex_encoding():UTF-8
内部エンコーディング mb_internal_encoding():UTF-8
内部エンコーディング : mb_internal_encoding()
以下はオプションの 3 番目の引数を指定した場合のサンプルです。
この場合、戻り値は一致した部分のバイト数になります。
<?php mb_regex_encoding("UTF-8"); $str = "この村の猫はほとんどが半野良です。"; $return = mb_ereg('猫.+(.野良)', $str, $matches); echo "パターン全体". $matches[0] ."<br>"; echo "サブパターン". $matches[1] ."<br>"; echo "戻り値:{$return}<br>"; ?>
サブパターン: 半野良
戻り値: 1
正規表現による置換
正規表現による文字列の置換を行うには、preg_replace() 関数、mb_ereg_replace() 関数を使います。
str_replace() 関数では、簡単に置換ができないような場合に使用します。
注意
preg_replace() 関数、mb_ereg_replace() 関数は、str_replace() 関数と比べると処理の遅い関数です。str_replace() 関数で処理できるような場合は、preg_replace() 関数、mb_ereg_replace() 関数はを使うべきではありません。
preg_replace()
preg_replace() 関数は、正規表現で検索した文字列を、別の文字列で置き換えるのに使用します。
以下が構文です。
mixed preg_replace($pattern, $replacement, $subject [, $limit [, &$count]])
パラメータ
- pattern(mixed):検索を行う正規表現パターン。文字列もしくは配列。
- replacement(mixed):置換内容。置換を行う文字列もしくは文字列の配列。
- この引数が文字列で、pattern 引数が配列の場合:
すべてのパターンがこの文字列に置換されます。 - pattern および replacement のいずれもが配列の場合:
各 pattern は 対応する replacement に置換されます。
注意
pattern および replacement を使用する際、配列の並び順に処理されます。添字は整数であっても、 その並びは値の小さい順になっているとは限りません。 ですから、配列の添字を使って、どの pattern が、どの replacement に置換されるかを指定しようとする場合は、 preg_replace() をコールする前に、各配列に対し ksort() を実行しておくべきです。 - replacement 配列の要素の数が pattern 配列よりも少ない場合:
余った pattern は 空文字に置換されます。
- この引数が文字列で、pattern 引数が配列の場合:
- subject(mixed):検索・置換対象となる文字列もしくは文字列の配列。subject が配列の場合、検索と置換は subject の各要素に対して行われ、返り値も配列となります。
- limit(int):subject 文字列において、各パターンによる 置換を行う最大回数。デフォルトは -1 (制限無し)。
- count(int):この引数が指定されると、この引数(変数)に置換回数が渡されます。
replacement(置換内容)では、括弧で囲まれたサブパターンにマッチした内容は、$0~$99($n 形式)で参照を指定することができます。 $0 は パターン全体にマッチするテキストを参照します。最初のサブパターンは $1 で参照できます。
戻り値
置換後の文字列を返します。subject 引数が配列の場合は配列を、 その他の場合は文字列を返します。
パターンがマッチした場合、〔置換が行われた〕新しい subject を返します。マッチしなかった場合、subject をそのまま返します。エラーが発生した場合、NULL を返します。
以下は、URL の文字列を、リンクに変更するサンプルです。
- 1つ目のサブパターン (https?:\/\/) は https:// または http:// にマッチします。デリミタにスラッシュを使用しているのでスラッシュはエスケープします。
- 2つ目のサブパターン ([-_.!~*'()a-zA-Z0-9;\/?:\@&=+\$,%#]+) は URL に出現する可能性のある文字クラスで、+があるのでこれらのいずれかの文字が1回以上続くパターンにマッチするという意味です。
- 置換内容の "<a href=\"$1$2\">$1$2</a>" の $1, $2 はマッチしたサブパターンの参照です。ダブルクォートはエスケープします。
<?php $pattern = "/(https?:\/\/)([-_.!~*'()a-zA-Z0-9;\/?:\@&=+\$,%#]+)/"; $url = "http://www.example.com/test/sample.html"; $link = preg_replace($pattern, "<a href=\"$1$2\">$1$2</a>", $url); echo htmlspecialchars($link); ?>
以下も、URL の文字列を、リンクに変更するサンプルですが、外部リンクの場合と内部リンクの場合に異なるリンクを適用するサンプルです。
外部リンクの場合のパターンの (?!www.webdesignleaves.com) の「(?! 」の部分は先読み言明です。「(?= 」だと肯定、「(?! 」 だと否定を意味します。ここでは「(?! 」を指定しているので先読み言明の否定になり www.webdesignleaves.com があるURLにはマッチしないことになります。
また、配列を使って、どの pattern が、どの replacement に置換されるかを指定しようとする場合は、 preg_replace() をコールする前に、各配列に対し ksort() を実行しておくようにします。
<?php //外部リンクの場合のパターン $external_pattern = "/(https?:\/\/)(?!www.webdesignleaves.com)([-_.!~*'()a-zA-Z0-9;\/?:\@&=+\$,%#]+)/"; //内部リンクの場合のパターン $internal_pattern = "/(https?:\/\/www.webdesignleaves.com)([-_.!~*'()a-zA-Z0-9;\/?:\@&=+\$,%#]+)/"; //パターンの配列 $patterns = array($external_pattern, $internal_pattern); ksort($patterns); //外部リンクの場合の置換内容 $external_replacement = "<a href=\"$1$2\" target=\"_blank\">$1$2</a>"; //内部リンクの場合の置換内容 $internal_replacement = "<a href=\"$2\">$1$2</a>"; //置換内容の配列 $replacements = array($external_replacement, $internal_replacement); ksort($replacements); //内部リンク $url1 = "https://www.webdesignleaves.com/pr/php/php_basic_01.php"; $link1 = preg_replace($patterns, $replacements, $url1); echo $link1 ."<br>"; echo htmlspecialchars($link1)."<br><br>"; //外部リンク $url2 = "http://www.example.com/test/sample.html"; $link2 = preg_replace($patterns, $replacements, $url2); echo $link2 ."<br>"; echo htmlspecialchars($link2); ?>
<a href="/pr/php/php_basic_01.php">https://www.webdesignleaves.com/pr/php/php_basic_01.php</a>
http://www.example.com/test/sample.html
<a href="http://www.example.com/test/sample.html" target="_blank">http://www.example.com/test/sample.html</a>
preg_replace_callback()
preg_replace_callback() は正規表現を使って検索を行い、コールバック関数で置換を行う関数です。
この関数の動作は、ほぼ preg_replace() と同じですが、 $replacement(置換内容)の代わりに $callback(コールバック関数)を指定します。
以下が構文です。
preg_replace_callback ( $pattern , $callback , $subject [, $limit [, &$count ]] )
パラメータ
- pattern(mixed):検索を行う正規表現パターン。文字列もしくは配列。
- callback(callable):このコールバック関数は、検索対象文字列でマッチした要素の配列が引数に渡されて呼び出されます。このコールバック関数は、置換後の文字列を返す必要があります。
function ($matches) { // $matches はマッチした要素の配列 ・・・処理・・・ // 通常は、$matches[0] がマッチした全体を表します。 // $matches[1] は、パターン内の最初の括弧で囲まれたサブパターン //'(...)' にあてはまる部分を表します。 return 置換後の文字列; },
- subject(mixed):検索・置換対象となる文字列もしくは文字列の配列。
- limit(int):subject 文字列において、各パターンによる 置換を行う最大回数。デフォルトは -1 (制限無し)。
- count(int):この引数が指定されると、この引数(変数)に置換回数が渡されます。
戻り値
置換後の文字列を返します。subject 引数が配列の場合は配列を、 その他の場合は文字列を返します。
マッチするものが見つかった場合は置換された文字列を返し、それ以外の場合は元の(変更されていない)文字列をそのまま返します。
以下は、文字列の中の数値の値を10倍に変更するサンプルです。
検索を行う正規表現パターンは、/(\\d+)/ で数値になります。この例の場合、パターン全体を括弧で囲んでいるので、$matches[0] と $matches[1] には同じ値が入っています。
<?php $text = "数値: 1, 2, 3, 45, 678 end"; $pattern = "/(\\d+)/"; function make_ten_times($matches) { return ($matches[0] * 10); //または return ($matches[1] * 10); } $new_text = preg_replace_callback($pattern, "make_ten_times", $text); echo $new_text; ?> // 数値: 10, 20, 30, 450, 6780 end と出力
前述の例では、make_ten_times() と言う関数を定義しましたが、関数を別途定義しない(無名関数を使う)場合は、以下のようになります。
<?php $text = "数値: 1, 2, 3, 45, 678 end"; $pattern = "/(\\d+)/"; $new_text = preg_replace_callback( $pattern, function($matches) { return ($matches[0] * 10); }, $text); echo $new_text; ?> // 数値: 10, 20, 30, 450, 6780 end と出力
コールバック関数に引数を渡す
コールバック関数の引数には検索対象文字列でマッチした要素の配列しか渡されません。そのため、function ($matches, $foo) { } のように追加することはできません。
追加で引数を渡したい場合は、以下のように無名関数と「use」キーワードを使うのが簡単です。
<?php $text = "数値: 1, 2, 3, 45, 678 end"; $times = 99; $new_text = preg_replace_callback( "/(\\d+)/", function($matches) use ($times) { return ($matches[0] * $times); }, $text); echo $new_text; ?> // 数値: 99, 198, 297, 4455, 67122 end と出力
その他の例
以下は、.html で終了する URL を見つけたら、その部分を target="_blank" rel="noopener" を指定したリンクに変更するサンプルです。
- 1つ目のサブパターン (https?:\/\/) は https:// または http:// にマッチします。
- 2つ目のサブパターン ([-_.!~*'()a-zA-Z0-9;\/?:\@&=+\$,%#]+\.html) は URL に出現する可能性のある文字クラスと「\.html」す。
- 置換内容 "<a href=".$matches[1].$matches[2].">".$matches[1].$matches[2]."</a>" の $matches[1] と $matches[2] にはマッチしたサブパターンが格納されています。
<?php $pattern = "/(https?:\/\/)([-_.!~*'()a-zA-Z0-9;\/?:\@&=+\$,%#]+\.html)/"; $text = "URL: http://www.example.com/test/sample.html より引用"; function add_link($matches){ return '<a href='.$matches[1].$matches[2].' target="_blank">'.$matches[1].$matches[2].'</a>'; } $text_with_link = preg_replace_callback($pattern, "add_link", $text); echo '<p>' .$text_with_link .'</p>'; ?> <!--以下のように出力されます。--> <p>URL: <a href="http://www.example.com/test/sample.html" target="_blank">http://www.example.com/test/sample.html</a> より引用</p>
前述の例では、add_link() と言う関数を定義しましたが、無名関数を使う場合(関数を別途定義しない場合)は、以下のようになります。
<?php $pattern = "/(https?:\/\/)([-_.!~*'()a-zA-Z0-9;\/?:\@&=+\$,%#]+\.html)/"; $text = "URL: http://www.example.com/test/sample.html より引用"; $text_with_link = preg_replace_callback($pattern, function($matches) { return '<a href='.$matches[1].$matches[2].' target="_blank">'.$matches[1].$matches[2].'</a>'; }, $text); echo '<p>' .$text_with_link .'</p>'; ?>
preg_replace() を使う場合は以下のようになります。
<?php $pattern = "/(https?:\/\/)([-_.!~*'()a-zA-Z0-9;\/?:\@&=+\$,%#]+\.html)/"; $text = "URL: http://www.example.com/test/sample.html より引用"; $text_with_link = preg_replace($pattern, "<a href=\"$1$2\" target=\"_blank\">$1$2</a>", $text); echo '<p>' .$text_with_link .'</p>'; ?>
mb_ereg_replace()
mb_ereg_replace() はマルチバイト文字列に正規表現による置換を行います。
内部エンコーディングあるいは mb_regex_encoding() で指定した文字エンコーディングを、 この関数の文字エンコーディングとして使用します。
以下が構文です。
string mb_ereg_replace( $pattern ,$replacement ,$string [, $option] )
パラメータ
- pattern(string):正規表現パターン。デリミタは不要。 マルチバイト文字を pattern で使用することができます。
- replacement(string):置換文字列。
- string(string):調べたい文字列。
- option(string):option パラメータで、マッチングの動作を変更可能です。
- i を指定した場合、大文字・小文字が 区別されなくなります。
- x を指定した場合、空白が無視されます。
- m を指定した場合、マルチラインモードとなり、 改行文字も "." に含まれるようになります。
- p を指定した場合、POSIX モードとなり、 改行も通常文字とみなされるようになります。
- e (非推奨→使用しない)を指定した場合、文字列 replacement がPHPの式として評価されます。
戻り値
成功した場合に結果の文字列、エラー時に FALSE を返します。
以下は、文字列に含まれているクレジットカード番号と電話番号を「********」で置き換えるサンプルです。
<<<EOL はヒアドキュメントの ID です。(ヒアドキュメント参照)
<?php $text = <<<EOL カード番号:1234 5678 9012 3456 <br> 電話番号(米国):123-456-7890 EOL; $replaced = mb_ereg_replace('\d{4}\s?\d{4}\s?\d{4}\s?\d{4}','****', $text); $replaced = mb_ereg_replace('\d{3}-?\d{3}-?\d{4}','****', $replaced); echo "オリジナル<br>"; echo $text. "<br><br>"; echo "置換後<br>"; echo $replaced; ?>
カード番号:1234 5678 9012 3456
電話番号(米国):123-456-7890
置換後
カード番号:********
電話番号(米国):********
正規表現による分割
正規表現による文字列の分割を行うには、preg_split() 関数、mb_split() 関数を使います。
正規表現の威力を必要としないのなら、より高速な (機能はシンプルですが) 代替関数として explode() を使用するという選択肢があります。
preg_split()
preg_split() は関数指定した文字列を、正規表現で分割します。以下が構文です。
array preg_split( $pattern , $subject [, $limit [, $flags ]] )
パラメータ
- pattern: 検索するパターンを表す文字列。(正規表現パターン)
- subject:対象となる文字列。
- limit:これを指定した場合、最大 limit 個の部分文字列を返します。 残りの文字列は、最後の部分文字列に含めて返されます。 limit が -1、0 あるいは NULL の場合は「制限が無い」ことを意味します。 制限を指定せずに flags パラメータを指定したい場合などに NULL を使用します。デフォルトは -1。
- flags:flags は、次のフラグを組み合わせたものとすることが可能です (ビット和演算子 | で組み合わせる)。
- PREG_SPLIT_NO_EMPTY:このフラグを設定すると、空文字列でないものだけが preg_split() により返されます。
- PREG_SPLIT_DELIM_CAPTURE:このフラグを設定すると、文字列分割用のパターン中の カッコによるサブパターンでキャプチャされた値も同時に返されます。
- PREG_SPLIT_OFFSET_CAPTURE:このフラグを設定した場合、各マッチに対応する文字列のオフセットも返されます。 これにより、返り値は配列となり、配列の要素 0 はマッチした文字列、 要素 1 は subject におけるマッチした文字列のオフセット値となることに 注意してください。
戻り値
pattern にマッチした境界で分割した subject の部分文字列の配列を返します。
マッチングに失敗した場合は、要素が一つだけの配列を返します。その要素の内容は、入力文字列そのままになります。
カンマまたは " ", \r, \t, \n , \f などの空白文字で句を分割するサンプルです。
<pre> <?php $keywords = preg_split("/[\s,]+/", "PHP JavaScript, programming"); print_r($keywords); ?> </pre>
Array ( [0] => PHP [1] => JavaScript [2] => programming )
mb_split()
mb_split() はマルチバイト文字列を正規表現により分割します。
mb_regex_encoding() で指定した文字エンコーディングを、 この関数の文字エンコーディングとして使用します。
以下が構文です。
array mb_split( $pattern , $string [, $limit ] )
パラメータ
- pattern(string):正規表現パターン。(デリミタ不要)
- string(string):分割する文字列。
- limit(int):オプションの引数 limit を指定した場合は、 最大 limit 個の要素に分割されます。 (デフォルト -1。無制限)
戻り値
分割した結果の文字列を配列で返します。
以下はサンプルです。但し、この場合は、explode() を使ったほうが良いでしょう。
<pre> <?php //mb_regex_encoding('UTF-8'); //mb_internal_encoding("UTF-8"); $text = "これは、日本語を使った、サンプルです。"; $result = mb_split('、', $text); print_r($result); ?> </pre>
うまくいかない場合は、mb_regex_encoding(), mb_internal_encoding() で文字コードを指定してみます。
Array ( [0] => これは [1] => 日本語を使った [2] => サンプルです。 )
以下は、explode() を使った例です。こちらの方が効率的(高速)です。(使用している文字コードによってはうまくいかない可能性もあります)
<pre> <?php $text = "これは、日本語を使った、サンプルです。"; $result = explode('、', $text); print_r($result); ?> </pre>
Array ( [0] => これは [1] => 日本語を使った [2] => サンプルです。 )
以下は、「2015/11/09」、「2015-11-10」や「2015.11.11」の形式の日付を区切り文字で分割して、その結果を「2015年11月09日」という形式で返すサンプルです。
<?php $dates = array("2015/11/09", "2015-11-10","2015.11.11" ); foreach($dates as $val) { $date = mb_split('[/.\-]', $val); echo $date[0] . '年' . $date[1]. '月'. $date[2]. '日 <br>'; } ?>
2015年11月10日
2015年11月11日
バリデーション
正規表現によるバリデーション等で、完全一致を示す目的で ^ と $ を用いる方法が一般的ですが、(これは間違いであり)正しくは \A と \z を用いる必要があります。
参考サイト:正規表現によるバリデーションでは ^ と $ ではなく \A と \z を使おう
以下はよく使われる形式のバリデーションの例です。
メールアドレス
厳密にメールアドレスの形式を検証するのは不可能です。なるべく厳密にチェックしようとするとかなり複雑なパターンになります。
また、もし仮に厳密に形式をチェックしたとしてもそのメールアドレスが存在するかどうかはわかりません。
以下のサンプルは、おおまかなメールアドレスの形式のチェックと入力ミス防止のためのものです。(正規のメールアドレスがエラーになったり、不正なメールアドレスがチェックをパスする可能性があります)
<?php $email = "abc123@example.com"; $pattern = '/\A([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}\z/ui'; if(!preg_match($pattern, $email)){ echo 'メールアドレスの形式が正しくないようです。'; }else{ echo "正しいメールアドレスの形式のようです。"; } ?>
- \A([a-z0-9\+_\-]+):「a~z」「0~9」「+」「_」「-」の文字が1回以上の繰り返し(最後の+)で始まることを意味します。文字クラス[ ] 内の「+」「-」はメタ文字なのでエスケープします。
- (\.[a-z0-9\+_\-]+)*:「.」文字に続いて、「a~z」「0~9」「+」「_」「-」の文字が1回以上繰り返すことを意味しますが、全体を括弧で括って、「*」があるので、0回以上繰り返すという意味になります。
- 「.」は任意の1文字を意味するメタ文字なので、文字そのものを表す場合は、エスケープします。
- 「@」が続きます。
- ([a-z0-9\-]+\.):「a~z」「0~9」「+」「-」が1回以上と末尾に「.」を加えた文字が1回以上続きます。(「_」はドメインには使えません)
- [a-z]{2,6}\z:「a~z」が2回以上6回以下繰り返して文字列が終わることを意味します。
- ui:パターン修飾子で「u」は UTF-8 エンコードを意味し、「i」は大文字・小文字を区別しません。
ドメインをチェック
checkdnsrr() 関数を使うと、メールアドレスのドメイン部分が本当に存在するかどうかを確認することができます。 但し、ドメイン名が存在しても、メールアドレスが正しく、かつ本人のものかどうかを確認することはできません。
この関数は、指定したインターネットホスト名もしくは IP アドレスに対応する DNS レコードを検索します。
以下が構文です。
bool checkdnsrr( $host [, $type] )
host に対応する type 型のレコードを DNS から探します。
パラメータ
- host(string):hostは、ドットで 4 つに分けられた形式の IP アドレスか、あるいはホスト名です。
- type(string):typeは、A, MX, NS, SOA, PTR, CNAME, AAAA, A6, SRV, NAPTR, ANY のどれか一つです。デフォルトは MX(Mail Exchanger Record)です。
【参考】
MXレコードは、電子メールの配送先であるメールサーバを決定する際に使用する、DNSで定義される情報の1つです。
また、配送先メールサーバのホスト名は A レコードまたは AAAA レコードが登録されていて、これに基づいてメールサーバの IP アドレスを取得します。
戻り値
レコードが見つかった場合に TRUE、 何も見つからないかエラーが発生した場合に FALSE を返します。
注意
5.3.0 以前の Windows プラットフォームでは動作しません。
以下は、サンプルです。第2パラメータの指定により、異なる結果が返る場合があります。
explode() でメールアドレスを @ で分割して、ドメイン部分を調べています。<?php $mailaddress = "info@example.com"; $exploded_address = explode('@', $mailaddress); //第2パラメータ:MXレコード(デフォルトなので省略) if(checkdnsrr($exploded_address[1])) { echo "このドメイン({$exploded_address[1]})は存在します。"; }else{ echo "このドメイン({$exploded_address[1]})は存在しないようです。"; } echo "<br>";「 //第2パラメータ:A レコード if(checkdnsrr($exploded_address[1], "A")) { echo "このドメイン({$exploded_address[1]})は存在します。"; }else{ echo "このドメイン({$exploded_address[1]})は存在しないようです。"; } ?>
このドメイン(example.com)は存在します。
以下は、URLのプロトコル部、ホスト名、ポート番号、パス名を取得して、ホスト名を checkdnsrr() で調べる例です。
<?php $url = "http://www.exapmle.com:1280/sample/index.htm"; $pattern = "/\A(http|https):\/\/([-\w\.]+)(:(\d+))?(\/[^\s]*)?\z/"; preg_match($pattern, $url, $matches); echo "Protocol: {$matches[1]}<br>"; echo "Host: {$matches[2]}<br>"; echo "Port: {$matches[4]}<br>"; echo "Path: {$matches[5]}<br><br>"; if(checkdnsrr($matches[2], "A")) { echo "このドメイン({$matches[2]})は存在します。"; }else{ echo "このドメイン({$matches[2]})は存在しないようです。"; } ?>
Host: www.exapmle.com
Port: 1280
Path: /sample/index.htm
このドメイン(www.exapmle.com)は存在します。
参考
以下のような関数もあります。
- dns_get_record() - ホスト名に関連する DNS リソースレコードを取得する
- getmxrr() - 指定したインターネットホスト名に対応する MX レコードを取得する
- gethostbyaddr() - 指定した IP アドレスに対応するインターネットホスト名を取得する
- gethostbyname() - インターネットホスト名に対応するIPv4アドレスを取得する
- gethostbynamel() - 指定したインターネットホスト名に対応するIPv4アドレスのリストを取得する
URL
以下は、URL をチェックする正規表現の例です。(それほど厳密ではありません)
URL には、スラッシュ「/」が含まれているので、デリミタをスラッシュ以外「{ }」にしています。こうすることで、スラッシュをエスケープしないで済みます。
{\A(https?|ftp)(://[-_.!~*\'()a-zA-Z0-9;/?:\@&=+\$,%#]+\z)}
- 最初の \A と最後の \z は、文字列の始端または終端だけにマッチします。
- (https?|ftp):http, https, ftp のいずれかにマッチします。
- [-_.!~*\'()a-zA-Z0-9;/?:\@&=+\$,%#]:URL で使用可能な文字を文字クラス [ ] を使って指定しています。
- 上記の文字クラスで先頭に「-」を記述していますが、これは文字クラス内の先頭または末尾では、「-」をエスケープしないで済むためです。先頭または末尾以外に記述する場合は、エスケープする必要があります。
- 最後の「+」は1回以上の繰り返しを意味します。
<?php $url = "http://www.exmple.com/test/index.html#sample"; $pattern = "{\A(https?|ftp)(://[-_.!~*\'()a-zA-Z0-9;/?:\@&=+\$,%#]+\z)}"; if(preg_match($pattern, $url)) { echo "正しい URL のようです。"; }else{ echo "正しくない URL のようです。"; } ?>