はじめに
- 3章関数に関しては、黒本に書いていないことはほぼないため省略し、4章文字列から
- 4章文字列だと、しれっと黒本に書いてない関数が出てきたりする
- 後半だと正規表現が出てくる。黒本だと18章。正規表現は結構使うので頑張って身につけていきたいところ。
4.1 文字列定数のクォート処理
バックスラッシュでエスケープされる対象が未知のものであった場合それはそのまま表示される
$str = "これは\c なんだ?";
echo $str;
出力
これは\c なんだ?
4.2.3 printf
- 各書式に合わせて文字列を出力可能(小数第1位切り捨てとか、日付とか、0付き数値とか、16進数形式など)
- 出力せず、変数に文字列として返したい場合はsprintf()を使う。
$date = new DateTIme('now');
$year = $date->format('Y');
$month = $date->format('m');
$day = $date->format('d');
$str = sprintf('%4d/%02d/%02d',$year,$month,$day);
echo '今日は' . $str . 'です';
出力
今日は2021/03/25です
※もっと効率的なやり方あると思うけど今回はこれで
4.2.4 print_r()とvar_dump()
- GLOBALSのような再帰構造をもつものだと、print_fは無限ループになってしまうが、var_dump()は3回までやってそのあとは出力切り捨てになる
4.3 個別文字へのアクセス
strlen()を使い、Helloという文字を1文字ずつ出力させよ。
$string = 'Hello';
for ($i=0; $i < strlen($string); $i++){
printf("%d番目の文字は %s です<br>",$i,$string{$i});
printf("%d番目の文字は %s です<br>",$i,$string[$i]);
}
※これは私も驚いたが、変数{}や変数[]で、中にある数値(0から)でそれぞれの順番にある文字が抜き出せる仕様らしい
4.4.1 空白の除去
空白の除去といえばtrim系関数だが、これにはオプションがつけられる。
デフォルトだと前後の空白しか削除しないが、オプションを指定するといろいろなものが削除可能。
$record = " Fred\tFlintstone\t35\tWilma\t \n";
$record = trim($record, " \r\n\0\x0B");
echo $record;
出力
Fred Flintstone 35 Wilma
// Fred\tFlintstone\t35\tWilma にならない?? というより\tが除去されてる感じ
なぜ??
4.6.2 類似性
2つの文字列が似ているかを調べる関数がphpには存在する
そこで問題
(1) ある単語が英語でどう発音されるかに基づいた値を出力する関数は?
(2) (1)のうち性能がよいのは?
(3) 2つの文字列に共通している文字の数を返す関数は?
(4) 2つの文字列を一致させるために何文字を修正すればいいかを計算することで類似性を出力する関数は?
解答
(1) soudex(),metaphone() サウンデックス・メタフォン
(2) metaphone()
(3) similar_text()
(4) levenshtein() リーベンシュタイン
以下、例
なお、soundexとmetaphoneだが、同時に使うことはできず、比較する時は必ずどちらかに統一しなければならない。
//(1)
$name1 = "Fred";
$name2 = "Phred";
if (soundex($name1) == soundex($name2)){
print "soundex: {$name1}と{$name2}は似てる!<br>";
} else {
print "soundex: {$name1}と{$name2}は似てないな<br>";
}
if (metaphone($name1) == metaphone($name2)){
print "metaphone: {$name1}と{$name2}は似てる!<br>";
} else {
print "metaphone: {$name1}と{$name2}は似てないな<br>";
}
//(2)
$name1 = "Rasmus Lerdorf";
$name2 = "Razmus Lehrdorf";
$common = similar_text($name1,$name2,$percent);
printf("2つの文字列は %d 文字(%.2f%%)一致",$common,$percent);
//(3)
echo $similarity = levenshtein('cat','cot');
4.7.3.3 sscanf
printfはフォーマットに沿って表示させるが、こっちは、フォーマットに沿って文字列を分解し、配列に入れるという動作を行う。
たとえば、次の例は日時を分解した例。
$x = '12/31 23:59';
$y = sscanf($x,'%d/%d %d:%d');
echo $y[0], '月';
echo $y[1], '日';
echo $y[2], '時';
echo $y[3], '分';
出力
12月31日23時59分
下はオプションの変数をつけた場合、返り値は要素(フィールド)数になる。
$x = '12/31 23:59';
$y = sscanf($x,'%d/%d %d:%d',$month,$day,$hour,$minute);
echo "{$y} 個のフィールドに分解しました<br>";
echo "{$month}月{$day}日{$hour}時{$minute}分";
4.7.4.3 マスクを使う関数
黒本にも載ってる内容だが、strcspn()はNULバイトやタブ、キャリッジ・リターンが含まれてないかを調べるのに有効。
echo strcspn($str,"\n\t\0") != strlen($str);
というように使う。
NULバイトやタブ・キャリッジ・リターンが含まれていないかを調べることができる。
4.8 正規表現
正規表現=検索のパターンを表す文字列。このパターンを別の文字列と比較し、その文字列がそのパターンにマッチするか調べる関数がある
使用方法としては大まかに「マッチした情報のゲット」、「マッチした内容を別の文字列への置換」、「マッチした文字列をいくつかに分離する」という3つがある。
PHPで使用可能な正規表現
(1) PHPで使用可能な正規表現を3つ
(2) (1)はそれぞれどのような拡張モジュールを持っているか?
(3) (1)の各正規表現の構文モードは?
(4) 日本語を含むマッチングのために必要なものは?
解答
(1) mbstringの正規表現、Perl互換の正規表現,POSIXの正規表現
※なおPOSIXはPHP5.3.0で非推奨
(2) mbstring拡張モジュール、PCRE(ペクル)拡張モジュール、regex(リジェックス)拡張モジュール
※mb_で始まる関数、preg_で始まる関数、ereg_で始まる関数である。
※PCREはC言語のライブラリ。
(3) Ruby,Perl,regex
※デフォルトだが、切り替えも可能
(4) mbstring以外だと、文字エンコーディングにUTF−8,オプション「U」を指定する
正規表現の基本
- NULLバイトが文字列の終わりの記号と判断され、認識されない時がある
→バイナリセーフな関数で対応可能 - 文字クラス[]/選択肢|/繰り返し/アンカー/デリミタ/量指定子は基本。
参考:サルでもわかる正規表現
ありがとうございます! - 言明(assertion)とは、パターンの前後に対する条件付け。アンカー、言明サブパターン(4.8.15参照)がある。
(1) 量指定子で{n,m} {n} {n,} の意味は?
(2) アンカーで、[[:<:]] [[:>:]] \b \B \A \z \Z の意味は?
(3) 文字クラスで\s \S \w \W \d \D の意味は?
解答
(1) 最低n回、m回以下 最低n回、n回以上
(2) 単語先頭/単語末尾/ワードの境界/ワードの境界以外/文字列の先頭/文字列の末尾(改行の直前)/文字列の末尾
(3) 空白文字/空白文字以外/ワード文字/ワード文字以外/数字/数字以外
文字クラス
(1) 照合順序とは?
(2) 等価クラスとは?
解答
(1) ロケール、つまり地域によっては特定の文字列を1つの文字として扱うことがあるが、これを照合順序という。
たとえば、スペイン語でchは1つの文字として扱われている。 a,b,ch,c、d・・というように。
とすると、文字クラスで[st[.ch.]]は、sかtかchのどれかにマッチするということになる。
(2) 等価クラスは、たとえば[a/a(ウムラウトつき)/a(アクセントつき)]の場合、並び順はaという区切りで行われる。
4.8.11 キャプチャしないグループ
(?:サブパターンさせる言葉)とする
下記はキャプチャさせるグループとキャプチャさせないグループの比較
echo preg_match("/(ello)(.*)/","jello biafra",$match1);
print "<br>";
echo $match1[1];
print "<br>";
echo preg_match("/(?:ello)(.*)/","jello biafra",$match2);
print "<br>";
echo $match2[1];
出力
1
ello
1
biafra
理由として、まず1つ目でマッチするのは
ello biafra,ello,biafraの3つ。
2つめは
ello biafra,biafraの2つだけ。
理由はおそらく、1つ目の場合、(ello)がキャプチャされ、2つ目で出力されたからだろう。
2つ目の場合はelloがキャプチャされなかったので出力されず、biafraだけ出力されたってことか。
4.8.12 後方参照
パターンの中で取得したテキストを同じパターンで後の方にないか探すことができる
echo preg_match("/([[:alpha:]]+)\s+\\1/","Paris in the the spring",$m);
print "<br>";
echo $m[1];
出力
the
まず最初と最後の/はデリミタ。
( )で囲まれたサブパターンは、アルファベットが1文字以上というのを表している。
そしてその後の\sはスペース。が+で一文字以上続くことを表す。
\1はバックスラッシュはエスケープで、後方参照、つまりさっきマッチした同じ文字が出てくることを表している。
これを全部確認すると、
たとえば、Paris Parisとか in in、the the とかspring springだったら、マッチするということ。
で、ここではthe theがマッチする。
ここで、echoは、$m[1]、つまりサブパターンなので、theが出力される。
ここは理解するのに悪戦苦闘した(こちら参照)
助けていただいた@blue32aさん、ありがとうございました。
要するにパターンがコピーされるというわけではないというのを勘違いしてたということ。
つまり、/([[:alpha:]]+)\s+\1/ は、/([[:alpha:]]+)\s+([[:alpha:]]+)/じゃないの? という。
そうではなくて、()内で取得できたワードがそのまま\1にくる。それがパターンになる。
4.8.13 後置オプション
閉じデリミタの後に置ける1文字のオプション。これによって挙動が変わる。
echo preg_match("/cat/i", "Stop,Catherine!");
出力
1
Catherineの「Cat」は大文字であるが、大文字小文字を区別しなくなったのでcatとマッチする。
以下はどんな挙動を起こすものか?
(1) i
(2) s
(3) x
(4) m
(5) e
(6) U
(7) u
(8) X
(9) A
(10) D
(11) S
解答
(1) 大文字小文字を区別しない
(2) ピリオドを改行を含めた任意の文字にマッチさせるようにする。
通常は改行は含めない。
(3) パターンから空白・コメントを除去する
(4) キャレットは各改行直後の文字、ドルは各改行直前の文字にマッチさせる。(いわゆる複数行モードのこと)
(5) 置き換え文字にPHPを指定すると、そのコードをevalした結果で文字列を置き換える。
(6) 貪欲さの指定を反転する。*や+の挙動を貪欲さを最低にする
(7) パターンをUTF-8として扱う
(8) バックスラッシュ直後に特殊文字でないものが続くとエラーを起こすようにする。
(9) パターンの最初の文字が^であるように文字列の先頭にアンカーを設定する
(10) $を行末のみマッチさせるようにする
(11) パターンを解析をじっくりして、次からの処理を早くする。
なお、オプションは複数設定できる。たとえば/imのように。
4.8.14 インラインオプション
上記以外にもパターンの一部にだけ適用するオプションを設定できる。
フォーマットは(?(フラグ名):(パターン))
使えるのはi,m,s,U,x,X
echo preg_match('/I like (?i:PHP)/','I like pHp');
もちろん1を返す。
逆に上記でオプションの前にハイフン(-)をつけることでその設定を無効にできる。
echo preg_match('/(?-i:I like) PHP/i','I like pHp');
もちろん1を返す。
echo preg_match('/I like (?i)PHP/',I like pHp');
echo preg_match('/I (like (?i)PHP)/',I like pHp a lot',$match);
echo $match[1];
※ただし、このインラインオプションが設定された部分(カッコ内)はキャプチャの対象にならず、キャプチャしたい場合は、別に()を用意する必要がある。
4.8.15 先読みと戻り読み
もし次(前)の文字が○○だったら、これをマッチさせるという機能。
文字列を分解するときに便利
これらはどういう意味か?
- パターンX(?=パターンY)
- パターンX(?!パターンY)
- (?<=パターンY)パターンX
- (?<!パターンY)パターンX
解答
- Xの直後でYがマッチする
- Xの直後でYがマッチしない
- Xの直前でYがマッチする
- Xの直前でYがマッチしない
下記は例
$message = preg_split('/(?=^From )/m' $mailbox);
メールボックスでメール内容を取得する方法。
Fromで始まるところが分離できる点なのでこれが効果的。
4.8.16 無視
繰り返しを含む式を更に繰り返さないようにする
FMTは(?>subpattern)。
つまりマッチした部分を更に繰り返さないようになる。
ただ、出力方法は以前と変わらず、時間が短くなるだけ。
下記は前者が無視を使わないパターンの例、後者が無視を使ったパターンの例
どちらも出力はN。
時間が短くなるかどうかはこのサンプルでは測れず。
$p = '/(a+|b+)*\.+$/';
//$p = '/(?>a+|b+)*\.+$/';
$s = 'ababbbababababababababababababa..!'
if (preg_match($p,$s)){
echo 'Y';
}
/*$p = '/(a+|b+)*\.+$/';*/
$p = '/(?>a+|b+)*\.+$/';
$s = 'ababbbababababababababababababa..!';
if (preg_match($p,$s)){
echo 'Y';
} else {
echo 'N';
}
4.8.17 条件式
正規表現で使用可能なif文のようなもの。
(?(条件)yespattern)
(?(条件)yespattern/Nopattern)
※なお、条件として使用可能なのは後方参照(1~99まで)・先読み・戻り読みのみ。
(?(?=[^a-z]*[a-z])
\d{2}-[a-z]{3}-\d{2} | \d{2}-\d{2}-\d{2} )
上記はYes/Noを使ったもの。
最初ので、数値が続いた後にそれがあってもなくてもよく、その後にアルファベットが続くものにマッチするか調べる。
つまり、数値のあとに少なくとも英字が1文字以上あるかを調べる。
この場合、-があるかどうかは条件式の時点では問題ではない、ということ。
マッチしたら、次にYesの場合はdd(数字)-aaa(アルファベット)-dd(数字)にマッチするか調べる
Noの場合はdd(数字)-dd(数字)-dd(数字)にマッチするか調べる。
この場合、dd(数字)-aaa(アルファベット)-dd(数字)か、dd(数字)-dd(数字)-dd(数字)に当てはまるものがあれば
マッチする。
たとえば、11-aaa-22とか、22-33-44とかにマッチするということ。
4.8.18 関数
ここでいう関数は正規表現に関連する関数ということ。
- 正規表現に関する関数は5種類に分けられるか、どんなカテゴリか。
- マッチングの関数は?
- 置き換えの関数は?
- 分割の関数は?
- フィルタリングの関数は?
- クォートの関数は?
- Perl互換の正規表現とPerlの正規表現の違いは?
解答 - マッチング・置き換え・分割・フィルタ・テキストのクォートの5つ。
- preg_match() Perl形式のパターン。
preg_match_all() - preg_replace()
- preg_split()
- preg_grep()
- preg_quote()
- パターン文字列内でNull文字は扱えない
Perlの\E,\G,\L,\i,\Q,\u,\Uはサポートしていない
(?(Perlのコード))はサポートしていない
\D\G\U\u\A\x
\v(垂直タブ)はサポートしている
先読み・戻り読み言明は*・+・?で繰り返せない
否定言明で使用した()付きのサブパターンにマッチした内容は記憶されない
戻り読み言明で選択肢を使う場合は、異なる長さの文字列を選択肢として選べる。
4.8.18.1 マッチング
preg_matchについては結構使ってきているので割愛
FMTはこちら
preg_match_all(pattern,target_string,matches[,order]);
target_stringをpatternで検索し、マッチしたすべての文字列をorderで指定した順番にmatchesで指定した配列に入れるというのが具体的な処理内容。
orderは2つのオプションが有る
1) PREG_PATTERN_ORDER(デフォルト)
$matches[0] は 1 回目のマッチングでキャプチャした値の配列、 $matches[1] は 2 回目のマッチングでキャプチャした値の配列。
preg_match_all("|<[^>]+>(.*)</[^>]+>|U",
"<b>example: </b><div align=left>this is a test</div>",
$out, PREG_PATTERN_ORDER);
echo $out[0][0] . ", " . $out[0][1] . "<br>";
echo $out[1][0] . ", " . $out[1][1] . "<br>";
最初は「<」で、その次は「>」以外(※キャレットがあるから)のすべての文字が1文字以上で、次に「>」があり、次にどんな文字でも良いから1文字以上、次にがきて、次はさっきのと同じで、>が来る。Uは貪欲さをなくすので、最小でマッチするものを探すということになる。
そんなわけで、$match[0]は、<b>example: </b>と、<div align=left>this is a test</div>の2つ。
次にサブパターン、つまり(.*)の部分だから、example:と、this is a testの2つがくることになる。
これはHTMLで抜き出すときに便利だな
2) PREG_SET_ORDER
$matches[0] は 1 回目のマッチングでキャプチャした値の配列、 $matches[1] は 2 回目のマッチングでキャプチャした値の配列、 といった順序。
preg_match_all("|<[^>]+>(.*)</[^>]+>|U",
"<b>example: </b><div align=left>this is a test</div>",
$out, PREG_SET_ORDER);
echo $out[0][0] . ", " . $out[0][1] . "<br>";
echo $out[1][0] . ", " . $out[1][1] . "<br>";
説明は上のとおりだが、配列の順番が変わる。
そんなわけで、$match[0]は、<b>example: </b>と、example:が入り、
$match[1]は<div align=left>this is a test</div>と、this is a testがくる
つまり1)はタグのみの配列と、タグに囲まれた文字列のみの配列、
2)はマッチングしたタグとそのタグに囲まれた文字列を配列に入れている、ということになる。
4.8.18.2 置換
replaceはWordの検索・置き換えと同じで、文字列中からパターンにマッチする内容を別の内容に変更する。
preg_replace (pattern , replacement , target[, limit, count])
target に関して pattern を用いて検索を行い、 replacement に置換。
その回数はlimitになり、実際の置き換え回数がcountに入る。
limitのデフォルトは-1でこれはマッチしたものすべてに対して置き換えを行う。
$string = 'April 15, 2003';
$pattern = '/(\w+) (\d+), (\d+)/i';
$replacement = '${1}1,$3';
echo preg_replace($pattern, $replacement, $string);
数字リテラルがあとに続く場合、\1の代わりに{1}を使うと正常に対応可能。
ちなみに上のパターンにはスペースが各()と()の間にあるので注意。
よって、{1}(\1)はApril,\2は15,\3は2003をキャプチャしている。
そこからリプレイスを行うと出力は「April1,2003」となる
次はcount引数を使った場合
$count = 0;
echo preg_replace(array('/\d/', '/\s/'), '*', 'xp 4 to', -1 , $count);
echo $count; //3
出力は
xp***to
3
まず パターンは 数字とスペースをに置き換えるということ。
結果としてxp**toになった。
最後は置き換えた回数が入るので、3になる。
4.8.18.3 分割
・pret_split() 取り出す各部分を区切る文字が予めわかっている時はこれを使う
上にある記号をマッチした箇所で区切る。
FMTはpreg_split(pattern,string [,limit [,flags]]);
$ops = preg_split('{[+*/-]}','3+5*9/2');
var_dump($ops);
この場合は+,*,/で区切るということなので、
array(4) { [0]=> string(1) "3" [1]=> string(1) "5" [2]=> string(1) "9" [3]=> string(1) "2" }
とオペランドが表示される。
flagsは3つある
- PREG_SPLIT_NO_ENTRY・・・区切り文字を返さない
- PREG_SPLIT_DELIM_EMPTY・・・分断された部分が空文字ならリターンしない
- PREG_SPLIT_DELIM_CAPTURE・・・パターンにマッチした箇所を返す
$ops = preg_split('{([+*/-])}','3+5*9/2',-1,PREG_SPLIT_DELIM_CAPTURE);
var_dump($ops);
内容的にはかわらないが、パターンにマッチした箇所を返す、というので、出力は下記のようになっている。
array(7) { [0]=> string(1) "3" [1]=> string(1) "+" [2]=> string(1) "5" [3]=> string(1) "*" [4]=> string(1) "9" [5]=> string(1) "/" [6]=> string(1) "2" }
$array = preg_sprit('//',$string);
これだと、文字列のすべての文字の間にマッチし、1文字ずつ抜き出す形になる。
4.8.18.4 フィルタリング
指定された配列の要素のうちパターンにマッチするものだけを返す。
下の例だと、末尾が.txtになるファイルだけを取得する。
$matching = preg_grep(pattern,array);
//ex
$textfiles = preg_grep('/.txt$/',$filenames);
4.8.18.5 クォート
指定された文字列にのみマッチする正規表現を作成
$re = preg_quote(string [, delimiter]);
//ex
echo preg_quote('$5.00 (five bucks)');
こうすると、出力は
\$5\.00 \(five bucks\)になる
つまり特別な意味を持つ文字(*や$など)をstringで指定するとエスケープする。
$toFind = '/user/local/etc/rsync.conf/'
$re =preg_quote($toFind, '/');
if (preg_match("/{$re}/",$filename)){
echo 'みつかりました!';
}
上のようにオプション2番めのところにクォートを行う文字を指定すると、エスケープする。