Help us understand the problem. What is going on with this article?

Ruby 正規表現の使い方

More than 3 years have passed since last update.

本記事について

本記事では、正規表現を用いて文字列操作が出来るようになることを目的にします。正規表現を使ったことがない人でも分かりやすいように書いたつもりです。

具体的には、Stringクラスのメソッドsub、gsub、matchメソッドが登場し、その中で正規表現を使っていきます。

なお、本記事の作成にあたって、
Rubyリファレンス(String)
RubyのString/Regexpクラスによる強力な文字列操作/正規表現
等を参考にしています。

それでは、早速みていきたいと思います。

正規表現を使う Regexpクラス

Rubyで正規表現を扱うためのクラスはRegexpクラスです。(Regexp = Regular Expression)
このRegexpクラスを用いながら、正規表現を行っていきます。
そもそも、正規表現を用いる理由は以下です。

  • ある文字列が特定のパターンを持っているか調べる
  • ある文字列から特定のパターンを持つ部分を抜き出す
  • ある文字列の特定のパターンを他の文字列で置き換える

引用:RubyのString/Regexpクラスによる強力な文字列操作/正規表現

つまり、強力な文字列操作の手法を手に入れることになります。

Regexpクラスとは?

"(ダブルクォーテーション)で文字を囲むと文字列オブジェクトを生成出来るのと同じように、/(スラッシュ)で囲むと正規表現オブジェクトを生成することが出来ます。

irb(main):001:0> "string".class
=> String
irb(main):002:0> /regexp/.class
=> Regexp

実際に正規表現で文字列操作をやってみる

実際にこのRegexpを登場させて文字列操作をやってみます。

まずは、String型のメソッドである、sub、gsubを紹介しながら説明していきます。

sub

str.sub(pattern, replacement)

正規表現のパターン(pattern)にマッチした最初の部分を文字列(replacement)に置換します。

例:

irb(main):001:0> str = "田中さん、こんにちは"
=> "田中さん、こんにちは"
irb(main):002:0> str.sub(/さん/, "君")
=> "田中君、こんにちは"

gsub

str.gsub(pattern, replacement)

正規表現のパターン(pattern)にマッチした全ての部分を文字列(replacement)に置換します。
subとの違いはパターンにマッチしたも全てを置換することです。subは最初だけです。

例:

irb(main):001:0> str = "咲いた、咲いた、チューリップが咲いた"
=> "咲いた、咲いた、チューリップが咲いた"
irb(main):002:0> str.gsub(/咲いた/, "咲かない")
=> "咲かない、咲かない、チューリップが咲かない"

パターンに一致したものを別の文字列で置換することが出来ましたね。

メタ文字の利用

subやgsubを使うと特定の文字列の置換ができること分かりました。ただし、正規表現の真髄はそこにあるのではありません。「メタ文字」を利用してより複雑なパターンを利用していきます。

繰り返しのメタ文字

まずは下記の表をさらっとながめて下さい。その後、具体例で説明しますのでそこで理解して頂ければ大丈夫です。

メタ文字列 意味
* 0回以上
+ 1回以上
? 0回か1回
{n} n回
{n,} n回以上
{,n} n回以下
{n,m} n回以上m回以下

具体的にみていきます。

irb(main):001:0> str = "abc,aabbcc,aaabbbccc"
=> "abc,aabbcc,aaabbbccc"
irb(main):002:0> str.gsub(/a+/, "z")
=> "zbc,zbbcc,zbbbccc"

パターンを意味する部分が/a+/となっています。メタ文字列の表を見てみると、「1回以上」を意味します。つまり、パターンはaが1回でも、2回でも、3回でも、、とにかくaが「1回以上」繰り返すというものがパターンとなります。つまり、ここでは「a」、「aa」、「aaa」のaの連続のパターンがすべて「z」に置換されました。

それでは次のようなコードで文字列はどのように変化するでしょうか。

irb(main):001:0> str = "ac,abc,abbc"
=> "ac,abc,abbc"
irb(main):002:0> str.gsub(/ab?c/, "z")

結果は下記のようになります。「?」は0回か1回繰り返すので「ac」と「abc」の部分がパターンにマッチするためこの部分が「z」となりました。

=> "z,z,abbc"

これでメタ文字列の使い方は分かってきたのではないでしょうか。今回、例では使わなかったものもぜひ試してみてください。

ワイルドカードのメタ文字

「.」は改行以外の任意の文字列を表すメタ文字です。
具体例で見てみましょう。

irb(main):001:0> str = "abc,adc,aec"
=> "abc,adc,aec"
irb(main):002:0> str.gsub(/a.c/, "z")
=> "z,z,z"

上記の例で分かるように「abc」、「adc」、「aec」の全てがパターンにマッチしています。このように「.」の部分は「b」でも「d」でも「e」でも1文字の文字列を表してくれます。

先ほどの繰り返しのメタ文字と組み合わせるとパターンの幅はさらに広がります。

irb(main):001:0> str = "aefefc"
=> "aefefc"
irb(main):002:0> str.gsub(/a.+c/, "z")
=> "z"

「+」は1文字以上を表しましたよね。「.+」によってaとcの間に「1文字以上の任意の文字列」が入るパターンを表すことが出来ました。

ここまで、繰り返しのメタ文字とワイルドカードの文字列をやりました。ここで、sub、gsubのようなStringクラスのメソッドであるmatchメソッドを紹介します。

match(Stringクラス メソッド)

str.match(pattern)

引数の正規表現をパターンマッチを行う。マッチしたときはMatchDataオブジェクトを返しマッチしなかったときはnilを返します。
つまり、パターンにマッチする文字列を取得するメソッドです。

例:

