Aikの技術的な進捗部屋

技術的な進捗とか成果とかを細々と投稿するブログです。時々雑記も。

正規表現記法についてまとめてみた その2

その1(正規表現の概要)はこちら

はじめに

痒いところに手が届きすぎる記法「正規表現」。
前回の記事では、正規表現の概要についてまとめてみました。

今回の記事では、正規表現の「特殊文字」に関してまとめようと思います。
めっちゃくっそ長いので覚悟してください(ブーメラン)。

((あれから筆が進み進み、あまり時間をおかずに次の記事が出来上がっちゃったのは小躍りするくらい嬉しいですわ…。

正規表現 特殊文字について

さて、色々前口上が長くなりましたが…いい加減参りましょう。
ちなみに、この特殊文字を使いこなせるという事は、正規表現をマスターするのと同義だと思いますよ!

※今回の参考記事はこちらです。
正規表現 - JavaScript | MDN
正規表現あれこれ

* 直前の文字の0回以上の繰り返し

*は直前の文字の0回以上の繰り返しを表します。

例:

正規表現パターン: 「ab*」の場合
"abc" -> ab
"ccccabbbcccc" -> abbb

※最短マッチの場合
"aab" -> a: bはあってもなくても良いので先頭のaだけでもマッチする

+ 直前の文字の1回以上の繰り返し

+は直前の文字の1回以上の繰り返しを表します。

例:

正規表現パターン: 「ab+」の場合
"aab" -> ab
"aaaabbbbcccc" -> abbbb

? 直前の文字があってもなくても良い

?は直前の文字があってもなくても良いことを表します。

例:

正規表現パターン: 「ab?」の場合
"abc" -> ab
"aab" -> a/ab: bは必要ないので先頭のaのみと、後続のabどちらにもマッチ
"ccccabbbcccc" -> ab: 最長マッチの規則から、aのみには出来ないので続くbもマッチ

. 任意の1文字

.は改行文字以外の任意の1文字を表します。

例:

正規表現パターン: 「a.b」の場合
"abc" -> null: aとbの間に文字がないのでマッチしない
"aab" -> aab
"aaaabbbbcccc" -> aab
"acccb" -> null: aとbの間に複数文字あるのでマッチしない

^ 文字列の先頭

^は文字列の先頭にマッチします。
この文字の動作は、複数行検索がある場合とない場合とで差異があります。

  • 複数行検索有り: 文字列中に改行がある場合、改行後の行頭にもマッチ
  • 複数行検索無し: 文字列全体の先頭にのみマッチ

エディタの検索機能に正規表現を使う場合は、大抵複数行検索有りが前提のはずなので、前者だけを頭に入れておくだけで十分かと。

なお、こちらは例を省きます。見たままだもんね…。

$ 文字列の末尾

$は文字列の末尾にマッチします。
この文字の動作も、数行検索がある場合とない場合とで差異があります。

  • 複数行検索有り: 文字列中に改行がある場合、改行前の行末にもマッチ
  • 複数行検索無し: 文字列全体の終末にのみマッチ

こちらに関しても例を省きます。見たままですからね。

エスケープ

*の様な特殊文字自体を、正規表現中で使う場合は「(バックスラッシュ)」を付けてエスケープしましょう。

例:

正規表現パターン: 「*」の場合
"*" -> null

正規表現パターン: 「\*」の場合
"*" -> *

| どちらか片方にマッチ(OR/論理和)

dragon|dungeonの様に2つの文字列を|で区切ると、「どちらか片方にマッチする」という表現になります。

例:

正規表現パターン: 「a|b」の場合
"aaabbb" -> 全てのaとbにマッチ

直前の文字の繰り返し回数の指定

{3}の様に、{}内に数値を指定すると「直前の文字の繰り返し回数の指定」という表現になります。

また、{3, 4}の様に、{}内に2つの数値を,(カンマ)で区切って指定すると、範囲指定ができます。
なお、2つ目の数字を省略すると「1つ目の数字個以上の繰り返し」という意味になります。

例:

正規表現パターン: 「a{2}」の場合
"aaaaa" -> aa

正規表現パターン: 「a{2,4}」の場合
"aaaaa" -> aaaa

正規表現パターン: 「a{2,}」の場合
"aaaaa" -> aaaaa

最短マッチに切り替え

*, +, ?, {}等の量指定子の直後に?を置くと、マッチの仕方がデフォルトの最長マッチから最短マッチに変わります。

例:

正規表現パターン: 「a{2,}?」の場合
"aaaaa" -> aa

なお、最長マッチは「貪欲マッチ(Greedy)」、最短マッチは「非貪欲マッチ(Non-Greedy)」とも呼ばれます。

文字集合

文字集合は、指定された文字の「いずれか1つ」とマッチします。
[]内に指定する文字を書く事で定義できます。

また、文字集合では[開始文字-終了文字]と書けば、範囲を指定する事ができます。
例えば[0-9]は数字全て、[a-z]は小文字アルファベット全てにマッチします。

また、複数の範囲を指定する場合はそれらを続けて記述するだけでOKです。
範囲指定と単一文字指定を組み合わせることもできます。
数字全てとアルファベット全てにマッチする正規表現は、[0-9a-zA-Z]と書くことができます。

なお、文字集合内では* + ? .などの特殊記号は使用できないので、これらの文字はエスケープしなくても大丈夫です。
その代わり、先に出てきた範囲指定時に扱う文字-に関してはエスケープが必要です。

また、文字集合の先頭に ^をつけた場合、文字集合の否定となります。
文字集合の否定は、「指定された文字以外の文字」にマッチする正規表現となります。
例えば[^0-9]とすれば数字以外の文字にマッチする正規表現になります。

グループ化、キャプチャ

()で文字列を括ると、その部分をグループ化する事ができます。
この機能を使えば、例えば以下の様な事が可能です。

// 表したい文字
「dragon」と「dungeon」

// 正規表現で
「d(rag|unge)on」

なお、正規表現の使用環境にもよりますが、()で文字列を括るとマッチした文字列を記憶することができます。
この機能はキャプチャと呼ばれているそうです。

また、記憶した文字列を再度正規表現内で扱うことも可能です。
例えば\1, \2とする事で、前方で記憶した文字列をマッチさせることが出来ます。
※(\1が最初のキャプチャに対応します。)

なお、キャプチャは1つの正規表現の中に複数含めることができます。
例えば(\d+)-(\d+)-(\d+)は、-で区切られた電話番号の各けたの番号を取得できる正規表現パターンとなります。

ちなみにキャプチャを行いたくない場合は、(?:)と書けばキャプチャを回避できます。

長々と書きましたが、例を見てもらえるとわかりやすくなるかもですね。
例:

正規表現パターン: 「([0-9]+)」として
"aa11aa22" -> 11と22にマッチ
$1 = "11", $2 = "22"

正規表現パターン「([a-z]+).*\1」-> 最初にマッチした単語が後でもう一度出現する文字列とマッチする
"apple111apple" -> マッチする
"busy111cat" ->マッチしない

また、キャプチャに名前をつけた「名前付きキャプチャ」というのもあります。(そのまんまですな…)
こちらはJavaJavaScriptでは対応していないそうですが、外部ライブラリを使って対応させることも可能だそうです。
※参考記事: 正規表現で名前付きキャプチャを使う

先読みと後読み

正規表現には、「先読み」と「後読み」というものがあります。
具体的にどんなものかは、下記の参考記事にわかりやすく書かれていたので…そちらを引用させていただきます。
正規表現あれこれ

先読み・後読み 正規表現 意味
肯定先読み(positive look forward) (?=abc) 前方を見てabcがある位置にマッチ
否定先読み(negative look forward) (?!abc) 前方を見てabcがない位置にマッチ
肯定後読み(positive look behind) (?<=abc) 後方を見てabcがある位置にマッチ
否定後読み(negative look behind) (?<!abc) 後方を見てabcがない位置にマッチ

…実は、先読みとか後読みとか知った当時、「これって何かメリットあるのだろうか」と筆者は思いました。
具体的にどう使ったらいいか、分からなかったんですよね…。

ただ、「こうして正規表現として採用されてるなら何かメリットがあるはず」と思い調べてみると、活用法を載っけてくれている記事が!
すっごく「なるほど」と思ったので、その活用法をこちらの記事から少し引用させていただきます…。

マッチする文字列には含まれないが、その前後の文字列によって除外したい条件がある場合、先読みや後読みが使えます。
私の正規表現コレクションの中からいくつか引用いたします。

a =~ /(?<!弱)小会社/
-> Rubex: (?<!弱)小会社 — このサイトで正規表現の動作を確認できます

これは「小会社」という誤用にマッチし、「弱小会社」という単語にはマッチしないパターンです。
マッチするかどうかだけが知りたいのであれば、[^弱]小会社という基本的な正規表現だけで十分です。
しかし私の場合は誤用の部分だけをハイライトしたいので、「小会社」という部分だけにマッチするよう、こうした先読み・後読みを多用します。

a =~ /就業(?=住宅)/
-> Rubex: 就業(?=住宅)

これは「集合住宅」のつもりが誤って「就業住宅」になっていることをチェックするパターンです。
もちろん、用途によっては単に就業住宅とすれば足りることもありますが、私は「就業」だけをハイライトするために肯定先読みを使っています。

※参考記事: 正規表現の先読み・後読み(look ahead、look behind)を活用しよう

これはなかなか便利そうですね…見方が変わりました。
他にもいろんな記事を見ていると、「正規表現で様々な表現をするには、先読みと後読みのマスターは必須」と書かれた記事もありました。そ、そこまで…。
何だか正規表現の沼の深さを、改めて垣間見た気もします…。

その他の表現

その他にも数字や英文字を扱う特殊文字や、改行や空白などの特殊文字と様々あります。
こちらに関しては表でまとめることにしました。

※なお、特にこの辺りは、環境によっては正規表現として扱えない文字も入ってるかもです…悪しからず!

正規表現 説明 同一表現 備考
文字系統
\w 単語を構成する文字にマッチ [a-zA-Z0-9_] wはwordの頭文字
\W 単語を構成する文字以外の文字にマッチ [^a-z^A-Z^0-9^_]
\d 数字(0~9)にマッチ [0-9] dはdigitの頭文字
\D 数字以外の文字にマッチ [0-9]
空白文字系統
\s 空白文字(半角スペース/全角スペース/改行/タブ文字)にマッチ sはspaceの頭文字
\S 空白文字以外の文字にマッチ
[\b] 後退文字(バックスペース)
\t タブ文字
\v 垂直タブ 垂直タブとは(Wikipedia)
改行系統
\n 改行文字
\f 用紙送り(改ページ)文字
\r 改行文字(キャリッジリターン) キャリッジリターンとは(Wikipedia)
その他特殊文字
\a 警告文字 エスケープ文字とは(Wikipedia)
\e エスケープ文字 ベル文字とは(Wikipedia)
\b 単語境界(\wと\Wの境目) マッチした単語の区切りの長さは0
\B 単語境界以外
\0 NULL文字 \0後に0〜7の数字が続くと8進数のエスケープシーケンスとなる
\xhh hh(2桁の16進数)コードからなる文字列 コード指定で文字を探したい時に
\uhhhh hhhh(4桁の16進数)コードからなる文字列 コード指定で文字を探したい時に
\u{hhhh} Unicode値hhhh(16進数)からなる文字列 JS環境下のみ、uフラグがセットされた時のみ
\cX 文字列中の制御文字 XにはA〜Zのうち1文字が入る、ex./\cM/-> control-M

次回記事では

次回記事では、JavaScript正規表現を扱う方法について見ていきます。
((どうしてJSかというと、筆者が(多分おそらく)今後一番お世話になる言語だからです。

ではでは|д゚)

余談 - 正規表現を扱う際に参考になるWebサービス・ツール

ここからは、正規表現を扱う際に参考になるWebサービスやツールをまとめました。

regular expressions 101

regex101.com

こちらは、任意の正規表現パターンのパフォーマンスを見る事ができます。

PHP, JavaScript, Python, Go言語下での動作も見る事ができます。
また、上記言語に加えてC#, java, Ruby, Rust, Perl言語でソースコード生成も可能です。

さらに、リファレンスも付属しており、完成した正規表現パターンをInputと一緒にURL共有することもできます。
とても高機能な正規表現Webサービスと言えそうですね。

Rubular

rubular.com

こちらは、Ruby言語下限定ですが任意の正規表現パターンのパフォーマンスを見る事ができます。

簡単なリファレンスも付属しており、こちらも完成した正規表現パターンをInputと一緒にURL共有することもできます。
先ほど紹介したものよりも機能は劣りますが、シンプルで非常に使いやすく感じます。

簡単な正規表現チェックならこちらで行っちゃっても問題ないかと。

regex-railroad-diagram(Atomパッケージ)

atom.io

こちらはWebサービス…ではなく、Atomエディタのパッケージの1つです。

こちらを使うと、何と「正規表現をグラフィカルに表現」してくれるのです…下記の様な感じに!

"Regex Railroad Diagramで図示された正規表現図"
こいつぁやばいぜ!

正規表現は可読性に欠けすぎた表現ですが、こうしてグラフィカルにしてくれると分かりやすいですよね…!
もしエディタでAtomを使われている方は、是非是非導入してみてください|д゚*)オススメ