連載
» 2014年06月26日 18時00分 公開

若手エンジニア/初心者のためのRuby 2.1入門(5):RubyのString/Regexpクラスによる強力な文字列操作/正規表現 (3/3)

[著:麻田優真、監修:山根剛司,株式会社アジャイルウェア]
前のページへ 1|2|3       

文字クラスの省略記法

 数字やアルファベットなど、基本的な文字クラスにはバックスラッシュから始まるメタ文字列を使えます。可読性が上がるので、これらの省略記法は好んで用いられます。以下に文字クラスの省略記法の一覧を示します。

メタ文字列 意味
\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]

 以下に10進数の数字の省略記法と繰り返しを組み合わせた例を示します。

[1] pry(main)> "2014/05/01".match(/\d{4}\/\d{2}\/\d{2}/)
=> #<MatchData "2014/05/01">
[2] pry(main)> "2014-05-01".match(/\d{4}\/\d{2}\/\d{2}/)
=> nil
[3] pry(main)> "2014/5/1".match(/\d{4}\/\d{2}\/\d{2}/)
=> nil

 これらの正規表現は、{4桁の数字}/{2桁の数字}/{2桁の数字}であるような部分文字列にマッチするものです。従って、[1]の文字列はマッチし、[2]と[3]の文字列はマッチしません。

パイプ記号(縦線)を用いた選択

 パイプ記号(縦線)を使うと、縦棒を中心とした左右の正規表現の、どちらかにマッチすれば全体としてマッチするような表現ができます。説明だけでは分かりにくいので、以下に例を示します。

[1] pry(main)> "teapot".match(/(tea|coffee)pot/)
=> #<MatchData "teapot" 1:"tea">
[2] pry(main)> "coffeepot".match(/(tea|coffee)pot/)
=> #<MatchData "coffeepot" 1:"coffee">
[3] pry(main)> "milkpot".match(/(tea|coffee)pot/)
=> nil

 例の正規表現は、teaもしくはcoffeeから始まり、potが続くような部分文字列にマッチします。そのため、[1]および[2]はマッチしますが、[3]はmilkから始まっているためマッチしません。

丸括弧を用いて1文字として扱う「グループ化」

 また、ここではさり気なく丸括弧を用いたグループ化というテクニックを使っています。括弧で囲まれた部分(先述の例では「(tea|coffee)」の部分)は、1文字として扱われ、繰り返しのメタ文字列を適用することもできます。以下のコードはその例となります。

[4] pry(main)> "abcd".match(/a(bc)+d/)
=> #<MatchData "abcd" 1:"bc">
[5] pry(main)> "abcd".match(/a(bc|xy)+d/)
=> #<MatchData "abcd" 1:"bc">
[6] pry(main)> "axyd".match(/a(bc|xy)+d/)
=> #<MatchData "axyd" 1:"xy">
[7] pry(main)> "abcbcd".match(/a(bc|xy)+d/)
=> #<MatchData "abcbcd" 1:"bc">
[8] pry(main)> "axyxyd".match(/a(bc|xy)+d/)
=> #<MatchData "axyxyd" 1:"xy">
[9] pry(main)> "axybcd".match(/a(bc|xy)+d/)
=> #<MatchData "axybcd" 1:"bc">

 +記号は1文字以上の繰り返しであったことを思い出してください。[4]〜[9]で用いられている正規表現は、aから始まり、bcかxyのいずれかが1回以上登場し、dで終わる部分文字列にマッチします。[9]のように、xyとbcが混ざった形で含まれていてもマッチしています。

文字列の先頭、末尾などを表現する「アンカー」

 アンカーとは、文字列の先頭、末尾などを表現するためのメタ文字列です。以下によく利用されるアンカーの一覧を示します。

メタ文字列 意味
^ 行頭
$ 行末
\A 文字列の先頭
\z 文字列の末尾
\Z 文字列の末尾(末尾が改行文字ならばその手前にマッチ)

 以下に行頭を表す「^」を用いた正規表現の例を示します。

[5] pry(main)> "Alice".match(/^Alice/)
=> #<MatchData "Alice">
[6] pry(main)> "**Alice**".match(/^Alice/)
=> nil

 [5]ではAliceという文字列が先頭から始まっているためマッチしますが、[6]ではアスタリスクが2個続いた後にAliceという文字列が存在するため、この場合はマッチしません。

 また、^と\Aの違いですが、改行を含む文字列を対象に正規表現を適用する場合に問題となります。^はあくまで複数行の文字列の行頭にマッチし、\Aは文字列の先頭にのみマッチします。つまり、^は文字列の先頭を含みますが、もう少し広い意味を持つ記号であるといえます。この関係性は$と\Zもしくは\zとの間でも成り立ちます。

メタ文字列のエスケープ

 ここまで、さまざまなメタ文字列を紹介しましたが、ときにはメタ文字列で使われている文字にマッチさせたい場合もあるでしょう。そのような場合は、バックスラッシュ記号に対象の記号を続けることで表現できます。「*」を正規表現内で使いたい場合は「\*」と入力すればよく、「+」「?」でも同様です。以下の例はエスケープを用いた例です。

