woshidan's blog

あいとゆうきとITと、とっておきの話。

今日からはじめよう、正規表現 in Ruby (論理式とオプション編)

論理式とオプション

 

今日は最後に論理式とオプションについてまとめて終わりにしましょう。

 

初心者:え、文字コードは扱わないのですか?

 

正規表現文字コードRubyの場合、オプションで指定が出来るけど、

大変申し訳ない話、日本語の正規表現エンコーディングをうまくRubyで扱えなかったので、

オプションのところでさわりだけ一緒にやりましょう。

(誰か、Regexp::FIXEDENCODINGあたりが分かる人がいたら、教えてください)

 

初心者:分かりましたー。

 

関係ないんだけど、せめて社外の人と話すときは語尾はのばさないでください。

 

論理式

正規表現で主に使う論理式には&&と^,|がありますが、まず&&から行きましょう。

文字クラスの中で&&って書くとandの意味になります。

 

初心者:どういうことですか?

    文字クラスって並列というか、どれか一文字なんじゃないですか。

 

ええと。

文字クラスでは基本的に書けば書くほどそこの文字でマッチする文字の種類が増えていってその中でどれか一文字に当てはまったらマッチする、と覚えてるんですかね。

 

文字クラスはマッチするためには文字がこの範囲に含まれていいっていう範囲を表すもので、この範囲を表す記号同士の間に&&を入れるとその範囲が表してる集合の積集合になります。

 

/[[a-k]&&[d-g]]+/.match("abcdefghijklmn") #=> #<MatchData "defg">

 

この例だと、左側の文字クラスにマッチする文字はa,b,c,d,e,f,g,h,i,j,k

右側の文字クラスにマッチする文字はd,e,f,g。

二つの積集合[[a-k]&&[d-g]]にマッチする文字は、両方に含まれるd,e,f,gになっていますね。

 

範囲を表していれば、別に文字クラスを表す[]がついてなくてもいいですよ。

また、下の段みたいに、文字クラスの中に、文字クラスを含んだ形でかいても大丈夫です。

 

/[a-k&&d-g]+/.match("abcdefghijklmn") #=> #<MatchData "defg">
/[a-k&&[d-g]]+/.match("abcdefghijklmn") #=> #<MatchData "defg">

 

初心者:? あの、文字クラスの中に文字クラスを含むと、何か特別な意味になったりしますか?

    見た目が何か特別そうに見えるんですけど。

 

いや、特に、&&や^を使わないのであれば、の中に普通に文字を並べた場合と、違いはないですよ。

文字クラスに論理式でこれらの演算を使いたいときに、の中に[]を入れることもある、という感じですかね。

実際、下の二段の正規表現は同じ意味になっていますよ。

 

/[a-de]+/.match("abcdefghijklmn") #=> #<MatchData "abcde">
/[a-d[e]]+/.match("abcdefghijklmn") #=> #<MatchData "abcde">

 

そうそう、_(アンダースコア)と違って、-(ハイフン)は範囲の意味を持つメタ文字だから、-を文字として使いたい場合は先頭におくか、\-って\(バックスラッシュ)をつけてくださいね。

 

^と順番は入れ替わりますけど&&があるなら|もあります。

これはグループの中に選択(|の前後のどちらかにマッチすればよい)を作りたいときに使いますよ。こんな感じです。

 

/(and|or)/.match("and") #=> #<MatchData "and" 1:"and">
/(and|or)/.match("or") #=> #<MatchData "or" 1:"or">

#andかorのどちらかにマッチするのであり、andが持つ文字(a,n,d)かorが持つ文字(o,r)

#を持っていてもそれだけではマッチしない

/(and|or)/.match("ndo") #=> nil

 

文字クラスの中にも使えますけど、もともと文字クラスの中に入れたという時点で|という意味を持っているから、意味はないですね。

下の二段を見てもらえば分かると思いますが、グループ化と違って文字を並べても、その文字の並びを指定する意味もないし。

 

