Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Rubyの正規表現(Regex)を徹底解説!基礎から実践テクニックまで

Rubyの正規表現(ruby regex)は、文字列の中から特定のパターンを見つけ出し、データを抽出してさらに処理を行うための強力なツールです。

正規表現の代表的なユースケースとしては、大きく分けて「バリデーション(検証)」と「パース(解析)」の2つがあります。

たとえば、メールアドレスを考えてみましょう。Rubyの正規表現を使えば、「有効なメールアドレスとはどのような形式か」を定義できます。つまり、プログラムが有効なメールアドレスと無効なものを自動的に判別できるようになるのです。

Rubyの正規表現(Regex)を徹底解説!基礎から実践テクニックまで

Rubyでは、正規表現を2つのスラッシュ(/)で囲んで定義します。これにより、他の構文と区別されます。最もシンプルな正規表現は、単語や1文字だけにマッチするものです。

例:

# 'like' という単語を探す
"Do you like cats?" =~ /like/

このコードは、マッチが成功した場合にその最初の出現位置(インデックス)を返し、見つからなければ nil を返します。位置情報が不要で「含まれているかどうか」だけを知りたい場合は、String#include? メソッドを使うとよいでしょう。

文字列が正規表現にマッチするかを確認するもう一つの方法が、match メソッドです。

if "Do you like cats?".match(/like/)
  puts "Match found!"
end

ここから先では、より高度なパターンの作り方を学びます。日付、電話番号、メールアドレス、URLなどをマッチ・キャプチャ・置換できるようになりましょう。

文字クラス(Character Classes)

文字クラスを使うと、マッチ対象となる文字の範囲やリストを定義できます。たとえば [aeiou] は、任意の母音1文字にマッチします。

例: 文字列に母音が含まれているかを判定する。

def contains_vowel(str)
  str =~ /[aeiou]/
end

contains_vowel("test") # => 1 を返す
contains_vowel("sky")  # => nil を返す

なお、この方法では文字の個数までは判定できません。個数の扱い方については後ほど説明します。

範囲(Ranges)

範囲を使うと、複数の文字や数字をいちいち書き並べることなく指定できます。つまり、[2-5][2345] と同じ意味になります。

覚えておくと便利な主な範囲は次のとおりです。

  • [0-9]:0〜9の任意の数字1文字
  • [a-z]:a〜zの任意の小文字アルファベット1文字
  • [^a-z]:否定の範囲(a〜z以外の文字)

例: この文字列に数字が含まれているか?

def contains_number(str)
  str =~ /[0-9]/
end

contains_number("The year is 2015")  # => 12 を返す
contains_number("The cat is black")  # => nil を返す

ポイント:=~ の戻り値は「マッチ位置のインデックス」または「nil」のどちらかです。

また、よく使う文字範囲には省略記法が用意されています。

  • \w[0-9a-zA-Z_] と同等(英数字とアンダースコア)
  • \d[0-9] と同等(数字)
  • \s空白文字にマッチ(タブ、半角スペース、改行など)

それぞれ否定形も存在します。

  • \W[0-9a-zA-Z_] 以外の文字
  • \D:数字以外の文字
  • \S:空白以外の文字

ドット(.)は改行以外のあらゆる1文字にマッチします。ドットそのものにマッチさせたい場合は、エスケープが必要です。

例: 特殊文字のエスケープ

# エスケープしない場合、'a' にもマッチしてしまう
"5a5".match(/\d.\d/)

# エスケープすれば、リテラルのドットのみにマッチ
"5a5".match(/\d\.\d/) # => nil
"5.5".match(/\d\.\d/) # => マッチ

量指定子(Modifiers)

ここまでは1文字ずつのマッチしかできませんでした。複数の文字にマッチさせるには、量指定子を使います。

量指定子 意味
+ 1回以上の繰り返し
* 0回以上の繰り返し
? 0回または1回
{3,5} 3回以上5回以下

これまで学んだ要素をすべて組み合わせれば、より複雑な正規表現を作れるようになります。

