hiyoko-programingの日記

プログラミングを勉強したてのひよっ子。   エンジニア目指して勉強中。

正規表現

正規表現とは

https://tech-master.s3.amazonaws.com/uploads/curriculums//ae32c309b0f5dfdd06a42ce2ae5ecbdc.png

登録フォームに電話番号を記入する欄がある。
Aさんはハイフンを付けて入力したが、Bさんはハイフン無しで入力をしている。そのままデータベースに保存すると、異なるフォーマットで保存されてしまい管理が大変。

フォーマットを統一させるために、入力された電話番号にハイフンが入っていたら必要に応じて処理をしてあげる。

https://tech-master.s3.amazonaws.com/uploads/curriculums//e25209bf673c18ba6c77aabb474e6679.png

このように、文字列に特定の文字が含まれているかを確認することや、特定の文字を取り除くなどの操作を行うための技術が正規表現である。

正規表現は様々な言語で同じようなコードで表すことができるように設計されている。そのため、ほとんどの記述をRubyJavaScriptなどの別言語間でも共有することができる。

正規表現の特徴

プログラミング中で正規表現を利用するケースは多くの場合、以下の3つ。

  • 文字列の一部分を置換する
  • 文字列が制約を満たしているか調べる
  • 文字列の一部分を抽出する

文字列の一部分を置換する

最初の例で紹介した電話番号のように、ハイフンを空文字に置換することで異なるフォーマットの文字列を同一フォーマットに変換することができる。

文字列が制約を満たしているか調べる

ユーザ登録の際に設定するパスワードにおいて、8文字以上という制約を付けたい場合にも正規表現を用いて実装することができる。

文字列の一部分を抽出する

入力されたメールアドレスからドメインの部分のみを抽出したい場合などに使用する。

これらの特徴は2つのメソッドのみで全て実装することができる。

正規表現を実現する2つのメソッド

Rubyにおいて正規表現を利用するために重要な2つのメソッド。

  • subメソッド
  • matchメソッド

subメソッド

1
2
3
4
5
irb(main):001:0> str = "りんごを食べる"
=> "りんごを食べる"

irb(main):002:0> str.sub(/りんご/,"みかん")
=> "みかんを食べる"

「りんごを食べる」という文字列が「みかんを食べる」という文字列に変換された。

 subメソッド

subメソッドは、文字列の指定した部分を別の文字列に置き換えるためのメソッド。第一引数に置き換えたい文字列を指定し、第2引数に変換後の文字列を指定する。
また、操作したい文字列は/で囲む。

また、この後のカリキュラムで学ぶ言語JavaScriptでは、subメソッドと同じ機能を持つメソッドとして、replaceメソッドがある。

 irbでsubメソッドを使う

まずはirbを起動。

ターミナル
1
2
$ irb
irb(main):001:0> #ここからRubyのコードを書いていきます。

「おにぎりを食べる」という文字列を「ラーメンを食べる」に変換する。

1
2
3
4
5
irb(main):001:0> str = "おにぎりを食べる"
=> "おにぎりを食べる"

irb(main):002:0> str.sub(/おにぎり/,"ラーメン")
=> "ラーメンを食べる"

matchメソッド

1
2
3
4
5
6
7
8
irb(main):001:0> str = "Hello, World"
=> "Hello, World"

irb(main):002:0> str.match(/Hello/)
=> #<MatchData "Hello">

irb(main):003:0> str.match(/Good/)
=> nil

上の例では「Hello World」という文字列に、「Hello」という文字列が含まれているか否かをチェックしている。

 matchメソッド

matchメソッドは引数に指定した文字列がレシーバの文字列に含まれているか否かをチェックするためのメソッド。含まれている場合は、指定した文字列がMatchDataオブジェクトの返り値で得られる。また、含まれていない場合は、返り値としてnilが得られる。

 MatchDataオブジェクト

マッチした文字列等はMatchDataオブジェクトで返される。MatchDataオブジェクトから文字列等を取り出す際は、以下の様に配列からデータを取り出す時と同様の形で取り出すことができる。

1
2
3
4
5
6
7
8
irb(main):001:0> str = "Hello, World"
=> "Hello, World"

irb(main):002:0> md = str.match(/Hello/)
=> #<MatchData "Hello">

irb(main):003:0> md[0]
=> "Hello"

 irbでmatchメソッドを使う

irbを使って、「good friend」という文字列に「good」という文字列が含まれているかをチェックしてみる。

1
2
3
4
5
6
7
8
irb(main):001:0> str = "good friend"
=> "good friend"

irb(main):002:0> md = str.match(/good/)
=> #<MatchData "good">

irb(main):003:0> md[0]
=> "good"

正規表現の様々なパターンを使う

正規表現の様々なパターンを使いより応用的な使用方法は、

以下の3つ。

  • 電話番号のハイフンを取り除く
  • パスワードに英数字8文字以上という制約を設定する
  • メールアドレスからドメインの部分のみ抽出する

電話番号のハイフンを取り除く

特定の文字を取り除く場合は、「特定の文字を空文字に置換する」と考える。置換するメソッドはsubメソッド。以下のコードがハイフンを取り除くための処理。しかしながら、subメソッドでは最初のハイフンしか置換されない。そこでgsubメソッドを用いることにする。

 電話番号からハイフンを取り除こう

1
2
3
4
5
6
7
8
9
irb(main):001:0> tel = '090-1234-5678'
=> "090-1234-5678"

