読者です 読者をやめる 読者になる 読者になる

Vimの正規表現で最短マッチ(非欲張り型)を実現する

tl;dr

https://dl.dropboxusercontent.com/u/7779513/blog/2015-04-08_vim-replace.gif

たとえば以下のようなテキストを編集しているとして、

そのバンドの3rdアルバム「A」は私のフェイヴァリットだが、彼らの代名詞とも言える独特のギターサウンドは2ndアルバム「B」から4枚目の「C」までに集約され、5枚目の「D」からはディスコサウンドに路線を転換したためファンの間でも賛否が分かれた。

いま書きながら考えたテキトウな文章ですが、この中のアルバム名をくくった括弧「」をすべて『』に変えたくなった、とする。

このぐらいの長さなら一つ一つ手で直すのもアリかもしれないけど、何万字というテキストに対して同様のことをしようとなると、けっこう泣ける。

そこで、Vim正規表現でガサッと変えよう! なんて思って以下のように打ち込むと、

:%s/\(.\+\)/\1/gc

「A」を拾いたいはずのところで以下の部分が拾われる。

「A」は私のフェイヴァリットだが、彼らの典型と言えるサウンドスタイルは2ndアルバム「B」から4枚目の「C」までに集約され、5枚目の「D」

長すぎる。
ようは、最初の括弧から最後の括弧までを対象として拾ってしまう、いわば「最長マッチ」、あるいは「欲張り型」とも言われるマッチの仕方をしている。

これを避けたい場合、Perl正規表現なら、「+」の後に「?」を入れると、「.+」に入る文字が最小限になり、これを「最短マッチ」と言ったり「非欲張り型」と言ったりするけど、Vimで同じことをやろうとすると、

:%s/\(.\+?\)/\1/gc

以下のような対象にしかマッチしない。

「A?」

つまり使えない。

では、「A」や「B」などに最短マッチさせるにはどうするんだ? という問題に数ヶ月に一度遭遇し、そのつど検索して、理解して、忘れて、というのを繰り返してきたので、この辺でそろそろブログに書いてなるべく今後忘れないようにしたい、という理由でこれを書いています。

結論的には、Perlにおける「+?」の代わりなら「{-1,}」、「*?」の代わりなら「{-}」を使う。
また、「3回から9回までのくり返し」みたいに回数を設定したい場合は、最長マッチなら「{3,9}」、最短マッチなら「{-3,9}」を使う。(多分)

この時、上記の「+」や「()」のように、「{}」にはエスケープが必要なので、実際はこうなる。*1

:%s/\(.\{-1,\}\)/\1/gc

本件については、「vim 最短マッチ」などで検索するといろいろ出てくるけど、個人的には他のコマンドも含めて以下が一番参考になった。ありがとうございます。
http://archiva.jp/web/tool/vim_regexps.html

*1:自分の場合は「{}」もエスケープしないと動作しないけど、検索した先の記事では大抵これをエスケープしていないので、環境による(OR/AND認識に不備がある)のかもしれない。