例: IPアドレスっぽい形式かどうかを判定する。

# 注意:999.999.999.999 のような不正なIPもマッチします。
# ここでは形式(フォーマット)だけをチェックしています。

def ip_address?(str)
  # !! で戻り値を真偽値に変換
  !!(str =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)
end

ip_address?("192.168.1.1")  # => true
ip_address?("0000.0000")    # => false

文字列の完全一致

厳密な一致を求める場合は、別の種類の指定子が必要になります。次の例を見てください。

# 「ちょうど4文字」かどうかを調べたいのに、
# 4文字以上あるためマッチしてしまう。
"Regex are cool".match /\w{4}/

# 代わりに「行頭」「行末」の指定子を使う
"Regex are cool".match /^\w{4}$/

# 今度はマッチしない。実際には .size で長さを調べれば済む話ですが、
# 挙動の違いをつかむには良い例です。

また、文字列全体の先頭・末尾に厳密にマッチさせたい場合(\n による各行ではなく)、^$ の代わりに \A\Z を使いましょう。

キャプチャグループ

キャプチャグループを使うと、マッチした一部を取り出して後から再利用できます。キャプチャしたい部分を丸括弧 () で囲みます。

例: ログファイルの解析

Line = Struct.new(:time, :type, :msg)
LOG_FORMAT = /(\d{2}:\d{2}) (\w+) (.*)/

def parse_line(line)
  line.match(LOG_FORMAT) { |m| Line.new(*m.captures) }
end

parse_line("12:41 INFO User has logged in.")
# 次のようなオブジェクトが生成される

この例では =~ の代わりに .match を使っています。

match メソッドは、マッチがあれば MatchData オブジェクトを、なければ nil を返します。MatchData クラスには便利なメソッドが多数用意されているので、公式ドキュメントを一度目を通しておくとよいでしょう。

単純に真偽値(true / false)だけが欲しい場合は、Ruby 2.4以降で利用可能な match? メソッドを使うのがおすすめです。MatchData オブジェクトを生成しないため、match よりも高速という利点もあります。

キャプチャしたデータには、.captures メソッドでアクセスできます。また、MatchData オブジェクトを配列のように扱うことも可能で、インデックス0にはマッチ全体が入り、それ以降のインデックスには各キャプチャグループが格納されます。

最初のキャプチャグループを取り出したい場合は、次のように書きます。

m = "John 31".match /\w+ (\d+)/

m[1]
# => "31"

さらに、キャプチャしないグループ(非キャプチャグループ)も使えます。これは式をまとめるだけで、パフォーマンス上の無駄がありません。また、名前付きグループを使うと、複雑な正規表現を読みやすくできます。

構文 説明
(?:...) 非キャプチャグループ
(?<foo>...) 名前付きグループ

例: 名前付きグループ

m = "David 30".match /(?<name>\w+) (?<age>\d+)/
m[:age]
# => "30"
m[:name]
# => "David"

名前付きグループを含むマッチでも MatchData オブジェクトが返され、名前をキーとして結果を読み取れます。

先読み・後読み(Look Ahead & Look Behind)

これはやや高度なテクニックで、正規表現エンジンによってはサポートされていないこともあります。幸い、Rubyの正規表現エンジンはこれに対応しているので、活用方法を見ていきましょう。

先読み・後読みを使うと、特定のパターンが直前または直後に存在するかを「覗き見」できます。

構文 説明
(?=pat) 肯定の先読み(patが直後にある)
(?<=pat) 肯定の後読み(patが直前にある)
(?!pat) 否定の先読み(patが直後にない)
(?<!pat) 否定の後読み(patが直前にない)

例: 少なくとも1文字の英字の直後に数字があるか?

def number_after_word?(str)
  !!(str =~ /(?<=\w) (\d+)/)
end

number_after_word?("Grade 99")

RubyのRegexpクラス

Rubyの正規表現は、すべて Regexp クラスのインスタンスです。通常このクラスを直接操作することはあまりありませんが、知っておいて損はありません。

