はじめての正規表現とベストプラクティス#1: 基本となる8つの正規表現


こんにちは、hachi8833です。
BPS社内勉強会で発表したスライドを元に、正規表現を学ぶときに最初に押さえておきたいことをまとめました。Rubyを主に使っていますが、汎用的な記述を心がけています。

正規表現に関する専門書籍やWebサイトはいろいろありますが、正規表現には唯一の正解がなく、さまざまな書き方が可能で、かつそうした資料では機能の説明がびっしり網羅されているので、資料で目についたメタ文字を手当たり次第に使っていると、効率の悪い書き方をしてしまったり、書き方を見つけられなかったり、見つけにくいバグを埋め込んでしまったりすることがしばしばあります。

通常ならば「正規表現とは何か」から説明するところですが、第1回では正規表現のメタキャラクタ(メタ文字)を基本的かつ有用な順に並べ、順に学ぶことで自然とベストプラクティスを実現できるようにしてみました。正規表現そのものについては次回#2で解説します。#3以降、順次高度な機能に進みます。

第1回は以下の8つに絞りました。少なくともこれらはどの正規表現環境(ライブラリ)でも利用できます。

  1. ? (2つの使い方)
  2. +
  3. \
  4. .
  5. []
  6. ()
  7. |
  8. {}

サンプルの正規表現はRuby風に/ /で囲みます(囲みの/ /は正規表現そのものではありませんのでご注意ください)。

参考: 正規表現をインタラクティブに試せるサイト

正規表現をインタラクティブに試すのに便利なWebサイトがいくつかありますので、こうしたサイトで正規表現を試しながら読み進めることをおすすめします。本シリーズでは原則としてrubular.comを使います。

  • Ruby向け: rubular.com — ただしRuby 2.1.5相当

なお、こういったサイトに限りませんが、顧客データや個人情報などを流し込まないよう注意しましょう。

正規表現はじめの一歩: ?

?
直前の1文字が0個または1個であることを表すメタ文字

?は単純ですが、実はこれだけでも相当使いでがあります。私は心の中でこっそり「ありやなしやの?」と呼んでいます。

ご注意いただきたいのは、?は何かの文字の代用ではなく、「?直前の文字を修飾している」ことです。いわゆるワイルドカードの?とは機能が異なります(ワイルドカードとの違いについては別途解説します)。

?の典型的な使い方は、カタカナの長音「ー」がある場合とない場合の両方にマッチさせたいときです。

例: /コンピューター?/とすると、「コンピューター」と「コンピュータ」のどちらにもマッチします(Rubular)。

正規表現はじめの二歩: +

  • +: 直前の1文字が「1個以上」繰り返されることを表すメタ文字

+も有用性の高いメタ文字です。「1個以上」なので、指定の文字がいくつ連続していてもマッチします。これも直前の文字を修飾しています。

なお、先ほどの?やこの+は、難しく言うと「量指定子(quantifier: 量化子とも)」に分類されます。要するに、直前の文字がいくつあるかを指定するものです。

例: /ずうー+っと/とすると、「ずうっと」「ずうーーっと」「ずうーーーっと」…などにマッチしますが、長音のない「ずうっと」にはマッチしません(Rubular)。

正規表現はじめの三歩: \

\
直後のメタ文字を通常の文字として扱う(エスケープ)

\はバックスラッシュと呼ばれ、正規表現ではバックスラッシュ直後のメタ文字の機能をキャンセルして通常の文字として扱うのに使われます。この操作を「エスケープ」と呼びます。なお、通常のスラッシュは/です。

バックスラッシュは、正規表現以外でもプログラミング言語などの文字のエスケープによく用いられます。

例: 「1+1」という文字列にマッチさせたいときは、正規表現の中で\+とすると、+がメタ文字ではなく通常の+として扱われます(Rubular)。

バックスラッシュ\そのものをエスケープしたい場合は、\\のように二重に表記します(Rubular)。

正規表現はじめの四歩: .

.
任意の1文字を表すメタ文字

.を単体で使うことは、意外にもそれほどありません。

そのかわり、さっきの+を組み合わせて.+とすることで、「1文字以上の任意の長さの文字列」を表すのに非常によく使われます。.+はぜひ覚えておきたい定番の表現です。

