はじめての正規表現とベストプラクティス#2: 正規表現とは何か/ワイルドカードとの違い

正規表現とは

正規表現は「Regular Expression」を直訳したもので、しばしばRegexpとかRegexと略されます(たまにREとも略されます)。詳しく説明するときりがありませんが、さしあたって「言語やツール内で利用できる一種のサブ言語」と考えておけばほぼ間に合います。

前回ご紹介した、正規表現に用いられる特殊な記号をメタ文字(メタキャラクタ)と呼びます。

たとえば、以下は正規表現の例です(Rubular)。

[。、,.]{2,}

これは、『「。」「、」「,」「.」のいずれかが2文字以上続いている』ことを表しています。「。。」のように同じ2文字の連続だけではなく、「。、」のように異なる2文字にもマッチします。

補足

正規表現は、理論が実装よりも先に誕生している点が特徴で(SQLも理論から先にできた言語です)、言語学や形式言語理論などの研究の過程で編み出された表現手段の一種です。当初は、表現に不可欠なごくわずかな記号しかなかったようです。

それが後にコンピュータでの文字列の検索や置き換えの表現方法として取り入れられ、実装や拡張が繰り返されて現在に至っています。

正規表現の背景などについて詳しくはWikipediaや正規表現をチョムスキー言語学まで遡って理解する(翻訳)などに譲ります。

参考: 正規表現 - Wikipedia

正規表現機能の使いみち

正規表現は、コンピュータでの検索や置き換えの対象となる文字列を指定するのに使われています。

  • 特定の文字パターンにマッチするかどうかを判定する」が基本の機能
    • Rubyなら"文字列".match?(/正規表現/)などと書ける
  • 付加機能
    • 特定の文字パターンにマッチしたものを置き換える
      • Rubyなら"文字列".gsub(/正規表現/, "置き換え文字列")
    • マッチした文字パターンを分解して部分を取り出したり部分置き換えする(今後解説します)

正規表現は、コード内のサブ言語の一種としての使い道の他に、テキストエディタやコマンドラインツールでも検索・置換機能のために内部でしばしば正規表現がライブラリまたは独自実装の形で利用されています。また、ApacheやSafariといった規模の大きいソフトウェアにも正規表現ライブラリが組み込まれています。

正規表現はプログラミングで時々とても重要になりますが、サブ言語だけあってほとんどの場合主役ではなく、一部の好き物を除いてガンガン使いまくる人はあまりいません。

現実のプログラミングでは、ある種の問題、具体的には「ある程度以上複雑な文字のマッチングや置き換え」を解決するときに使われることが多く、その場の問題が解決されればむしろ正規表現のことなど忘れていたい開発者もいるようです。

正規表現は無理して使うものではありませんが、文字のマッチングや置き換えを扱うときに遅かれ早かれ使うことになります。また、Rubyのメタプログラミングでは正規表現が援用されることがしばしばありますので、プログラミングの幅を広げることができます。

ツールの例

正規表現の使えるコマンドラインツールとしてはgrepが有名ですが、文字列置き換えに特化したsedなどさまざまなコマンドがあります。なお、私自身はripgrepというgrepよりも高機能/高速なツールを使っています。また、pecoというインタラクティブなフィルタツールも非常に便利なのでよく使いますが、これにも正規表現モードがあります。

正規表現は「ライブラリ」で実現される

正規表現は、プログラミング言語やツールのコアに組み込まれることはあまりなく、多くの場合その言語やツールのライブラリ(エンジンとも呼ばれます)という差し替え可能な形で導入されます。したがって、異なる言語であってもライブラリが同じであれば同じ正規表現が使えますし、逆に同じ言語であっても正規表現ライブラリが変更またはアップデートされれば機能が追加されたり、逆に一部で互換性がなくなることもありえます。

Rubyの場合、Onigmo(鬼雲)というライブラリが使われています。以前はoniguruma(鬼車)というライブラリでした。

正規表現をサポートするエディタの中には、正規表現のライブラリを差し替えることでより強力な正規表現の機能を使えるものもあります(秀丸エディタなど)。

正規表現には「方言」がある

正規表現のライブラリや実装が多様であるため、残念ながら正規表現には方言がものすごく多いという問題があります。具体的には、さまざまな言語/ライブラリ/ツールで実装されている正規表現はたいてい独自拡張されています第1回で説明した正規表現はほぼ確実に共通で使えるのが救いです。

ライブラリやツールごとの互換性については以下のWikipediaページによくまとまっています。

参考: Comparison of regular expression engines - Wikipedia

正規表現ライブラリの中で比較的メジャーなのはPCREと呼ばれるPerl 5互換の正規表現ライブラリです。PCREで拡張されている正規表現はかなり強力なのですが、それでも(私には)少し足りない機能があります。

