Vimの正規表現で最短マッチを使う(.\{-})
HTMLのhref属性の値だけを消したくて.*で置換したら、最初のタグから最後のタグまでまるごと巻き込まれてしまいました。Vimの正規表現はデフォルトで貪欲(できるだけ長くマッチしようとする)に動くため、これを避けるには最短マッチの書き方を知っておく必要があります。置換そのものの打ち方は別にまとめてあるので、ここではパターンの書き方だけを扱います。
目次(10項目)
| パターン | これだけ覚える |
|---|---|
.* | 貪欲マッチ(できるだけ長く一致させる、デフォルト) |
.\{-} | 最短マッチ(できるだけ短く一致させる) |
最短マッチの書き方
.\{-}
他の言語の正規表現でよく見る.*?に相当します。.*が「できるだけ長く」マッチしようとするのに対し、.\{-}は「できるだけ短く」マッチしようとします。
<a>1</a><a>2</a>
:s/<a>.\{-}<\/a>/X/X<a>2</a>
最短マッチなので、最初の<a>から一番近い</a>までだけが対象になり、2つ目のタグは残ります。同じ文字列に.*を使うと、最初の<a>から最後の</a>までを一気にマッチし、結果はXだけになります。
実例:href属性の値を消す
:%s/\(href="\).\{-}\("\)/\1\2/g
この置換パターンは3つの要素で構成されています。
| パターン | 役割 |
|---|---|
\(href="\) | グループ1:href="までを記憶 |
.\{-} | 最短マッチ:属性値の部分(次の"の直前まで) |
\("\) | グループ2:閉じの"を記憶 |
置換後は\1\2でグループ1とグループ2だけを残し、間の属性値を消します。もし.*を使っていたら、最初のhref="から最後の"までを一気にマッチしてしまい、複数のタグをまたいで意図しない範囲が消えてしまいます。
他の量指定子でも同じ考え方が使える
a\{-} " aの0回以上の最短マッチ
a\{-1,} " aの1回以上の最短マッチ
\{-}は.専用の記法ではなく、量指定子全般に付けられる最短マッチの印です。*(0回以上)や\+(1回以上)にもそれぞれ最短版があると考えると理解しやすくなります。
確認しながら実行する
:%s/\(href="\).\{-}\("\)/\1\2/gc
末尾にcを付けると、置換前に1件ずつ確認しながら実行できます。正規表現に自信がないうちは、まずc付きで試して意図通りの範囲がマッチしているか確認する習慣をつけると事故を防げます。
.*は貪欲、.\{-}は最短。開始タグと終了タグのペアのように「ここからここまで」を狭く指定したいときは、最短マッチを思い出すと便利です。
最初に.*で置換して、意図しない範囲が丸ごと消えたときは焦りました。.\{-}を知ってからは、開始と終了の目印で挟まれた部分だけをピンポイントで狙えるようになり、HTMLやXMLのような入れ子構造のテキストを編集する自信がつきました。
グループを複数使う場合
開始・終了の目印が複数種類ある場合でも、同じ考え方を繰り返せば対応できます。\(始まりA\).\{-}\(終わりA\)のように、対象ごとにグループと最短マッチのペアを増やしていくだけで済みます。複雑に見えるパターンも、突き詰めると「グループ・最短マッチ・グループ」の繰り返しでしかないことが多いです。複雑に見えて怖じ気づく前に、パターンを要素ごとに分解して読む習慣をつけると安心です。
Vimの最短マッチは書き方が独特
他の言語の正規表現では *? や +? のように ? を足して最短にしますが、Vimでは \{-} という別の書き方になります。この形を知らないと、そもそも検索の仕方が分かりません。
.\{-} " 0回以上の最短マッチ(他言語の .*?)
.\{-1,} " 1回以上の最短マッチ(他言語の .+?)
.\{-1,5} " 1〜5回の最短マッチ
覚え方としては、回数を指定する \{n,m} の形に - を足すと最短になる、と考えると筋が通ります。- が「できるだけ少なく」の印です。
very magic なら短く書ける
パターンの先頭に \v を付けると打ち消しがほぼ不要になり、最短マッチも {-} と書けます。長いパターンではこちらのほうが読めます。
/\v\<a href\="(.{-})"
:%s/\v\<(.{-})\>//g
ただし \v を付けても ? を足す書き方にはなりません。Vimの最短マッチは常に波カッコの形です。他の言語から来た人がいちばん引っかかるところなので、ここだけは書き換えて覚えることになります。
最短マッチが要らない場合もある
タグや引用符の中身を取りたいだけなら、最短マッチを使わずに「区切り文字以外」を並べたほうが速く、意図も明確になります。
/"[^"]*" " 引用符の中身(最短マッチ不要)
/<[^>]*> " タグ(同上)
正規表現の実行速度は行数が多いほど効いてきます。.\{-} は1文字ずつ後戻りしながら探すので、否定文字クラスで書けるなら、そちらのほうが安定します。テキストオブジェクトで済む場面ならそもそも検索が要らないので、ci" の記事とあわせて手段を選んでください。
まとめ
正規表現の貪欲マッチで意図しない範囲まで巻き込んでしまったら、.\{-}による最短マッチを試すと効果的です。開始・終了の目印で挟まれた範囲を正確に指定したいときに欠かせないテクニックです。