例: /rails_.+\.rb/とすれば、「rails_migration.rb」や「rails_batch.rb」など「rails_なんちゃら.rb」にマッチします(Rubular)。

この場合、_のすぐ後ろが\.rbになっている「rails_.rb」にはマッチしません。

なお\.の部分は、任意の一文字でない「本来のピリオド文字.」を表すために先ほどのバックスラッシュ\を直前に置いてエスケープしています。こうしないと、たとえば意図しない「rails_view.erb」にもマッチしてしまいます(Rubular)。

注意: +量指定子は「最長一致」

+のような「n個以上の繰り返し」量指定子は、正規表現ではデフォルトで最大限の長さで一致する(最長一致)ので注意が必要です(ただし正規表現エンジンによっては異なるかもしれません)。言い換えると、行けるところまでめいいっぱいマッチします。

例: 「豚が豚をぶったのでぶたれた豚がぶったその豚をぶった豚」という文字列に/豚.+ぶった/という正規表現を適用すると、以下のようにほぼめいいっぱいマッチしてしまいます(Rubular)。

「豚をぶった」「豚がぶった」も含めて最長マッチしているので、マッチだけを調べていると気づきにくいバグになりがちです。このようにハイライトしたり置き換えようとしたときにバグが顕在化します。

?のもうひとつの機能: 最短一致

最長一致では困る場合、多くの正規表現では量指定子(ここでは+)の直後に?を付けて+?とすることで最短一致を指定できます。この?は先ほどのありやなしやの?と意味の異なる、もうひとつの機能です。

なお、秀丸エディタでは最短一致を「ものぐさ一致」というユニークな言葉で表しています。

例: 「豚が豚をぶったのでぶたれた豚がぶったその豚をぶった豚」という文字列に/豚.+?ぶった/という正規表現を適用すると、最短一致で3箇所にマッチします(Rubular)。

「豚が豚をぶった」「豚がぶった」「豚をぶった」の3箇所で最短マッチしています。

なお、「豚が豚をぶった」にはマッチさせたくないのであれば、.+ではなく、もっと精密な正規表現を書く必要があります。.+は有用なイディオムですが、このように意図しないマッチを呼び込む可能性があることを頭に置いておきましょう。

正規表現はじめの五歩: 文字セット[]

[]
この記号で囲まれた文字(文字セット)のどれか1文字を表すメタ文字

文字セット[]も正規表現の非常に有用なメタ文字です。後ろに量指定子を置いて[0123456789]+のように使うことがよくあります。

文字セット[]全体は1文字とみなされることにご注意ください。

例: 「豚が豚をぶったのでぶたれた豚がぶったその豚をぶった豚」という文字列に/豚[がを]ぶった/という正規表現を適用すると、3箇所にマッチします(Rubular)。

「豚をぶった」「豚がぶった」「豚をぶった」の3箇所でマッチしています。先ほどの「豚が豚をぶった」のようなマッチを排除できましたね。

文字セット[]は、数字やアルファベットに限定した文字とマッチさせるときにもよく使われます。文字セット[]の直後に+などの量指定子を置くことで、マッチの長さを任意にできます。

例: /id_[0123456789]+/とすることで、「id_」の直後に数字が1文字以上続く文字列にマッチします(Rubular)。

実は文字セット[]の中は少し特殊な世界になっていますが、今後説明する予定なのでまだ気にしなくてもよいです。詳しくは正規表現: 文字クラス [ ] 内でエスケープしなくてもよい記号をご覧ください。

文字セット[]の注意

意外に間違えられやすいのですが、文字セット[]の中に書く文字の順序はマッチに関係ありません。

例: ぐ[りら]とすると「ぐり」か「ぐら」にマッチします(Rubular)。

これはぐ[りら]と書いても、ぐ[らり]と書いても同じです。

正規表現はじめの六歩: ()

()
文字列をグループ化するメタ文字

()にはいくつかの機能がありますが、ここでご紹介するのは文字列をグループ化する機能です(他の機能は今後ご紹介します)。難しく言うと「グループ化構成体(grouping construct)」です。

先の文字セット[]では中の文字に順序がありませんが、グループ化()の場合は文字の順序が保たれます()の直後に量指定子を置くことも、()の中でメタ文字を使うこともできます。

例: 「とうきょうとっきょきょかきょくきょくちょう きょうきゅうきょきょかきゃっか」に/(きょ)+/を適用した場合です(Rubular)。