また、Perl 5はPCREそのものを用いていますが、PHPやPythonの正規表現やRubyのOnigmoなどはPCREにかなり近いながらも独自実装であり、実際にはPCREよりさらに機能が足りない部分や独自の拡張がありますので、PCREと完全互換ではありませんし、お互い同士にも互換性のない部分があります。機能が不足する場合、開発者がより強力な正規表現ライブラリを導入することもあります。

個人的には.NET Frameworkの正規表現ライブラリが総合的に見て最も強力だと思います。ドキュメントも充実しています。

通常の開発やエディタでの検索置換であれば方言を意識する必要はほとんどありませんが、正規表現を複数の言語やプラットフォームで共有する場合は、方言やライブラリ(種類やバージョン)を意識しなければならなくなります

参考: メタ文字を含まない文字列も正規表現の一種

基本的なことですが、厳密に言うと?+などのメタ文字を含まない単なる文字列も正規表現の一種です。言い換えると、あらゆる文字列は正規表現の部分集合とみなせます。したがって、メタ文字を含まない正規表現も「ありです」(Rubular)。

ついでながら、どこからどこまでが正規表現かを表す区切り方は言語やツールの実装によって異なります。Rubyの場合は/ /で囲むことで正規表現を表せます(他の記号を使うこともできる)が、grepなどでは" "で囲んで指定することもあります。エディタの検索窓では区切り文字が不要な場合がほとんどです。

ワイルドカードと正規表現の違い

ワイルドカードとは

WordやExcelなどの検索置換機能、OSのファイル検索機能、シェル(bashやDOS窓など)のファイル名展開機能、一部のWebサイト内検索機能などで使われる?*という特殊な記号をワイルドカード(wildcard)と呼びます。元々はトランプのジョーカーのような任意のカードになれる特殊なカードを指す言葉です。

*
任意の文字列を表すワイルドカード
?
任意の1文字を表すワイルドカード

たとえば*.txtとすると「.txtで終わるすべてのファイル名」を表現できます。

記号が2種類なので、一般ユーザーにわかりやすいのが特徴です。WordやExcelでは、ワイルドカードの機能が多少拡張されており、正規表現に少し似たことができます。

[!。の]{1,}の[!。の]{1,}の[!。の]{1,}の[!。の]   #「の」の連続を検出するWordのワイルドカード

参考: ワイルドカード (情報処理) - Wikipedia
参考: 【まとめ】ワイルドカード(正規表現)に関する記事一覧 | みんなのワードマクロ

ワイルドカードは正規表現ではない

しかし、ワイルドカードは正規表現とは異なるものです。

  • ワイルドカードは表現力が乏しい
  • ワイルドカードの記号は正規表現の記号と意味が異なる

ワイルドカードと正規表現は見た目の印象がよく似ており、Wordの拡張ワイルドカード機能は一見正規表現のように見えるので、紛らわしいのですが、少なくとも両者の?*という記号は以下の点が決定的に異なります。

ワイルドカードの?
「1文字そのものの代わりに置く」(new?など)
正規表現の?
直前の1文字を後ろから修飾する」(/news?/など)

ワイルドカードの*
「任意の長さの文字そのものの代わりに置く」(*.xlsなど)
正規表現の*
直前の1文字を後ろから修飾し、その文字がゼロ文字以上であることを表す」(/./*\.xls/など)

まとめると、?*という記号は、ワイルドカードでは「文字の代替」、正規表現では「直前の文字を修飾する量指定子」となります。

第1回で*を紹介しなかった理由

*
直前の1文字の0回以上の繰り返しを表すメタ文字

正規表現を使ったことがある方なら誰もが知っている*(=直前の文字のゼロ回以上の繰り返し)というメタ文字ですが、第1回ではあえて含めませんでした。

取り上げなかった理由は、ワイルドカードの*の考え方に毒されたと思われる.*という正規表現が安易に用いられることが多いためです。

正規表現の書き方は一種類ではないので「機能すればよい」と割り切ることもできますが、.*.+といった正規表現はしばしば曖昧さにつながり、不要なマッチを呼び込みやすくなると個人的に考えています。最長一致や最短一致も意識しなければなりません。

本音を言えば、.+すら基本には含めたくなかったぐらいです。

もちろん.*.+がどうしても必要になることもありますが、その前に{N}などのより限定的な量指定子が使えないか、.のような範囲の広すぎるメタキャラクタではなく[]などでもっと限定できないかを検討するようにしています。

これまで多くの正規表現を書いてきましたが、幸いにして*.*を使う機会は一度もなく、+.+もときどき使う程度です。


関連記事

正規表現の先読み・後読み(look ahead、look behind)を活用しよう

Ruby 2.4.1新機能: Onigmo正規表現の非包含演算子(?~ )をチェック

デザインも頼めるシステム開発会社をお探しなら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探訪シリーズ