puts /a/.class
# => Regexp

一つの活用法として、文字列から正規表現を生成できます。

regexp = Regexp.new("a")

別の生成方法として、%r 記法もあります。

regexp = %r{\w+}

正規表現のオプション

正規表現にオプションを設定すると、その振る舞いを変えられます。

オプション 説明
i 大文字・小文字を区別しない
m ドット(.)が改行にもマッチする
x 空白を無視する(可読性向上のための整形が可能)

オプションを使うには、正規表現の閉じスラッシュ / の後ろに文字を付け加えます。

例:

"abc".match?(/[A-Z]/i)

長い正規表現を見やすく整形する

複雑なRubyの正規表現は、読むのがかなり大変になりがちです。そこで役立つのが「x」修飾子です。これを使うと、正規表現を複数行に分割して書けます。さらに、正規表現内にコメントを書くことも可能です。

例:

LOG_FORMAT = %r{
  (\d{2}:\d{2}) # 時刻
  \s(\w+)       # イベント種別
  \s(.*)        # メッセージ
}x

実践編:正規表現を組み合わせて使う

正規表現は、Rubyの多くのメソッドと組み合わせて使えます。

  • .split
  • .scan
  • .gsub
  • その他多数…

例: .scan で文字列からすべての単語を取り出す

"this is some string".scan(/\w+/)
# => ["this", "is", "some", "string"]

例: 文字列からすべての数字を抽出する

"The year was 1492.".scan(/\d+/)
# => ["1492"]

例: 文字列内のすべての単語をキャピタライズ(先頭を大文字化)する

str = "lord of the rings"

str.gsub(/\w+/) { |w| w.capitalize }
# => "Lord Of The Rings"

例: メールアドレスの形式を検証する

email = "test@example.com"

!!email.match(/\A[\w.+-]+@\w+\.\w+\z/)

# => true

最後の例では !! を使って結果を真偽値(true / false)に変換しています。Ruby 2.4以降なら、同じことを自動的に行ってくれる match? メソッドを使うのもよいでしょう。こちらの方が高速です。

まとめ

正規表現は非常に強力ですが、ときに扱いが難しい場面もあります。そんなときは、rubular.com のようなツールを使うと、対話的にRubyの正規表現を組み立てられて便利です。Rubularには正規表現のチートシートも付属しており、とても役立ちます。

さあ、エディタを開いて、実際にコードを書いてみましょう。この記事が役に立ったら、ぜひ友人にもシェアしてください。より多くの人が学べるはずです。


  1. JavaScriptの正規表現(RegExp)とは?基本の使い方を実例付きで解説

    正規表現(Regular Expression)とは、検索パターンを表す文字列のことです。あらかじめ定義したパターンをもとに、テキストの中から特定の文字列や条件に一致する部分を効率的に検索したり、置換したりすることができます。 JavaScriptでは、スラッシュ「/」で囲んでパターンを記述する方法と、RegExpオブジェクトを使う方法の2通りで正規表現を作成できます。また、パターンには「g(グローバル検索:すべての一致を対象)」「i(大文字・小文字を区別しない)」「m(複数行モード)」などのフラグを組み合わせることで、より柔軟な検索が可能です。 ここでは、replace() メソッドと正規

  2. JavaScript正規表現の後読みアサーション(Lookbehind)の使い方を実例で解説

    後読みアサーション(Lookbehind)とは、あるパターンの直前に特定の文字列や記号が存在する場合にのみ、そのパターンをマッチさせることができるJavaScript正規表現の機能です。マッチ結果には直前のパターン自体は含まれず、あくまで「存在確認の条件」として扱われる点が大きな特徴です。 後読みアサーションの基本構文 後読みアサーションは (?<=パターン) の形式で記述します。例として (?<=\$)\d+ という正規表現を見てみましょう。 (?<=\$): 直前にドル記号($)が存在することを確認する \d+: 1桁以上の数字にマッチする この組み合わせにより、「$