LoginSignup
60
48

More than 5 years have passed since last update.

Ruby の正規表現を複数行で書く

Last updated at Posted at 2015-06-16

正規表現リテラルを複数行にわたって書くことを勧める初心者向け記事です。

一行で書くと読みづらい

正規表現は,ちょっと複雑になると,非常に読みづらくなります。

Write Once(一度書いておしまい;再編集不可〔困難〕)になりがちですね。

たとえばこんな正規表現はどうでしょうか。

/\A(?=.*\d)(?=.*[a-z])(?=.*[_\-])[a-z\d][a-z\d_\-]{7,15}\z/i

これは文字列が以下の条件をすべて満たすことを確認するための正規表現です。

  • 英字,数字,アンダースコア,ハイフンのみからなる
  • 英字,数字,記号(アンダースコアまたはハイフン)をそれぞれ少なくとも 1 字含む
  • 先頭は英字または数字である
  • 8 文字以上,16 文字以下である

正規表現として かなり単純なほう ですが,それでもじっくり見ないと読めませんね。

x オプションを使おう

Ruby の正規表現リテラルでは,x オプションを付けると,スペースや改行といった〈空白文字〉が無視されます。また # 以下がコメントと解釈されます。

よって,先ほどの正規表現は以下のように書き換えることができます:

/
  \A
  (?=.*\d)    # 数字を含む
  (?=.*[a-z]) # 英字を含む
  (?=.*[_\-]) # アンダースコアかハイフンを含む
  [a-z\d] [a-z\d_\-]{7,15}
  \z
/xi

ものすごく見やすくなったと思いませんか?

入れ子はインデントで表そう

x オプションを付けると,複雑な入れ子構造も,インデントを使って見やすくすることができます。

たとえば,カタカナ語を抽出する正規表現の一例を以下に示します。

/
  \p{Katakana}[\p{Katakana}ー]*
  (?:
    [・==]
    \p{Katakana}[\p{Katakana}ー]*
  )*
/x

「ジャン=ジャック・ルソー」のような,つなぎ記号を含む場合も考慮しています。

この程度ならインデントしなくても大丈夫ですが,「インデントが役立ちそう」という感じは摑んでいただけると思います。

検索すべきスペースを書くには

スペースが無視されてしまうので,スペース自体を検索したいときは,特別な書き方が必要になります。

例えば〈数字+スペース+数字〉という検索パターンは

/\d \d/

でいいわけですが,x オプションを使う場合はバックスラッシュ(\)でエスケープしてやって

/\d\ \d/x

と書かなければなりません。
※もちろん \u{20} あるいは \u0020 という書き方もできます。

なお,文字クラスの [ ] の中のスペースはエスケープする必要がありません。(逆に言えば [ ] の中をスペースと改行で見やすくすることはできない)
よくできてますね。

検索すべき # を書くには

# も,それ自体を検索すべきものとして書くには,エスケープしてやります。

たとえば〈数字+「#」+数字〉という検索パターンは x オプション付きの場合,

/\d\#\d/x

と書きます。こちらもスペースと同じく,文字クラスの [ ] の中に # を書くときはエスケープする必要がありません。

式展開は大丈夫なのか

正規表現リテラルは,文字列リテラルと同様,#{ } を用いて式展開を行うことができましたね。

たとえば,「明治4年」「昭和48年」といった文字列を検索するのに,

gengou = ["明治", "大正", "昭和", "平成"]
re = /(?:#{ gengou.join('|') })\d+年/

と書けますよね。

しかし,x オプションを付けた場合,# はコメントの開始のはずですから,式展開は使えない???

実は大丈夫です。上の例は以下のように書き換えられます。

gengou = ["明治", "大正", "昭和", "平成"]
re = /
  (?:
    #{ gengou.join('|') } # 変数 gengou を変えれば他の元号も OK
  )
  \d+
  年
/x

わざとらしく式展開の他にコメントも入れてみました。

なぜこれがうまく行くかというと,Ruby の処理系は,正規表現を解釈しようとするに先立って,式展開を済ませてしまうからです(知らんけど,そうらしい)。
このことは x オプションの有無には関係ありません。

式展開が済むと,このリテラルは

/
  (?:
    明治|大正|昭和|平成 # 変数 gengou を変えれば他の元号も OK
  )
  \d+
  年
/x

になりますね。これを正規表現として解釈するので,問題無いのです。

式展開が処理されるとき,コメント部分の # は,直後に { が無いので,スルーされます。この事情は文字列リテラルにおいて

"#999 の次は ##{999.next} だ"

が期待通り "#999 の次は #1000 だ" になるのと同じです。

ということは,郵便番号を検索する正規表現にコメントを書いたつもりで

/
  \d{3} #{n} は n 回繰り返しの意味
  -
  \d{4}
/x

などとしてはダメということです。#{n} の部分が式展開になってしまいます。n が定義されていなければ NameError になります。

見やすさという意味でも,コメント開始記号 # のあとには必ずスペースを入れるようにしましょう。

/
  \d{3} # {n} は n 回繰り返しの意味
  -
  \d{4}
/x

コメント中に書いてはいけないもの

コメントを書く際,もう一つ注意があります。正規表現リテラルの終端と解釈される文字を書いてはいけません。

「抽象的で意味わかんない」ですって? 要するに

/
  [a-zA-Z] # 英字(小文字/大文字)
/x

はダメってことです。一見何の問題もなく見えますが,コメント中にスラッシュ(/)があるので,ここで正規表現リテラルが終わりになります。

これを Ruby に喰わせると,正規表現リテラルの直後に「大文字)」なんて書いてあるので syntax error になります。

正規表現リテラルはスラッシュ記法の他に,%r| | とか %r< > などという書き方もできますが,その場合はコメントにスラッシュを書くことができて,終端記号である | などが書けなくなります。

参考サイト

標準添付ライブラリ紹介 【第 14 回】 正規表現 (3)
http://magazine.rubyist.net/?0021-BundledLibraries

リファレンスマニュアルの「正規表現リテラル」
http://docs.ruby-lang.org/ja/2.2.0/doc/spec=2fliteral.html#regexp

リファレンスマニュアルの「式展開」
http://docs.ruby-lang.org/ja/2.2.0/doc/spec=2fliteral.html#exp
※必読。ただし,式展開中のコメントのところはちょっと分かりにくい。

追記(2016-09-09)

続編として Ruby の文字列配列・シンボル配列を複数行で書く を書きました。

60
48
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
60
48