175
237

More than 3 years have passed since last update.

正規表現が苦手な人、お集まりください。

Last updated at Posted at 2021-05-09

はじめに

あなたは正規表現は得意ですか??
「はい、得意です!」と答える人は、そんなに多くないのかな?と思います。
自分は特に苦手で嫌いでした。。
このままではダメだと思い、正規表現の壁に立ち向かいました。
自分がもがいて手に入れた情報で、皆さんに少しでもいい情報を届けたれたら幸いです!!

※一番下に、正規表現に用いる記号一覧を載せてあるので参考にしてください!

正規表現とは

文字列とのパターンマッチを行うための小さな言語のことです。
正規表現を用いることで、ある文字列があるパターンに該当する文字列を含んでいるかを確認したり、パターンに該当する部分を検索できます。

パターンマッチ(Ruby)

Regexp#=~

正規表現にマッチした位置を返すときは、=~を使用する

# マッチしたら、その位置を返す
/[0-9]/ =~ 'ruby6' #=> 4

# マッチしない場合はnilを返す
/[0-9]/ =~ 'ruby' #=> nil

Regexp#===

あるパターンにマッチするかを調べるには、===を使用する

# 0〜9の数字を含んでいるか
/[0-9]/ === 'ruby' #=> false
/[0-9]/ === 'ruby6' #=> true

Regexp#match

マッチすればMatchDataオブジェクトを返し、しなければ「nilを返す

str = 'ruby6'

if matched = /[0-9]/.match(str)
  p matched #=> "#<MatchData \"6"\>"
end

MatchDataから得られる情報は以下の通りです。

MatchData 変数 得られる値
MatchData#[0] $& 直前にマッチした文字列
MatchData#[1] $1 直前にマッチした正規表現の中の、最初の括弧にマッチした文字列
MatchData#pre_match $` 直前にマッチした文字列より前の文字列
MatchData#post_match $' 直前にマッチした文字列より後の文字列

式展開時の注意点

正規表現で、式展開を用いる際は必要に応じてエスケープしましょう!

# エスケープする必要のある文字列
part = Regexp.escape('(incomplete)')
/[^.]+#{part}\.txt/ #=> /[^.]+\(incomplete\)\.txt/ # 括弧がエスケープされる

文字クラス

[]で囲むことで文字クラスの指定が可能です。

[a-z] #=> aからzまでの文字

[\w] #=> 英数字a(A)~z(Z)、0~9

量指定子

直前パターンの繰り返しをします。

# 郵便番号の正規表現の場合
post_num = /\d{3}-\d{4}/
post_num === '123-4567' #=> true

先頭と末尾

先頭には\A、末尾には\zとして表現する。

# 携帯電話番号の正規表現の場合
phone_num = /\A\d{3}-\d{4}-\d{4}\z/
phone_num === '012-3456-7890' #=> true

指定位置からの正規表現

指定された文字列 str に対して 位置 pos から自身が表す正規表現によるマッチ

/R.../.match?("Ruby")    #=> true
/R.../.match?("Ruby", 1) #=> false
$&                       #=> nil

グルーピングと後方参照

()によりグルーピングをすることが可能。
後で、\1\2のように参照することを後方参照と言います。

# 基本的な後方参照
/(text)\ to\ \1/ === 'text to text' #=> true

# ラベル付きグルーピングでの後方参照
/(?<num>[0-9+])[a-c\-]+\k<num>/ === '123-abc-123' #=> true

部分式呼び出し

後方参照と似ているが、グルーピングを再利用できる部分式呼び出しという参照方法がある。

# 部分式呼び出しの「\g<1>」は最初のグルーピングの式「[0-9+]」で置換される
/([0-9+])-\g<1>-\g<1>/ === '012-3456-7890' #=> true

先読みと後読み

先読みとは、(?<=sample)は「"sample"という文字列と一致する箇所の直後」を表します。
また、後読みはその逆で直前になります。
また、否定文に関しては、文字通り肯定先(後)読みと逆の動作をします。