「きょ」「きょきょ」にマッチしているのがわかります。

グループ化()も単体で使うことはあまりなく、次の|とよく併用されます。

正規表現はじめの七歩: |

|
複数のパターンを列挙するメタ文字

|は、プログラミングにおける「OR演算(論理和)」(Rubyでは||演算子)によく似ています。難しく言うと「代替構成体(alternation construct)」です。

|は、先ほどのグループ化()と組み合わせて(dog|cat|horse|cow)のようにパターンを列挙してその中のいずれかにマッチさせるのに使うことがよくあります。この場合、「dog」「cat」「horse」「cow」のいずれかにマッチします。各文字列の長さは異なっていても構いません。

例: (東京|神奈川|鹿児島)特許事務所という正規表現の場合です(Rubular)。

|は正規表現の中で処理の優先順位が低いのが特徴です。()に含まれない|は最後に解釈されます。

例: /東京|神奈川/とすると、「東京」または「神奈川」にマッチします(Rubular)。

|も有用なメタ文字です。複雑な正規表現でも|で上手に分割すると書きやすさ読みやすさが向上することもあります(Rubular)。無理して正規表現を一発で決めようとするより、|で分割できるかどうかを検討しましょう。

注: 上の例では今回解説しなかった正規表現((?<=))が使われています。

|の注意

|は有用ですが、以下のような点を見落としがちなのでご注意ください。

(||)+を追加する場合は注意

たとえば/(引き|抜き)にくい/とすると、「引きにくい」「抜きにくい」にはマッチするが「引き抜きにくい」にはマッチしません(Rubular

こういう場合、/(引き|抜き)+にくい/とすれば「引き抜きにくい」にもマッチしますが、今度は「引き引きにくい」や「抜き抜きにくい」にもマッチしてしまいます!(Rubular)。現実の日本語にはまず出現しませんが、欲しくないマッチであることには変わりありません。

これは、たとえばパターンをフレーズにして|で全体を並列する方法で回避できます(Rubular)。

「引き引きにくい」や「抜き抜きにくい」に部分マッチしているのを排除したい場合については、今後扱います。

|を余分に付けてしまう事故

()の中に|で文字列を列挙するときに、つい末尾に余分な|を置いてしまいがちです。なまじ意図しているマッチは正常に動いてしまうので案外見落とされやすいバグです。

例: /(A|B|C)/と書くつもりが/(A|B|C|)/になっていた(Rubular

マッチさせるつもりのなかった「さいたま特許事務所」にまで部分マッチしてしまいました。

さらに、|の列挙が()で囲まれていないとより深刻です(Rubular)。

上は「文字ではない部分」、つまり文字と文字の間にまでマッチしてしまいました。実は直前の「さいたま特許事務所」も、「さいたま」と「特許事務所」の間がマッチに含まれているのです。

正規表現はじめの八歩: `{ }

{N}
直前の1文字をN回繰り返すことを表す(Rubular
{N,}
直前の1文字をN回以上繰り返すことを表す(Rubular
{N, M}
直前の1文字をN回以上M回以下繰り返すことを表す(Rubular

{ }も量指定子の一種で、回数を数値で指定できるのが特徴です。通常の文字はもちろん、文字セット[]()などを修飾して回数を指定することもできます。

{ }も有用な表現です。+だと上限がありませんが、{ }だと上限を指定できるので効率が落ちにくく+よりも安心感があるので、回数を限定できるのであれば+よりも{}を積極的に使いましょう。

なお、上の例には「N回以下」がありませんが、{,N}という表現は私が知る限りではなぜかRubyでしか使えません

しかし{N, M}のパターンを応用して{1, 6}{0, 2}のように開始を1やゼロにすれば「N回以下」を表現できるので実用上は問題ありません(Rubular)。


今回は正規表現の基本的な8つのメタ文字と利用法をご紹介しました。次回にご期待ください。

関連記事

正規表現: 文字クラス [ ] 内でエスケープしなくてもよい記号

正規表現をチョムスキー言語学まで遡って理解する(翻訳)

Ruby正規表現の後読みでは長さ不定の量指定子は原則使えない

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ 開発エンジニア積極採用中です! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好き。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

週刊Railsウォッチ

インフラ

ActiveSupport探訪シリーズ