tech-dig

新しい技術が見つかるブログ

Ruby における正規表現の使い方

正規表現とは

文字列の集合を、「繰り返し」や「否定」など特殊な意味を持つ メタ文字 を使いながら、一つの文字列として表現したものです。

正規表現を用いることで、特定の文字列を含んでいるかどうかを判別したり、文字列から特定のパターンにマッチする部分を探し出すことができます。

Regexp クラス

Ruby では、正規表現を扱うクラスとして Regexp クラスが存在します。

下記のように Regexp オブジェクトを生成することができます。

pattern = Regexp.new("hoge")
# /hoge/
pattern = /hoge/
# /hoge/

pattern.class 
# Regexp < Object

また、%記法(パーセント記法) を用いることもできます。

pattern = %r{https?://}
# /https?:\/\//

%記法では、丸括弧 () 以外の括弧で囲うことができ、出現頻度の高いスラッシュ / などをエスケープしてくれるため、すっきりと書ける場合が多いです。

マッチしたかどうかを判定する

文字列があるパターンにマッチするかどうかを確認する場合、下記のように Regexp#=== または Regexp#=~ を用いることが多いです。

pattern = %r{[123]}

pattern === "abc123"
# true
pattern =~ "abc123"
# 3

Regexp#=== の場合は true または false、Regexp#=~ の場合はマッチした位置を返すので、期待される戻り値に合わせて使い分けましょう。

マッチした箇所を抜き出す

マッチした箇所を抜き出したい場合は、() によるグルーピング を行います。 こうすることで、正規表現内では \1 や \2 といった形、Ruby 内部では $1, $2 といった形で参照することができます。

実際にマッチさせた文字列を取り出す場合は、Regexp#match を使うと便利です。 パターンにマッチした場合は MatchData オブジェクトが返され、マッチした文字列に関する情報を得ることができます。

matched = %r{(\d+)}.match("出席番号10番")
# #<MatchData "10番" 1:"10">

matched[0] # マッチした文字列
# "10番"
matched[1] # マッチした正規表現の中で1つめの括弧にマッチした文字列
# "10"
matched.regexp # 使用した正規表現
# /(\d+)番/

マッチした箇所を置き換える

マッチした部分を別の文字列に置き換える場合、String#subString#gsub を使うことができます。 sub の場合は置換は1度だけ、gsub の場合はマッチした全ての箇所において置換を行います。

下記のように、第一引数に Regexp オブジェクトを、第二引数にマッチした箇所に置き換える文字列を渡します。

"abcabc".sub(/a/, "A")
# "Abcabc"

"abcabc".gsub(/a/, "A")
# "AbcAbc"

正規表現の記法

正規表現には、集合を効果的に表現するための様々な記法が用意されています。

ある文字集合を表す「文字クラス」

[ ] で文字を囲むことで、文字クラス を指定することができます。

例えば [123] は 1, 2, 3 のいずれか1文字にマッチし、ハイフン - を利用することで、[a-z] で a から z までのうちのいずれか、といったような範囲指定ができるようになります。

組み込みで用意されている文字クラスには下記のようなものがあり、それらを組み合わせて使うことで様々な正規表現を簡潔に書くことができます。

文字クラス 意味
\w 英数字 [0-1A-Za-z] にマッチする
\W 英数字 [0-1A-Za-z] 以外にマッチする
\s 空白文字 [\t\r\n\f] にマッチする
\S 空白文字 [\t\r\n\f] 以外にマッチする
\d 数字 [0-9] にマッチする
\D 数字 [0-9] 以外にマッチする

例) 空白を含む文字列 かどうかを判別したい場合

pattern = %r{\s}
# /\s/

pattern === "aaa bbb"
# true
pattern === "aaabbb"
# false

繰り返しを表す「量指定子」

「*」「+」などは 量指定子 と呼ばれており、直前のパターンの繰り返しを表現することができます。

例えば、改行を除く任意の1文字にマッチするドット . と、直前のパターンの1回以上の繰り返しを表す「+」を組み合わせて [.+] とすることで、任意の1文字以上のパターンを表現することができます。

量指定子 意味
* 直前の正規表現の0回以上の反復
+ 直前の正規表現の1回以上の反復
? 直前の正規表現の0回または1回の反復
{m, n} 直前の正規表現のm回~n回までの反復

例)11桁の電話番号 かどうかを判別したい場合

pattern = %r{\d{3}-\d{4}-\d{4}}
# /\d{3}-\d{4}-\d{4}/

pattern === "080-0000-0000"
# true
pattern === "05-0000-0000" 
# false

先頭と末尾の表現

文字列の先頭や末尾を表現するための記号にはいくつか種類があります。 それぞれ微妙な違いがあるため、対象の文字列に改行が含まれる場合は、特に注意が必要です。

記号 意味
^ 行頭、改行文字の直後にマッチする
$ 行末、改行文字の直前にマッチ
\A 文字列の先頭にマッチする
\Z 文字列の末尾にマッチするが、末尾が改行であれば改行の直前にマッチする
\z 文字列の末尾にマッチするが、末尾が改行であっても常に末尾にマッチする

例えば、文字列の途中に改行文字が存在する場合 は、下記のようになります。

## $ であれば行末と改行文字の直前の両方にマッチする
/hoge$/ =~ "hoge\nfuga"
# 0
/fuga$/ =~ "hoge\nfuga"
# 5

## \Zと \z は末尾(行末)にしかマッチしない
/hoge\Z/ =~ "hoge\nfuga"
# nil
/fuga\Z/ =~ "hoge\nfuga"
# 5

また、文字列の末尾に改行文字が存在する場合 は、下記のようになります。

## \Z は末尾に改行があった場合に直前にマッチするため、改行文字の前の fuga にマッチする
/fuga\Z/ =~ "hoge\nfuga\n"
# 5

## \z は常に末尾にマッチするため、改行文字そのものにマッチし、その直前の fuga にはマッチしない
/fuga\z/ =~ "hoge\nfuga\n"
# nil

先読みと後読み

「前後にパターンAがある場合」という条件のもとで特定のパターンBをマッチさせたいという場合に使われるのが 先読みと後読み です。

そこにさらに否定が加わることで、「〜がない場合」についても表現できるようになります。

記号 意味
(?=) 先読み
(?!) 否定の先読み
(?<!) 後読み
(?<!) 否定の後読み
(?#) コメントアウト

例を見てみましょう。
下記の例では、「数学1位 国語3位 英語5位」という文字列があった場合に、真ん中の国語の順位にマッチさせます。

str = "数学1位 国語3位 英語5位"

## 「国語」で後読み(抜き出す対象の手前のパターンを指定する)
%r{(?<=国語)(\d+)}.match(str)
# #<MatchData "3位" 1:"3">

## 「 英語」で先読み(抜き出す対象の後ろのパターンを指定する)
%r{(\d+)(?= 英語)}.match(str)
# #<MatchData "3位" 1:"3">

最後に否定の例を見ておきます。
都市名と天気の書かれた文字列から、天気が 雨 ではない場合のみマッチさせます。

%r{(.+):(?!)}.match("京都:雨")
# nil
%r{(.+):(?!)}.match("東京:晴れ")
# #<MatchData "東京:" 1:"東京">
%r{(.+):(?!)}.match("名古屋:曇り")
# #<MatchData "名古屋:" 1:"名古屋">