irb(main):001:0> str = "りんごが食べたい"
=> "りんごが食べたい"
irb(main):002:0> str = str.match(/.+が食べたい/)
=> #<MatchData "りんごが食べたい">
irb(main):003:0> str[0]
=> "りんごが食べたい"

このようにパターンにマッチしたものを抜き出せます。ここで注意してほしいことはMatchDataオブジェクトのstrに対しstr[0]で文字列を取り出せるということです。

str[0]があるということはstr[1]も出てきそうですね。しかし、今回の例ではstr[1]はnilになります。

それではここからmatchをさらに説明します。
例えば、「お腹がすいた、りんごが食べたい」、「お腹がすいた、みかんが食べたい」という文字列があったとして「りんご」、「みかん」の部分だけを取り出したいとします。
このとき登場するのが()キャプチャです。

実際に使い方を見てみましょう。

irb(main):001:0> str = "お腹がすいた、りんごが食べたい"
=> "お腹がすいた、りんごが食べたい"
irb(main):002:0> str = str.match(/お腹がすいた、(.+)が食べたい/)
=> #<MatchData "お腹がすいた、りんごが食べたい" 1:"りんご">
irb(main):003:0> str[1]
=> "りんご"
irb(main):004:0>

今まで通りに正規表現のパターンを作成し、抜き出したい部分を()でくくってあげるとその部分がなんとstr[1]で取得出来ます。

このようにmatchメソッドはパターンにマッチする部分をMatchDataオブジェクトの[0]で取得出来、()でキャプチャした部分を[1]で取得出来るのです。

複数の文字のうちいずれかの一致を確かめる文字クラス

いくつかの文字列の中から1文字にマッチさせたいとき[]を使います。

例:

irb(main):001:0> str = "pane"
=> "pane"
irb(main):002:0> str.match(/p[aiAI]ne/)
=> #<MatchData "pane">
irb(main):003:0> str = "pine"
=> "pine"
irb(main):004:0> str.match(/p[aiAI]ne/)
=> #<MatchData "pine">
irb(main):005:0> str = "pAne"
=> "pAne"
irb(main):006:0> str.match(/p[aiAI]ne/)
=> #<MatchData "pAne">
irb(main):007:0> str = "pune"
=> "pune"
irb(main):008:0> str.match(/p[aiAI]ne/)
=> nil

ここでは2文字目が[aiAI]のいずれかに一致すればパターンにマッチします。

否定文字クラス ^(ハット)

文字クラスの[の後に^を付けると先ほどとは逆にどの文字にもマッチしないというパターンを作成出来ます。

irb(main):001:0> str = "pAne"
=> "pAne"
irb(main):002:0> str.match(/p[^aiAI]ne/)
=> nil
irb(main):003:0> str = "pune"
=> "pune"
irb(main):004:0> str.match(/p[^aiAI]ne/)
=> => #<MatchData "pune">

範囲を用いた文字クラス

[a-z]、[A-Z]、[0-9]がそれぞれ小文字英字、大文字英字、1桁数字を表します。
[abcdefghijklmnopqrstuvwxyz]、[ABCDEFGHIJKLMNOPQRSTUVWXYZ]、[0123456789]のことを表していて、それをより簡単な形で指定出来ます。

irb(main):019:0> str = "apple"
=> "apple"
irb(main):020:0> str.match(/[a-z]pple/)
=> #<MatchData "apple">
irb(main):021:0> str = "Apple"
=> "Apple"
irb(main):022:0> str.match(/[A-Z]pple/)
=> #<MatchData "Apple">
irb(main):028:0> str = "1pple"
=> "1pple"
irb(main):029:0> str.match(/[0-9]pple/)
=> #<MatchData "1pple">

文字クラスの省略記法

可読性が上がるため省略記法が使われるようです。他人のコードに登場してくる可能性もあるので覚えておく必要はあります。また、自分でも積極的に使用していきましょう。

メタ文字列 意味
\w 単語を構成する文字 [a-zA-Z0-9_]
\W 単語を構成しない文字 [^a-zA-Z0-9_]
\s 空白文字 [ \t\r\n\f]
\S 空白でないような文字 [^ \t\r\n\f]
\d 10進数の数字 [0-9]
\D 10進数の数字でないような文字 [^0-9]
\h 16進数の数字 [0-9a-fA-F]
\H 16進数の数字でないような文字 [^0-9a-fA-F]
irb(main):001:0>  "2015/07/27".match(/\d{4}\/\d{2}\/\d{2}/)
=> #<MatchData "2015/07/27">

のように使用出来ます。{}の部分は繰り返しのメタ文字のところで出てきましたよね。

文字列の先頭、文末等を表現するための文字列

メタ文字列 意味
^ 行頭
$ 行末
\A 文字列の先頭
\z 文字列の末尾
\Z 文字列の末尾(末尾が改行文字ならばその手前にマッチ)
irb(main):001:0> "りんごみかん".match(/^りんご/)
=> #<MatchData "りんご">
irb(main):002:0> "みかんりんご".match(/^りんご/)
=> nil

前者の例では「りんご」が先頭にあるためマッチしましたが、後者の例だと「りんご」は先頭でないためnilが返ります。クラスの否定でも「^」が出てきたので混合しないように注意。

メタ文字列のエスケープ

メタ文字列に含まれる文字をパターンに含みたいときがあるかもしれません。そのときは\(バックスラッシュ)を使いましょう。

例:

irb(main):001:0> "aaa??bbb".match(/\?+/)
=> #<MatchData "??">

追記

ワイルドカードのメタ文字列の「.」も文字クラスであるようです。
@scivolaさん より

文字クラスというのは,〈何らの文字の集合の要素のいずれかに一致する〉という検索パターンです。
\d も文字クラスに含めるのであれば(含めるほうが自然だと思います),. は文字クラスです。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away