[1] pry(main)> "!@*+?".match(/\*\+\?/)
=> #<MatchData "*+?">

String#sub/#gsubメソッドによる文字列置換は、正規表現で強力に

 正規表現を用いた置換は、String#subまたはString#gsubメソッドを使います。これらのメソッドには破壊的なバージョンもあり、それぞれString#sub!、String#gsub!となります。これらのメソッド引数には文字列を与えることも可能ですが、正規表現を与えることで、さらに強力な置換を行えます。

 以下に、文字列の置換を行ういくつかの例を示します。

[1] pry(main)> "Bob in Wonderland".sub(/^Bob/, "Alice")
=> "Alice in Wonderland"
[2] pry(main)> "This        is  an   uneven     space string".gsub(/\s+/, " ")
=> "This is an uneven space string"
[3] pry(main)> "This        is  an   uneven     space string".sub(/\s+/, " ")
=> "This is  an   uneven     space string"

 [1]は行頭のBobという文字列をAliceという文字列に置換するコードです。

 また、[2]および[3]は、1個以上の空白を見つけて1文字の空白に縮めるようなコードとなっています。[2]はString#gsubメソッドを用いており、返り値からも分かるように、文字列中のマッチする部分全てについて置換します。[3]はString#subメソッドを用いており、文字列中で最初にマッチした部分のみ置換しています。

 ちなみに、gsubの「g」は「global」という意味です。

正規表現で文字列から特定のパターンを抽出

 正規表現を使うと、文字列の中から特定のパターンを持つ部分を抽出することもできます。

「$1」「$2」……といった「特殊変数」で抽出する

[1] pry(main)> "2014/05/01".match(/(\d{4})\/(\d{2})\/(\d{2})/)
=> #<MatchData "2014/05/01" 1:"2014" 2:"05" 3:"01">

 年、月、日に当たる部分を丸括弧で囲んでいるところに注目してください。このようにグループ化した正規表現の一部分は、「$1」「$2」……といった「特殊変数」で抽出することができます。「$1」は1番目にマッチしたグループ、「$2」は2番目にマッチしたグループ……と続きます。

[2] pry(main)> $1
=> "2014"
[3] pry(main)> $2
=> "05"
[4] pry(main)> $3
=> "01"

グループに名前を付けて抽出する

 コードの可読性を上げるために、グループに名前を付けて抽出することもできます。以下はMatchDataオブジェクトを利用したパターンの抜き出し例です。

[5] pry(main)> match_data = "2014/05/01".match(/(?<year>\d{4})\/(?<month>\d{2})\/(?<day>\d{2})/)
=> #<MatchData "2014/05/01" year:"2014" month:"05" day:"01">
[6] pry(main)> match_data[:year]
=> "2014"
[7] pry(main)> match_data[:month]
=> "05"
[8] pry(main)> match_data[:day]
=> "01"

 [5]から[8]の例では、String#matchメソッドによって得られたMatchDataオブジェクトを使い、グループ化した部分にマッチした文字列を抜き出しています。

次回は、日付と数値を扱う組み込みクラス

 今回は、文字列を表すためのStringクラスと、文字列を操作する際に便利な正規表現を扱うRegexpクラスについて解説しました。次回は、引き続き組み込みクラスの解説です。日付を扱うためのDateTimeクラスや、数値そのものに関連したクラスを紹介します。お楽しみに!

著者プロフィール

麻田 優真(Rails技術者認定シルバー試験問題作成者)

イタリア、ローマ生まれ。中学生のころHSPに初めて触れ、プログラミングの楽しさを知る。オープンソースやハッカーカルチャーを好む。C#からRubyに転向したときに、動的型付け言語の柔軟性やメタプログラミングの魅力に感動し、Rubyとともにプログラマーとしての人生を歩む決意を固める。

現在は奈良先端科学技術大学院大学で学生として所属するかたわら、株式会社アジャイルウェアでプログラマーとして従事。Ruby on Railsによるコンシューマー向けのWebサービスの開発などに尽力している。

好きなメソッドは、define_method。

Twitter:@Mozamimy、ブログ:http://blog.quellencode.org/


監修者プロフィール

山根 剛司(Ruby業務開発歴7年)

兵庫県生まれ。1997年からベンチャー系のパッケージベンダーで10年間勤務。当時、使用していた言語はJavaとサーバーサイドJavaScript。

2007年よりITコンサル会社に転職し、Rubyと出会って衝撃を受ける。基幹システムをRuby on Railsで置き換えるプロジェクトに従事。それ以来Ruby一筋で、Ruby on Railsのプラグインやgemも開発。

2013年より、株式会社アジャイルウェアに所属。アジャイルな手法で、Ruby on Railsを使って企業向けシステムを構築する業務に従事。

Ruby関西所属。

Twitter:@spring_kuma、Facebook:山根 剛司


前のページへ 1|2|3       

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。