/[and|or]+/.match("or") #=> #<MatchData "or">
/[andor]+/.match("or") #=> #<MatchData "or">

 

#ndもoもand,orの一部であるが、and,orにはマッチしない。

#and,orの文字列の選択になっている上の段にndoはマッチしないが、

#aかnかdまたはoかrという風に範囲の選択(和集合)になっている下の段にはndoはマッチする。

/[and|or]+/.match("ndo") #=> #<MatchData "ndo">
/(and|or)/.match("ndo") #=> nil

 

初心者:要するに[]の中では|は意味ないんですね。

 

説明理解できてなかったみたいで申し訳ないけれど、それでいいですよ。

一回中学生高校生に戻った気持ちで樹形図を書いて、

何文字目にいくつの文字が入っていいか、みたいなのをやったら理解しやすいと気がします。

 

初心者:あ、できま……

 

手書き画像はもういいですよ。

 

さて、今度は否定(^)ですね。

文字クラスの先頭に^(キャレット)を置くと、その後ろの列挙した文字以外って意味になります。

 

つまり

[^a]って書いたら、この部分にはa以外の文字が来ますよ、という意味になります。

 

よく分かってない人:なるほどーって、あれ?

 

/[^a]+/.match("abc\ndef.") #=> #<MatchData "bc\ndef.">
/[^\s]+/.match("abc\ndef.") #=> #<MatchData "abc">

 

君は期待を裏切りませんね。

 

とりあえず、[^a]はa以外の英数字ではなくて、a以外の文字、つまり特殊文字も入ります。

というのが一行目。

二つ目。空白文字(\s)には改行も含まれています(ここは言語にもよるかも)。

ので、空白文字の否定には改行は入りません。

 

初心者:つまり……

 

この結果自体を覚えるというより、

普通の文字に否定を使うときは、入って来たら困る特殊文字が検索対象の文字列に入ってないか、

確認しておく事。

特殊文字に否定を使うときは、その特殊文字が具体的に何をさすのか確認しておく事。

 

正規表現の試験でもない限り、調べながら書けば十分だと思います。

 

オプション

 

オプションは、基本的にこの位置につけます。

 

/正規表現/オプション

 

初心者:あ、iとかmとか書いたことあります。

 

はい、iやmが代表的なオプションですね。

言語によって細かいところは違うから、とりあえずRubyのものをあげると、こんな感じになります。

 

/pat/i 大文字小文字を無視する

/pat/m メタ文字「.」が改行にマッチするようになる

/pat/x コメント内の空白を無視する。コメントの仕様が変化する

/pat/o パターン内の #{} の展開を1回限りしかしない。