# 肯定先読み
'sampleApp'.scan(/sample(?=App)/) #=> ["sample"]

# 肯定後読み
'sampleApp'.scan(/(?<=sample)App/) #=> ["App"]

バックトラックの抑止

(?>)で正規表現を囲むことで、そこにマッチした部分でバックトラックを抑止できる。

/(?>\w+)[0-9]/ === 'ruby6' #=> false

オプションの指定

以下の表を用いて、オプションを指定することでパターンマッチの挙動を変化させることが出来ます。

オプション 意味
i 大文字、小文字の区別をしない
o 正規表現リテラルが最初に評価された際に、一度だけ式展開を行う
x 正規表現中の「#」を無視して、コメントとみなす
m 「.」に改行もマッチさせる、複数行モード
u 正規表現をUTF-8として解釈
s 正規表現をShift-JISとして解釈
e 正規表現をEUC-JPとして解釈
n 正規表現をASXIIとして解釈
# オプション無し
%w(foo bar).map { |str| /#{str}/ } #=> [/foo/, /bar/]

# オプション有り
%w(foo bar).map { |str| /#{str}/o } #=> [/foo/, /foo/]

終わりに

正規表現は必ずと言っていいほど、最初はハマります。。
暗号のように見えてきて気が狂いそうですが、この記事を機に慣れていってくれることを願っています!

宣伝

想像以上の反響をいただき、ありがとうございます!!
よかったら、自分のTwitterでも、有益な情報を発信していますので、興味があればフォローしていただけると嬉しいです!

参考(正規表現に用いる記号一覧)

1 2
^ 行頭、改行文字の直後にマッチ
$ 行末、改行文字の直前にマッチ
\A 文字列の先頭にマッチ
\Z 文字列の末尾にマッチ、改行文字なら改行の直前
\z 文字列の末尾にマッチ、改行でも常に末尾にマッチ
\w 英数字[0-9A-Za-z_]にマッチ
\W 英数字[0-9A-Za-z_]以外にマッチ
\s 空白文字[\t\r\n\f]にマッチ
\S 空白文字[\t\r\n\f]以外にマッチ
\d 数字[0-9]にマッチ
\D 数字[0-9]以外にマッチ
\h 16進数に使われる文字[0-9A-Fa-f]にマッチ
\b []の外では単語の境界、中ではバックスペースにマッチ
\B 単語の境界以外にマッチ
\G 直前にマッチした箇所の直後にマッチ
. 改行を除く任意の1文字にマッチ
[] 文字クラスの指定
[0-9] すべての数字にマッチ
[^0-9] すべての数字以外にマッチ
* 直前の正規表現の0回以上の反復、できるだけ長くマッチ
+ 直前の正規表現の1回以上の反復、できるだけ長くマッチ
*? 直前の正規表現の0回以上の反復、より短くマッチ
+? 直前の正規表現の1回以上の反復、より短くマッチ
? 直前の正規表現の0〜1回の反復
?? 直前の正規表現の0〜1回の反復、より短くマッチ
{m} 直前の正規表現のm回の反復
{m}? 直前の正規表現のm回の反復、より短くマッチ
{m,} 直前の正規表現のm回以上の反復
{m,}? 直前の正規表現のm回以上の反復、より短くマッチ
{m,n} 直前の正規表現のm〜n回の反復
{m,n}? 直前の正規表現のm〜n回の反復、より短くマッチ
() グループ化
\n 後方参照、正規表現のn番目のグループにマッチした文字列にマッチ
\k< label name > 後方参照、ラベルでマッチ
(?:) グループ化、後から参照出来ない
(?< label name >) ラベル付きグループ化
(?#) コメント
(?=) 先読み
(?!) 否定先読み
(?<=) 後読み
(?<!) 否定後読み
(?>) バックトラック抑止
(?ixm-ixm) オプションのon/off
(?ixm-ixm:) 括弧内のオプションのon/off
a | b aもしくはbにマッチ
175
237
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
175
237