正規表現
初めまして!技術部サーバーチームのダリエンと申します。正規表現は役に立つ知識なので、勉強してみました。
では、正規表現について調べた知識をシェアしたいと思います。
こちらは Tech Kayac Advent Calendar 2017 の25日目の記事になります。
イントロダクション
定義
- 検索パターンを象る文字列
- 文字列の集合を一つの文字列で表現する(ja.wikipedia.org)
- 英語:Regular Expression(s) / Regex
何のために使いますか?
- ウェブブラウザーエクステンションやIDEやコードなどで:
- 文字をそのまま検索 (⌘/Ctrl+Fと全く同じ)
- パターンの一致
⌘/Ctrl+Fは簡単でしょう?
そうです。しかし、パータン一致は? 下のテキストを検討してください:
JPY152 JPY40501 USD501 IDR1261 JPY999
上のテキストで、3桁JPYしか絞り込みたくない時、⌘/Ctrl+Fでできませんね。
正規表現の(?<=JPY)\d{3}$
で、152と999を取得することができます。
さらに、プログラムの中に⌘/Ctrl+F
を実行することができませんね。正規表現ならできます。
結局、正規表現を使えるようになるためにはこのようなパターンがわからないといけない:
^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$
難しそうですが、できたら本当に役に立ちますから、勉強甲斐があると思います!
例えば、上のパターンで、どんな浮動小数点数でも検索できます。便利ですね。
なので、正規表現を勉強しましょう!
量指定子
定義
正規表現と文字列が合致しないといけないという個数。
種類
{N,N}
: 範囲の量指定子 (range quantifier)
例:a{2,4}
~aa
とかaaa
とかaaaa
とか*
: 0以上の量指定子
例:(ab)c*
~ab
,abc
,abcc
,abccc
, …+
:1以上の量指定子
例:(ab)c+
~abc
,abcc
,abccc
, …?
: 0または1の量指定子
例:(ab)c?
~ab
またはabc
注意:量指定子は、量指定子の演算子の左の「文字一個・グループ一つ」だけに影響があります。
量指定子の大切な概念:最長一致
一般的に、量指定子は最長一致という挙動を持っています。それで、できるだけ一番多い結果を戻してみます。
例:abcccccc
は、abc*
で合致されると、abcccccc
を戻します。(ab
,abc
, abcc
, abccc
, … じゃありません)。
この案件に、*
と言う量指定子は 0 から6まで ’c’ を戻せるんですが、最長一致に処理しますから、一番多い結果(6)を戻します。
最短一致
できるだけ一番少ない結果を戻してみます。
例:abcccccc
は、abc*?
で合致されると、ab
を戻します。(abc
, abcc
, abccc
, …, abcccccc
じゃありません)。
この案件に、*?
と言う量指定子は 0 から6まで ’c’ を戻せるんですが、ものぐさ的に処理しますから、一番少ない結果(0)を戻します。
最短一致を合致しみる量指定子シンタックス:
{パターン量指定子}?
例:abc*?
;abc+?
;abc??
;abc{2,5}?
グループ
後方参照グループ (Capture Group)
括弧に入れられる正規表現は後方参照グループと言われます。
後方参照グループは二つのことに使います: - 綺麗な正規表現を書けるように(色々なパターンがグループに分けられた正規表現は見やすい) - backreferences(後方参照)で呼ばれるようになります。
例:(Tarzan*)
がある場合は、\1
はTarzan*
という意味があります。
解説:(Tarzan*)
は後方参照グループ、\1
は後方参照です。
(Tarzan*)
は第一のグループなので、そのグループの参照のために、\1
で書きます。
後方参照 (Backreference)
後方参照グループを参照するため正規表現です。
シンタックス:\{集合グループ数}
(\1
, \2
, \3
, …)
例:HelloBelleHelloBelleImGaston
は、適当な正規表現で表現:(Hello)(Belle)\1\2ImGaston
です。
解説:Hello
は第一の集合グループ、Belle
は第二の集合グループなので、\1
はHello
を参照して、\2
はBelle
を参照します。
名前をつけるグループ (Named Capturing Group)
名前をつけるグループは数で参照しなくて、名前で参照します。それで、グループは名前をつけなければなりません。
RubyやPerl (SublimeTextの正規表現エンジン)でこのシンタックスを使うことができます:
グループ:(?<名前>パターン)
参照:\k<名前>
使い方は普通の集合グループと全く同じです。
非後方参照グループ (Non-Capturing Group)
普通のグループですが、参照されることができないというグループ。
例:(?:Tarzan*)
はどこにいるか\1
→ エラー
解説:(?:Tarzan*)
は後方参照グループじゃないので、第一集合グループはまだ存在していません。それで、\1
は存在していない 後方参照グループを参照してみますから、エラーになります。
例:(?:Tarzan*)
と(Belle)
がある場合は、どこかに\1
があったら、その\1
はBelle
を参照します。
解説:(?:Tarzan*)
は 非後方参照グループなので、(Belle)
は第一 後方参照グループです。
アトミックグループの導入:バックトラッキングの概念 (Backtracking)
正規表現は、パターンを合致する処理にバックトラキンッグという処理を使います。
バックトラッキングとは:失敗された合致する処理の対応のため挙動です。
量指定子と関係がある大切な概念。
量指定子があるregexが失敗した時、その量指定子で合致した文字は一個除いて、全ての合致する処理を繰り返します。
最短一致量指定子の場合は、一個除かなくて、一個追加します。
例:abccdd
は ab(.+)dd
で検索されるとします。
- regexの
ab
は文字列のab
を合致しました。 - regexの
.
は最長一致にccdd
を合致しました。 - 全ての文字列の文字が合致されたので、regexに残る
dd
はなにも合致できないので、regexは失敗でした。 - バックトラック:
.
は全ての残っている文字を合致するのはなくて、一個少ない文字を一致します(ccdd
からccd
になります)。 (1)&(2)を繰り返した後、文字列にまだ合致しいなかった文字は:d
- regexに残る
dd
は文字列の残っている文字を合致しみて、regexの第一のd
を合致したけど、最後のd
がまだ合致しませんでした。まだ失敗でした。 - バックトラック:
.
はもう一回一個少ない文字を一致します(ccd
からcc
になった) (1)&(2)を繰り返した後:文字列にまだ合致しいない文字は:dd
- regexに残る
dd
は文字列の残っている文字を合致しみます。 今回、全ての文字列に残っている文字(dd
)が合致したので、regexは成功でした(abccddを返します)。
独占的量指定子 (Possessive Quantifier)
バックトラッキングじゃない量指定子です。
シンタックス:{パターン量指定子}+
ー 例:abc*+
;abc++
;abc?+
;abc{2,5}+
アトミックグループ (Atomic Group)
普通な後方参照グループと同様ですが、
Atomicグループを使うと、失敗する時にBacktrackをしません。つまり、合致する処理の時にグループが固めます。
シンタックス:(?>{パータン})
例:
バックトラッキングの例の復習:abccdd
は正規表現で検索されるとしますが、
ab(.+)dd
で検索される代わりに、ab(>.+)dd
で検索されるとします。
1. regexのab
は文字列のab
を合致しました。
2. regexの.
は最長一致にccdd
を合致しました。
3. 全ての文字列の文字が合致されたので、regexに残るdd
はなにも合致できないので、regexは失敗でした。
4. (>.+)
はアトミックグループなので、正規表現の.
の部分がバックトラッキングされません。ですから、合致処理は完了です。regexは失敗でした。
条件 (Conditional)
If...else
のような挙動を持っている正規表現です。
シンタックス:(?(A)B|C)
基本的には、A
は後方参照。A
が参照されるグループが合致した場合は、B
に続きます。合致しなかった場合は、C
に続きます。
例:
FOObar FOObaz foobaz
は、(FOO)?(?(1)bar|baz)
で検索される場合は、戻した文字列は:FOObar
とbaz
です。
解説:
第一の文字列 (FOObar
) の合致する処理:FOO
が合致したので、bar
に続けました。それで、FOObar
を戻します。
第一の文字列 (FOObaz
) の合致する処理:FOO
が合致したので、baz
に続くのは間違えます。それで、なにも戻しませんでした。
第一の文字列 (foobaz
) の合致する処理:FOO
は合致しなかったので、baz
に続けました。それで、baz
を戻します。
オア (OR)
またはのロジック。(a|b)
の意味は、a
とかb
とかもOKです。だけど、両方ab
のはダメです。
例:
gray grey graey
は:gr(a|e)y
で合致する場合は:gray
とgrey
を戻します。
特別な文字
下述文字は特別な意味を持っています。ですから、regexに入れると文字通りに扱われません。
\d
: 一桁の数字
\w
: 文字(アルファベット、数字、Underscore)
\s
: ホワイトスペース (スペース、横/縦タブ、新しいライン、キャリアッジ・リターン、等)
.
(dot) : \n以外、任意の文字
\t
: 横タブ
\h
: スペース
\v
: 縦タブ
\r
: キャリアッジ・リターン
\n
: 新しいライン(Windows以外、キャリアッジ・リターンも含めています)
普通な .
(dot)を使いたい時、エスケープ(\.
)が必須です。
\d
, \w
, \s
の否定:\D
, \W
, \S
例:
\D
の意味は数字以外(アルファベットとか、ホワイトスペースとか)
\W
の意味はアルファベット以外(ホワイトスペースとか)
文字クラス (Character Class)
定義
いくつかある文字から一つ文字しか合わせない条件を作りたい時、文字クラスを使います。
シンタックス:[文字]
例:gr[ae]y
ー gray
とgrey
が戻せます(graey
は戻さない)
メタ文字 (Meta Character)
文字クラスの中では、]
, \
, ^
, -
以外は文字通りに扱われます。それで、後方参照グループとか量指定子とかは意味がなくなります。
[
, ]
, -
, \
とかはメタ文字と呼ばれます。これらをクラスに入れたい時、エスケープしないといけません(\[
, \]
, \-
, \\
になります)。
メタ文字:^
(キャレット ー Caret)
^
は、[]
の中に意味が切り替えます。[^{文字}]
の意味は:文字の否定。
例:
gr[^ae]y
ー gray
とgrey
が戻せない(aとeが断れたので)。ですが、他の文字が戻せるようになります。grby
, grcy
, …
^
は文字通りに扱われたい時に、後ろに置かないといけません。例:gr[ae^]y
メタ文字:-(ハイフン ー Hyphen)
-
は範囲の意味を持っています。
例:[A-Za-z0-9]
の意味は A から Z まで、 a
から z
まで、0
から 9
までです。それで、abcdefghijklmnopqrstuvwxyz0123456789
を書くのは必要じゃなくなります。
ハイフンで文字クラスの減算も実装できます。
文字クラスの減算:[{クラス}-[{減算}]]
。
例:[a-z-[aiueo]]
= [b-df-hj-np-tv-z]
= 子音を戻します。
量指定子で文字クラスを繰り返す
[…]+
は、合致した文字を繰り返さなくて、すべての字クラスを繰り返す。それで、[0-9]+
は繰り返す数しか表示されなくて、なんでも数字が表示されます。
- 解決は:
([…])\1+
- 例:
[0-9]{5}
の意味は:[0-9][0-9][0-9][0-9][0-9]
([0-9])\1{4}
の意味は:
もし、[0-9]
の結果は5
だったら、regexは(5)\1{4}
になりますから、regexの意味は55555
です。 - 解説:文字クラスの結果はグループに入れて、グループの中は
\1
で参照されて、最後はその後方参照が+
で繰り返します。
アンカーと境界
アンカー:定義
regexエンジンの位置をアサートするのに使います・regexのポインタの現在の位置はどこにあるかをチェックします。
アンカーでチェックできる位置:
- 文字列の冒頭:^
, \A
- 文字列の終了:$
, \z
さらに、(?m)
という修飾語がある場合は、^
と$
の意味の変更があります:
- ラインの冒頭:^
- ラインの終了:$
アンカーの種類
^
(キャレット):文字列の冒頭アンカー(?m)
を使うと、ライン(文字列じゃない)の冒頭になる$
:文字列の終わりアンカー*(?m)
を使うと、ライン(文字列じゃない)の終了になる\A
:文字列の開始。^
との違い:\A
は(?m)
に、(?m)
を使うと意味が変えられない(^
が変わります)
例:Apple\nApricot
:\AA
を使うとApple
しか戻しません(Apricot
は新しい文字列じゃない)
(?m)^A
を使うとApple
とApricot
を戻す(Apricot
は別のラインにあるので)\z
:$
と同様ですがラインの終わりじゃなくて、文字列の終わり(\Aと^の関係と同じ)
境界
\b
:ワード境界
\b
の前とか後とか、ASCII/Unicode文字とか数字とかunderscoreとかあるかどうかをチェックします - 合致する条件は文字がありません。
\b
の否定:\B
です。
例:
\bcat\b
は
bobcat
を合致する場合:なし(cat
の前b
があります)catfish
を合致する場合:なし(cat
の後f
があります)his cat eats
を合致する場合:catを戻します(cat
の前と後は文字じゃなくて、ホワイトスペースです)
修飾語
定義・種類
Regexの挙動を変えるのに使います。
種類
(?i)
- 大文字と小文字の区別じゃないのに使います(?s)
- DOTALLモード (.
は\r\n
も合致します)(?m)
- 複数行モード (^
と$
の挙動の変更に使います)(?x)
- FreeSpacingモード (Regexを読みやすいために、regexを書く時スペースと新しいラインが使えます)修飾語の書き方
複数修飾語は一緒に含めていることができます。
例:(?ismx)
どこにも書けます。修飾語の効果は修飾語の後に開始です。
例:
caseSensitive(?ix)whOcAreSaBoutCases asLonG asImAlive
説明:
(?ix)
の前に、文字大小はチェックして、すべてのregexは同じラインにかかないといけないんですが、(?ix)
の後には文字大小を無視して、複数行regexも書けます。
- 修飾語の効果を制止したい時、
(?-{文字})
を打ちます。
例:
caseSensitive(?i)whOcAreSaBoutCases(?-i)nowicare
エスケープ文字
説明
特別な意味がある文字は普通な文字としたい時、その文字をエスケープするのが必要です。
例:.
を打つ時、普通な(dot)じゃなくて、「\n
以外、任意の文字」という意味が持っていますから、普通な(dot)としたい時、エスケープが必要です。
シンタックス:
\{特別な文字}
例:\.
;\\
;\+
;\*
;\?
など- 文のエスケープシーケンス:
\Q{エスケープされたい文}\E
例:\Q 3*4+5 = 6 \E
→ 量指定子に扱われなくて、普通の数学演算子に扱われます。
上記のシンタックスを使うと、全て文字は文字通りに扱われます。
先読み・後読み言明 (LOOKAROUNDS)
定義
「先読み・後読み言明」は、アンカーと境界とそっくりですが、「先読み・後読み言明」はregexポインターの位置を一致する代わりに、文字を一致してみます。
例:
^
文字は、文字
の前
に「ライン・文字列の開始」を一致してみる
(?<={パターン}){文字}
は、{文字}
の前
に「パターン」を一致してみる
幅0のアサーション・Zero-Length Assertion
アンカーと境界と同じですが、「先読み・後読み言明」にはっきり見えます。
Zero-Length Assertion:
Regexは文字を合致してみるが、合致する場合は、合致した文字を集合しなくて、放置されます。
普通の処理は、Regexは文字を合致した後、regexのポインタは次の文字列の文字に進みますが、アンカーと「先読み・後読み言明」には、合致した後、ポインタが進みません。それで、次の処理はまだその文字を合致してみます。
例:
q(?=u)it
ー quitを合致しない。
解説:
q
の後ろはu
ということは正しいですから、後読みは成功でした。
ですが、regexのポインタが進まないので、次の処理はregexのi
が文字列のu
を合致してみます。合致しないので、regexが失敗です。quit
を戻したい場合は、正しいパターンは q(?=u)uit
です。
種類
基本的には、
肯定先読みアサーション :
文字(?=パターン)
文字の後ろ、パターンを合致してみる。合致する場合は正しい否定先読みアサーション :
文字(?!パターン)
文字の後ろ、パターンを合致してみる。合致しない場合は正しい肯定後読みアサーション :
(?<=パターン)文字
文字の前、パターンを合致してみる。合致する場合は正しい否定後読みアサーション:
(?<!パターン)文字
文字の前、パターンを合致してみる。合致しない場合は正しい
例
下のテキストを検討してください:
123JPY #(1) 4567JPY #(2) 890IDR #(3)
\d{3}(?=JPY)
で合致する場合は、(1)の123
と (2)の567
を戻します。
\d+(?!JPY)
で合致する場合は、(1)の12
、(2)の456
、と (3)の890
を戻します。
\d{3}(?=JPY)
の説明:
123JPY
->123
-
\d{3}
は123
を合致します。 -
123
の後ろにJPY
があるかどうか(?=JPY)
で検証します。123
の後ろにJPY
が実際にあるので、検証は成功します。 - ですが、後読みは合致された文字列を集合しませんから、
JPY
が集合されません。 - それで、合致の結果は以前に集合した
123
です。
-
4567JPY
->567
-
\d{3}
は456
を合致します。 456
の後ろにJPY
があるかどうか(?=JPY)
で検証します。456
の後ろにJPY
がないので、検証は失敗します。それで、合致処理が失敗します。- バックトラッキングで、
\d{3}
の合致を繰り返します。- 今回、
456
を合致しなくて、567
を合致します。 567
の後ろにJPY
があるかどうか(?=JPY)
で検証します。567
の後ろにJPY
があるので、検証は成功します。- ですが、後読みは合致された文字列を集合しませんから、
JPY
が集合されません。
- 今回、
- それで、合致の結果は以前に集合した
567
です。
2番目の例に567
を戻したくない場合は: \d{3}(?=JPY)
の冒頭に^
を追加します。- 文の中に検索する場合は、
\d{3}(?=JPY)
の前後に\b
を追加します。
-
\d+(?!JPY)
の説明:
890IDR
->890
-
\d+
は890
を合致します。 -
890
の後ろにJPY
がないかどうか(?!JPY)
で検証します。890
の後ろにJPY
がないので、検証は成功します。 - ですが、後読みは合致された文字列を集合しませんから、
JPY
が集合されません。 - それで、合致の結果は以前に集合した
890
です。
-
4567JPY
->456
-
\d+
は4567
を合致します。 4567
の後ろにJPY
がないかどうか(?!JPY)
で検証します。4567
の後ろにJPY
があるので、検証は失敗します。それで、合致処理が失敗します。- バックトラッキングで、
\d+
の合致を繰り返します。- 今回、
4567
を合致しなくて、456
を合致します。 456
の後ろにJPY
があるかどうか(?!JPY)
で検証します。456
の後ろにJPY
がないので、検証は成功します。- ですが、後読みは合致された文字列を集合しませんから、
7JPY
が集合されません。
- 今回、
- それで、合致の結果は以前に集合した
456
です。
-
123JPY
-> 上と全く同じ解説で、12
を戻します。
私の期待は\d+(?!JPY)
で合致すると、4567JPY
と123JPY
から何も戻したくないんですが、期待がミスしてしまいました。
このような挙動があるので、先読みを使う時、注意しなければなりません。
最後に
正規表現を勉強する前に、大きな一覧に多い項目の編集が必要の時に、パターンがわかるのに、正規表現がわからないので一個ずつ編集しなければなりません。それはすごく面倒でした。
正規表現の基本的な特性を生かせるようになったので、そのようなタスクは一つコマンドだけでやり遂げることができます。本当に便利です!
さらに、正規表現でテキスト検証(正規表現を使わなければすごく難しくなります)を実装することができるようになりした。
12/1から25日間にわたって更新してきた Tech Kayac Advent Calendar 2017 もこれが最後の記事となります。お楽しみいただけましたでしょうか。 来年もまたよろしくお願いします! メリークリスマス!🎄