正規表現?
- 本記事は、正規表現の存在意義、使い方の導入です。
- 各パターンのリファレンスなどは後述でまとめております。例示として、JavaScript、Ruby、PHPを使用しております。
プログラミングの勉強を始めた時、誰でも出会う正規表現。
なんだこの暗号は、文字化けか?嫌がらせなのか?と私は直感的に感じました。その先正規表現に出会うたび、わかったふりをして逃げ回っていました。
ですが、世に中には色々な人がいますね。
電話番号一つ取っても、090-XXXXX-XXXXか、090XXXXXXXXと入力するかは育って来た環境など、それぞれの流儀があるものです。
金額を入力するときだって、円なのか$なのか¥なのか数字だけなのか、桁数で区切るのか、それぞれのポリシーがあるものです。
ユーザーに気持ちよく利用してもらうためには、入力の際の変なルールはできるだけ省くべきでしょう。しかし、裏側での処理ではデータ形式を統一しないとコンピュータはどうしていいかわかりません。
そこで必要になるのが、みんな大好き、正規表現ですね。
正規表現とは
正規表現(Regular Expression)とは、特定の文字列をパターン化して記号で表現する手法
引用 : 押さえておきたい!正規表現の利用方法をわかりやすく解説
正規表現とは、文字列の特徴(パターン)を記号化して表現するものです。
引用 : すぐ使える!正規表現サンプル集
とあります、ことWeb開発で使用する場合は、主にフォームに入力される文字列に対して使用されます。
ですから、JavaScriptの正規表現オブジェクト生成、RegExp( , )の意味がわかりますね。知らなかったけど。
要は文字の並び(文字列)にある文字が含まれているかの確認や、特定の文字を消してフォーマットするといった、ある種の フォーマット技法
実例(文字列置換、行末一致処理)
Javascript
//文字列置換(/-/で-にマッチするものを、空文字に置換)
//=>つまり、ハイフンが含まれていれば取り除くという処理
var post = '090-1234-5678';
post.replace(/-/, "");
=> '09012345678'
//文字列抽出(\dは数字にマッチするか,{}は桁数マッチ,$は末尾からマッチ)
//=>つまり、下四桁を抽出して切り出す処理、「\d」による判定が後ろから4回発生するということ。
var birth = "19910422";
birth.match(/\d{4}$/);
=> "0422"
Ruby
#gsubは当てはまるものすべてを置換。(subは最初の1つ)
post = '090-1234-5678'
post = post.gsub(/-/, "")
=> '09012345678'
birth = "19910422"
birth = birth.match(/\d{4}$/)
p birth
=> #<MatchData "0422">
@scivolaさん
またJavaScript の $ は,m オプションを付けない場合,文字列の末尾を表します。
対して Ruby の $ は,行末を表します。
ですので、改行が行われる場合はRuby では**/\d{4}$/ではなく/\d{4}\z/**になるみたいです。
PHP
<?php
// Your code here!
$post = '090-1234-5678';
//置換して再代入
$post = preg_replace('/-/', '', $post);
$birth = 19910422;
//抽出して再代入
$birth = preg_match('/\d{4}$/',$birth, $birthday);
//配列として取り出し
echo $birthday[0];
=>0422
また、PHPには正規表現リテラルは存在しないそうなので、デリミターを付与してあげるみたいです。スラッシュに限らないそうです。
@chitokuさん
また、PHP にも正規表現リテラルはないため文字列に任意の 1 バイトのデリミターを含めて表します(記事通りです)。
<?php
// デリミターに / を使います
preg_match('/\d{4}/', '1234');
// デリミターは変更できます
preg_match('@\d{4}@', '1234');
// たとえば / がたくさん含まれる場合は変えたほうが読みやすいです
preg_match('|(/\d*)+|', '/1/2/3')
上記の例を見るに正規表現オブジェクト生成の関数名は言語ごとに違うみたいですね。
しかし、共通するのは//で囲った$や\dといった、正規表現パターンの記述です。
厳密には各言語の仕様によって(正規表現リテラルがない言語もある)異なるのですが、特殊なケースでない限り、基本的なパターンの記述は同じなので、覚えておいて損はないでしょう。
後述する各リファレンスを参照してもらえれば、基本的な記述はほぼ同じということがわかります。
正規表現エンジン(より正確な記法)
各言語に組み込まれている正規表現エンジンは、細かく見ると違うそうです。
例えば、PHPは-m修飾子をつけない限り、文字列の途中に改行があった場合でも、文字列全体を1行と見なします。反対にRubyの正規表現機能は、デフォルトで複数行モードであるそうです。
正規表現によるバリデーション等で、完全一致を示す目的で^と$を用いる方法(上述例示の記法)が一般的ですが、正しくは\Aと\zを用いる必要があるそうです。(PHPでもRubyでも)
つまり、^と$で完全一致のチェックをしているつもりでも、データ末尾に改行が含まれている場合を見逃してしまう可能性があるみたいです。
以下の記事がとても参考になりました。
またご指摘いただいた内容は下記になります。
マルチラインモードとは上述の、Rubyがデフォルトで複数行モードという事です。
@t_o_jpさん
- 1.実装にどのエンジンを使っているか(PHPの場合)
- PCRE:preg系
- Oniguruma(鬼車):mb_ereg系
- POSIX:ereg系 < 現在は非推奨
**PHPでは以上の3種類があって、例えばpreg系とmb_ereg系とではデリミタの書き方が異なります※mb_ereg()はデリミタ不要**
- 2.デフォルトの動作モードと$の扱い
- PHP:^と$は行頭と行末にマッチ
- JavaScript:PHPと同様だが\Aや\zは使えない
- Ruby:マルチラインモード
**Rubyはデフォルトがマルチラインモードなので、例えばRailsで^や$を使ってると、「本当にそれ使っちゃって大丈夫?」と親切に指摘してくれたりします。**
逆にPHPでは、\Aや\zを使って正しく書けていない正規表現をよく見かけます。
engine.php
$str = "abcd\n"; // 最後に改行があります
var_dump(preg_match('/cd$/', $str)); // int(1) 改行があってもマッチしてしまいます
var_dump(preg_match('/cd$/D', $str)); // int(0)
var_dump(preg_match('/cd\z/', $str)); // int(0)
ですので、正規表現はとても便利なものだというのは確かなのですが、「言語に初めから用意されている関数やフレームワークに処理を任せる」という考え方もとても大切になってきます。
各言語の正規表現エンジンの特性を把握し、組み込まれたものに沿った、正しい処理を施してあげる事ができれば、より堅牢な処理、品質保証に繋がるかもしれません。もっと深く勉強したいと思いました。
各言語別正規表現まとめ
個人的に参照しやすかったサイトをまとめました
JavaScript
Ruby
PHP
おまけ
チェックツール
いい表現、悪い表現
[正規表現:悪い表現、いい表現、最良の表現]
(http://postd.cc/regexes-the-bad-the-better-and-the-best/)
まとめ
わざわざコードを書かなくても、空いた時間に上記リファレンスなどを参照しながら、色々な文字列に対して、おまけの各ツール使用し、結果を確認していけば、訳のわからない複雑な正規表現パターンもだんだん読めるようになってくると思います。(私も頑張ります)