Rubyの正規表現

Ruby

こんにちは。
WYRDの豊田です。
持ち回りブログ3週目です。

早速ですが、みなさんはお休みの日はどのように過ごしていますか?

私たちの業界は基本的に土日お休みなことが多いのですが、私は特に予定がなければ技術的な勉強(本を読んだり、調べ物をしたり)に時間を割くことが多いです。
技術は物凄いスピードで進化・進歩していて、常にアンテナを張っていないとすぐに取り残されてしまいますからね。
「これだけ学べば十分」と思える日は来ないと思っています。

少し話が逸れてしまいました。

最近、弊社内ではRuby技術者認定試験に合格しようぜ!という機運が高まっています。
この間の浅野さんのブログ記事もSilverを受験するというお話しでしたね。(ここだけの話、合格したらしいですよ

ということで、私も今更ながらGoldの合格を目指して久しぶりに試験勉強を始めました。

今回は試験勉強の中で出てきたRubyでの正規表現について書いていこうと思います。

基本的なところ

Rubyでは他の多くの言語で採用されているPerl拡張正規表現が採用されています。(完全互換ではないので注意)

Regexpクラス

正規表現を使ったパターンマッチングを行うにはRegexpクラスを利用します。
Regexpはregular expressionの略ですね。

Regexp.new('hoge') === 'hoge' #=> true

または、正規表現リテラルを使うこともできます。(生成されるオブジェクトはRegexpクラス)

/hoge/ === 'hoge' #=> true
%r{hoge} === 'hoge' #=> true

パターンマッチ

パターンマッチには以下の方法があります。

  • Regexp#===
  • Regexp#=~
  • String#=~
  • Regexp#match? ( Symbol#match?, String#match? ) ※ 2.4以降
/hoge/ === 'hoge' #=> true
/hoge/.match?('hoge') #=> true
:hoge.match?(/hoge/) #=> true

# `=~` はマッチした位置を返す
/hoge/ =~ 'hoge' #=> 4
'hoge' =~ /hoge/ #=> 4

# String#===は挙動が異なる
'hoge' === /hoge/ #=> false

String#===は同値であるかの判定しかしません。

Regexp#match

Regexp#matchメソッドを使えば、マッチするかの判定に加えマッチした情報の詳細を取得することもできます。

match_data = /\d/.match('hoge9')
match_data.class #=> MatchData

MatchDataクラスについては、話すと長いのでここでは割愛します。
MatchData – リファレンスマニュアル

先頭と末尾

これはRubyを使っていると一度は通る道だと思います。
/\A ... \z/とすることで、文字列の先頭と末尾を明示的に指定できます。
他の言語でいうところの ^ , $ です。

Rubyでも ^$ も使えますが、挙動が違います。

"hogehoge\n1234567890" =~ /\A\d+\z/ #=> nil

# 行単位でマッチするか確認するので、改行コードが入っていると挙動が変わる
"hogehoge\n1234567890" =~ /^\d+$/ #=> 9

あと /\A ... \Z/というのもありますが、あまり使わないので割愛します。
(知りたい方はググるとたくさん良い記事が見つかります)

キャプチャと後方参照

キャプチャは正規表現では結構普通に使われるテクニックになりますが、Rubyでも利用可能です。

()で囲んだ範囲がマッチすると、マッチした文字列を同一正規表現内で使ったり確認したりできます。

# キャプチャした文字列は \1 で後方参照できる
/(\w+) of \1/ === 'hoge of hoge' #=> true

# Rubyの実行環境内であれば $1 でキャプチャ文字列を参照できる
$1 #=> 'hoge'

複数キャプチャした場合、\1, \2, \3... , $1, $2, $3... 数字を増やすことで後方参照できます。

名前付きキャプチャ

\1 で後方参照するのは直感的ではなく使いづらい気もします。
そんな時はキャプチャには名前をつけて後方参照することができます。

名前付きキャプチャは (?<name>pattern) (or (?'name'pattern) )のように書きます。
後方参照時は \k<name> (or \k'name' ) と書くことができます。

/(?<number>\d+) and \k<number> is same number/ === '1 and 1 is same number' #=> true

名前付きキャプチャと前述の数字の単純なキャプチャは併用できません。”use name” と怒られます。

numbered backref/call is not allowed. (use name)

また、 Regexp#=~ を利用した場合、キャプチャした文字列はローカル変数に代入してくれたりします。
(正規表現側に #{} による文字列展開が含まれていない場合に限定)

/(?<name>\w+)/ =~ 'toyoda' #=> 0
name #=> 'toyoda'

部分式呼び出し

マッチした文字列ではなくて、マッチした正規表現自体を呼び出すことも可能です。

\g<1> でキャプチャした箇所の “正規表現” を呼び出せます。

# /\d+-\d+-\d+/ と同じ意味
/(\d+)-\g<1>-\g<1>/ === '123-456-789' #=> true

# 名前付きキャプチャも利用可能
/(?<num>\d+)-\g<num>-\g<num>/ === '123-456-789' #=> true

なんかもういろいろ出来すぎて、そろそろ覚えていられなくなってきました。
あと少しで終わりにしようと思います。

先読み、後読み

ある正規表現にマッチした位置より前(先読み)または後(後読み)にある特定の正規表現にマッチさせることができます。

肯定先読み(positive lookahead)

マッチさせたい文字列の後ろに、さらに特定の文字列が続く場合にだけマッチさせたい場合に使います。
わかりにくいと思うので例を示します。

肯定先読みは (?=pattern) と記述します。

# "ですかね" が続く直前の数字を取りたい
/\d+(?=ですかね)/.match('たぶん20ですかね') #=> #<MatchData "20">

否定先読み(negative lookahead)

肯定先読みの逆です。
マッチさせたい文字列の後ろに、特定の文字列が続かない場合にだけマッチさせられます。

否定先読みは (?!pattern) と記述します。

# "ですかね" が続かない直前の数字を取りたい
/\d+(?!ですかね)/.match('たぶん20ですかね') #=> #<MatchData "2">

直感的でない動作だと思った方もいるかもしれませんので、簡単に説明します。
/\d+(?!ですかね)/ が意味するところは “「ですかね」が続かない数字” です。

20という文字列のうち
0は後ろに “ですかね” が続くので否定条件に合致します。(マッチしません)
2は後ろに “0” という文字が続くので否定条件に合致しません。(マッチします)

肯定後読み(positive lookbehind)

肯定先読みは後に続く文字を指定しましたが、後読みは先に来る文字列を指定します。

肯定後読みは (?<=pattern) と記述します。

/(?<=答えは)\d+/.match('答えは3600円でした') #=> #<MatchData "3600">

否定後読み(negative lookbehind)

否定先読みと同じ要領で、先行する文字列を指定します。
指定した文字列に続かない文字列を条件にマッチします。

否定後読みは (?<!pattern) と記述します。

/(?<!答えは)\d+/.match('答えは3600円でした') #=> #<MatchData "600">

これも、否定先読みと同じ原理ですね。
“3” は否定されますが、それ以降の “600” は該当しないのでマッチします。

最後に

Rubyの勉強していたはずなのに、なぜか正規表現の勉強をしていたなんてことは良くある話で…

他にも色々ありますが今日はこの辺にしておこうと思います。
いやー正規表現は奥が深い。

興味のある方はRubyの公式リファレンスを見てみてもいいかもしれません。

タイトルとURLをコピーしました