正規表現で部分マッチに .* を使うと上手くいかないことがあるよ、という話

Pocket

statuscodeのメルマガでWhy Using the Greedy .* in Regular Expressions Is Almost Never What You Actually Wantという記事を見かけて、「おおー、これはあるあるな話だよね」と思ったのでブログネタにしてみるテスト。

欲張りなマッチ

  • .*を使っても欲しい結果が得られない場合があるよ。
  • 代わりに.*?を使おう。

というのが記事の趣旨なのですが、かの有名な詳説正規表現でも同じような例が載っているので、それを引用して説明してみようと思います。

詳説 正規表現 第3版
詳説 正規表現 第3版
posted with amazlet at 14.06.07
Jeffrey E.F. Friedl
オライリージャパン
売り上げランキング: 122,572

The name “McDonald’s” is said “makudonarudo” in Japanese (p.157)

上記の文中でダブルクォート(")で囲まれたテキスト、つまり「”McDonald’s”」にマッチする正規表現を考えたい、とします。

単純に考えると".*"という正規表現でマッチできそうなので、これを実際使ってみると、

“McDonald’s” is said “makudonarudo”

最初の"で止まるという意に反して、文字列の最後の"までマッチしてしまうという問題があります。

これは最初の"がマッチした後、.*文字列の末尾までマッチした後に、次の"の条件を満たす場所まで戻る、という動きをするためです。

こうした、標準の量指定子(?、*、+、{min,max})ができるだけ最大マッチ回数までマッチしようとする性質を、詳説正規表現では「第2原則:標準の量指定子は欲張りである」というマッチの基本原則で説明しています。気になる人は是非買って読んでみてくださいね!

で、じゃあどうすれば「”McDonald’s”」にマッチさせることができるの?というと、「ダブルクォートに囲まれた『全ての文字』」という正規表現ではなく、「ダブルクォートに囲まれた『ダブルクォート以外のすべての文字』」とすれば、文字列の末尾まで探査されたとしても、最初のダブルクォートで止まってくれそうです。つまり"[^"]*"という正規表現を使うことで

“McDonald’s”

にマッチさせることができるのです。やったね!

非欲張り量指定子(最短マッチ)

ただこれだとあまりに*が使いづらいので、大体の正規表現が非欲張り量指定子(最短マッチ)というものをサポートしています。つまり、最初の"で止まってくれるような量指定子です。

  • *?
  • +?
  • ??
  • {min,max}?

みたいな表記法で利用することができます。

上記の例だと、".*?"という正規表現で

“McDonald’s”

にマッチさせることができる、ということになります。こちらの方が簡潔で良さそうですね!

こうした「何かに囲まれた部分のマッチ」というのはhtmlをパースするときにもよく利用されるので、プログラマとしては知ってて損はない内容ですよね。

ただ、ネストしたhtmlのタグを考慮したマッチなど、もう少し難しいマッチの場合はもう少し考える必要があります。

スパンに<span>囲まれ<span>すぎな</span>文章</span>だよねー

という文字列があったときに

<span>囲まれ<span>すぎな</span>文章</span>

までマッチさせたければ、<span>.*</span>でマッチさせることができます。

しかし、

<span>すぎな</span>

だけをマッチさせたいとしたときに<span>.*?</span>としてしまうと

<span>囲まれ<span>すぎな</span>

がマッチしてしまうことになります。

ここではもう少し複雑な正規表現が必要で、<span>((?!<span>).)*?</span>という正規表現を使うと

<span>すぎな</span>

にマッチさせることができます。

この正規表現を分解してみると以下のような形になります。

この考え方で<span>がネストされていない部分にマッチすることができるのです。

いやー、正規表現って奥が深いですね。いくら正規表現を知っていても、人の書いた正規表現ってやっぱり読みにくいので、個人的にはもう少し可読性の高い正規表現の記法が欲しいなー、なんて思っております。。