statuscodeのメルマガでWhy Using the Greedy .* in Regular Expressions Is Almost Never What You Actually Wantという記事を見かけて、「おおー、これはあるあるな話だよね」と思ったのでブログネタにしてみるテスト。
欲張りなマッチ
.*
を使っても欲しい結果が得られない場合があるよ。- 代わりに
.*?
を使おう。
というのが記事の趣旨なのですが、かの有名な詳説正規表現でも同じような例が載っているので、それを引用して説明してみようと思います。
オライリージャパン
売り上げランキング: 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>
がネストされていない部分にマッチすることができるのです。
いやー、正規表現って奥が深いですね。いくら正規表現を知っていても、人の書いた正規表現ってやっぱり読みにくいので、個人的にはもう少し可読性の高い正規表現の記法が欲しいなー、なんて思っております。。