Vimの正規表現で最短マッチを使う(.\{-})
HTMLのhref属性の値だけを消したくて.*で置換したら、最初のタグから最後のタグまでまるごと巻き込まれてしまいました。Vimの正規表現はデフォルトで貪欲(できるだけ長くマッチしようとする)に動くため、これを避けるには最短マッチの書き方を知っておく必要があります。
| パターン | これだけ覚える |
|---|---|
.* | 貪欲マッチ(できるだけ長く一致させる、デフォルト) |
.\{-} | 最短マッチ(できるだけ短く一致させる) |
最短マッチの書き方
.\{-}
他の言語の正規表現でよく見る.*?に相当します。.*が「できるだけ長く」マッチしようとするのに対し、.\{-}は「できるだけ短く」マッチしようとします。
<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\)のように、対象ごとにグループと最短マッチのペアを増やしていくだけで済みます。複雑に見えるパターンも、突き詰めると「グループ・最短マッチ・グループ」の繰り返しでしかないことが多いです。複雑に見えて怖じ気づく前に、パターンを要素ごとに分解して読む習慣をつけると安心です。
まとめ
正規表現の貪欲マッチで意図しない範囲まで巻き込んでしまったら、.\{-}による最短マッチを試すと効果的です。開始・終了の目印で挟まれた範囲を正確に指定したいときに欠かせないテクニックです。