irb(main):002:0> tel.sub(/-/,'')
=> "0901234-5678"
# 最初のハイフンしか置換されない

irb(main):003:0> tel.gsub(/-/,'')
=> "09012345678"

解説

ポイント

  • グローバルマッチのg

subの前にgが追加された、gsubメソッドが登場した。このgが意味するのは、グローバルマッチと呼ばれ、文字列内で指定した文字が複数含まれている場合、その全てを置換するという意味になる。gsubではなくsubを使用した場合、初めの1つだけ置換される。

パスワードに英数字8文字以上という制約を設定する

 パスワードに英数字8文字以上という制約を設定

今回はパスワードに「Hoge1234」という大文字小文字を区別した英字と数字を使用することを想定する。

matchメソッドを使用して以下のように記述。

1
2
3
4
5
irb(main):001:0> pass = 'Hoge1234'
=> "Hoge1234"

irb(main):002:0> pass.match(/[a-z\d]{8,}/i)
=> #<MatchData "Hoge1234">

解説

ポイント

  • [a-z]: 角括弧で囲まれた文字のいずれか 1 個にマッチ
  • \d: 数字にマッチ
  • {n, m}: 直前の文字が少なくとも n 回、多くても m 回出現するものにマッチ
  • i: 大文字・小文字を区別しない検索

[a-z] : 角括弧で囲まれた文字のいずれか 1 個にマッチ

[](角括弧)を使用することで角括弧で囲まれた文字のいずれか1つがマッチするかをチェックしている。また、-(ハイフン)を使用することで範囲を設定することができる。[a-z]はアルファベットのa からzまでのいずれかにマッチという意味になる。

a~cの英字を抽出
1
2
irb(main):001:0> 'dog'.match(/[a-c]/)
=> nil

「dog」という単語にはa ~ cのどの英字も含まれていないのでマッチしない。

\d : 数字にマッチ

このdは数字を表す。数字を表すdのような文字を特殊文字と呼ぶ。特殊文字を使用する場合は直前に\を記述するというルールが存在する。

今回は、\dは角括弧の内部にあるので、[a-z\d]は「英数字のいずれか1つにマッチ」という意味になる。

数字のみ抽出する
1
2
irb(main):001:0> 'I have 3 pens'.match(/\d/)
=> #<MatchData "3">

{n, m} : 直前の文字が少なくとも n 回、多くても m 回出現するものにマッチ

波括弧を使用することで文字数の制約を追加することができる。{8, }は、直前の文字が少なくとも8回出現するものにマッチという意味になる。波括弧の直前は[a-z\d]。つまり、英数字のいずれか1つが少なくとも8回出現するものにマッチという意味になる。

少なくとも4回、多くても6回出現するものにマッチ
1
2
3
4
5
irb(main):001:0> '12345678'.match(/\d{4,6}/)
=> #<MatchData "123456">

irb(main):002:0> '123'.match(/\d{4,6}/)
=> nil

i : 大文字・小文字を区別しない検索

最後にiオプションを加えることで大文字・小文字を区別せずに検索します。iオプションを付けない場合だと、[a-z]と小文字で記述しているので大文字にマッチしなくなってしまう。

大文字・小文字の区別
1
2
3
4
5
irb(main):003:0> 'Cat'.match(/cat/)
=> nil

irb(main):004:0> 'Cat'.match(/cat/i)
=> #<MatchData "Cat">

実践的な使用方法

パターンにマッチした場合はマッチした要素の配列が返される。またマッチしなかった場合はnullが返ってくるので、その性質を使用してif文で処理を分ける。

1
2
3
4
5
6
pass = 'Hoge1234'
if pass.match(/[a-z\d]{8,}/i)
  //パスワード設定の処理
else
   puts 'パスワードの形式が間違っています。'
end

メールアドレスからドメインの部分のみ抽出

hoge@tech-camp.com」というアドレスから「@tech-camp.com」の部分のみを取得したい場合。

 メールアドレスからドメインの部分のみ抽出

matchメソッドを使用することで抽出することができる。

1
2
3
4
5
irb(main):001:0> mail = 'hoge@tech-camp.com'
=> "hoge@tech-camp.com"

irb(main):002:0> mail.match(/@.+/)
=> #<MatchData "@tech-camp.com">

解説

ポイント

  • . : どの1 文字にもマッチ
  • + : 直前の文字の 1 回以上の繰り返しにマッチ

. どの1文字にもマッチ

ハイフンやピリオドなど含めた全ての英数字において、どの1文字にもマッチする。

1
2
irb(main):001:0> 'hoge'.match(/./)
=> #<MatchData "h">

+ 直前の文字の 1 回以上の繰り返しにマッチ

直前の文字が1回以上の繰り返しにマッチする。

1
2
irb(main):001:0> 'aaabb'.match(/a+/)
=> #<MatchData "aaa">

これらを踏まえると、.+は何かしらの文字が1回以上繰り返すものにマッチする。先頭に@を付けることで「@から始まり、何かしらの文字が1回以上繰り返すものにマッチ」という意味になる。

こうすることでメールアドレスからドメイン部分のみを抽出することができる。

正規表現のパターン表記一覧

今回登場した正規表現の表記方法をまとめた。

パターンの記述方法

パターン 意味
[a-z] 角括弧で囲まれた文字のいずれか 1 個にマッチ
\d 数字にマッチ
{n, m} 直前の文字が少なくとも n 回、多くても m 回出現するものにマッチ
. どの1 文字にもマッチ
+ 直前の文字の 1 回以上の繰り返しにマッチ