(http://docs.ruby-lang.org/ja/1.9.3/doc/spec=2fregexp.htmlより引用)

 

初心者:xって見た事ないですけど。どういう使い方をすr

 

//の中にコメントまで入れておく必要、あります?(そんなのめちゃくちゃ読みたくない)

 

初心者:あ、はい。すみませんでした。

 

/oもほとんど使わないと思うから、/i,/mだけでいいかな。

 

初心者:分かりました。

    ところで、文字コードの話はどこへ行ったのですか?

 

今から出来る範囲でします。

 

正規表現エンコーディング

 

通常、正規表現エンコーディングソースコードエンコーディングと 同じであると見なされます。 ただし正規表現が ascii 互換の文字しか含まない場合は エンコーディングは US-ASCII になります。

 

/pat/u UTF-8

/pat/e EUC-JP

/pat/s Windows-31J

/pat/n ASCII-8bit

 

(http://docs.ruby-lang.org/ja/1.9.3/doc/spec=2fregexp.htmlより引用)

 

初心者:なるほど……って使い方が、わからないんですが。

 

そうだね、これは、ほとんど使う事がないかもしれないですけど。

とりあえずね、Rubyの場合は、

 

/文字だよー/.encoding #=> #<Encoding:UTF-8>

 

という風に、正規表現の中の文字のエンコーディングを調べる事が出来ます。

これを使いながら、上のオプションをつけた場合のエンコーディングの変化を見てみましょう。

 

/str/.encoding #=> #<Encoding:US-ASCII>
/str/u.encoding #=> #<Encoding:UTF-8>
/str/e.encoding #=> #<Encoding:EUC-JP>
/str/s.encoding #=> #<Encoding:Windows-31J>
/str/n.encoding #=> #<Encoding:US-ASCII>

 

初心者:

ちょっとなんで「文字だよー」から「str」に変わってるのか気になるんですけど。

オプションでの//中のエンコーディングが変わるってことですか?

でも//中のエンコーディング変えて何がいいんですが?

あと、US-ASCIIってなんですか?

 

気持ちは分かるけど、答えにくいから質問は一つずつにしてくれると助かります。

 

さて、とりあえず、US-ASCIIについて、答えましょうか。

strっていうのは英数字だけで構成されていて、これはASCIIで扱っている文字だけですね。

この場合、正規表現エンコーディングはUS-ASCIIとなるように設定されています。

 

それ以外の文字(主に日本語)が入っている場合はデフォルトとして

正規表現エンコーディングは私の場合、UTF-8になります。

 

初心者:私の場合?

 

まあ、自分の場合、エディタやターミナルのエンコーディングの設定がデフォルトでUTF-8になっているから、UTF-8になる。

正規表現エンコーディングのデフォルトの設定は、エンコーディングの最初に確認した通り、「正規表現エンコーディングソースコードエンコーディングと 同じ」だから、ソースコードEUC-JPで書いていたら、そのソースの中の正規表現のデフォルトのエンコーディングEUC-JPになると思います。

 

初心者:なるほどー。

 

エンコーディングオプションをいつ使うのか

次、//中のエンコーディングを変えることの意味なのですが、

これは、たとえば別のファイルの内容を文字列として読み込んでマッチングさせる、といった感じ。

 

HTMLファイルがShift-JISだけど、

要素の属性の指定漏れをチェックする正規表現を動かすテストプログラムのコードはUTF-8みたいなケース。

 

初心者:title要素に「ページタイトル!」みたいに入ってるか調べたいんだけど、そのまま調べたら対象文字列のほうが、テストプログラムからしたら文字化けして見えるから、調べられない、ということですね。

 

まあ、そんな感じですね。分かっていただけたら結構。

 

で、『なんで「文字だよー」から「str」に変わってるのか』に戻るんだけど、

ソースコードエンコーディング正規表現エンコーディングが違うと、エラーが出るんですよね。

 

/"文字だよー"/e
SyntaxError: regexp encoding option 'e' differs from source encoding 'UTF-8'

 

こんな感じです。

 

初心者:え、じゃあどうやって使うんですか?

 

Regexp#fixed_encoding?を入力してみよう。

 

Regexp#fixed_encoding?
=> Regexp

 

初心者:こうですか。

 

ええ。真に相当する値が返って来たね(Rubynil,false以外真ですね)。

これが真だと、

正規表現エンコーディングソースコードエンコーディングが一致しておらず、

ASCIIの範囲から外れた文字が入っている正規表現を書いた場合、

さっきのエラーが出ます。

 

初心者:じゃあ、使いどころがないじゃないですか。

 

いやいや、とりあえず知っておくだけで十分じゃないでしょうか。

 

自分はまだ、このエラーに対する対処法が分かってないから、

この手の正規表現エンコーディングのエラーが出たら、

マッチ対象の文字の方を先にソースコードエンコーディングに変換するようか、

ファイル読み込むときにきちんと指定するように直すかしますかね……。

 

興味あって調べて分かったら教えてください。

 

初心者:無茶ぶりですね。 

 

最後にグダっちゃってごめんなさい。

 

とりあえず、これで、この記事のはじめの頃よりだいぶ正規表現が使えるようになったと思いますよ。

じゃ、これからお仕事頑張ってください。

 

初心者:はい。