Posted at

ここまで出来る!Vimの(ちょっと高度な)正規表現と検索/置換テクニック

More than 3 years have passed since last update.

Vimは非常に強力な正規表現とそれに伴う強力な検索/置換機能を備えています。

今回はそんなVimの正規表現と検索/置換のテクニックをまとめてみます。


Vim特有の正規表現を覚える


Vimの正規表現には独特な方言がある

Vimの正規表現は何かと面倒な事が多いです。

Perl等と比べて、様々なメタ文字をエスケープしてやらなければなりません。

例えば通常の正規表現であれば (abc)+ と書く事で、

文字列 abc の繰り返しを意味することができますが、

これをVimの正規表現で書こうとすると、\(abc\)\+ 等と書かなければならず、大変面倒です。

これらの方言はVimを使っていく上で自然に覚えていくにはいくのですが、

ついうっかりいつもの正規表現を使ってしまったり、

逆にプログラム側で正規表現を書く必要のあるときに

Vim方言な正規表現を書いてしまったりして悲しい事が起こったりと結構厄介です。

でも、そんなあなたに朗報です!


\v(Very Magic)を利用していつもの自然な正規表現に!

そこで、正規表現の頭に \v をつけてみましょう。

たったこれだけで、Vim特有の方言を使わずに、

一般的な(?)正規表現に限りなく近い形で書くことができます。

文字列 abc の繰り返しはちゃんと (abc)+ と書けますし、

| 等、Vimでだけ何故かエスケープが必要だった文字列が不要になります。

これらの挙動については :help magic すれば見る事ができます。

以下のような表が出てきますね。 \v 以外にも色々あるようです。

Examples:

after: \v \m \M \V matches ~
'magic' 'nomagic'
$ $ $ \$ matches end-of-line
. . \. \. matches any character
* * \* \* any number of the previous atom
() \(\) \(\) \(\) grouping into an atom
| \| \| \| separating alternatives
\a \a \a \a alphabetic character
\\ \\ \\ \\ literal backslash
\. \. . . literal dot
\{ { { { literal '{'
a a a a literal 'a'

ちなみに、僕はしていませんが、この挙動をデフォルトとしたい人なんかは、

.vimrcに nmap / /\v 等と書くと良いみたいです、こうしておけば / での検索時には

いつも \v がついた状態から検索を始められますね!


置換時に役立つTips


事前検索で置換条件は省略できる

これは非常によく使う便利なテクニックです。

Vimでは置換する際に 置換条件を省略すると、最後に検索した検索条件を利用 します。

なので、複雑な正規表現等で置換を行う際は、まず / による検索で、

ハイライトされる範囲が正しい事を確認してから、

以下のようにすることで直前に引っ掛けた箇所をそのまま置換することができます。

/FooBarBaz          " FooBarBazを検索

:%s//HogeFugaPiyo/g " FooBarBaz -> HogeFugaPiyoに置換される。

こうしておけば、事前に置換対象を確認できるので安心ですね!


検索には含めたいけど置換対象には含めたくない!というケース

ちょっとわかりづらいかもしれませんが、

これも非常によくあるケースで、よく困ってるという方はかなり多いんじゃないでしょうか。

例えば以下のようなコードがあるとします。

FooBarBazHogeBarFugaPiyoFirst

FooBarBazHogeBarFugaPiyoSecond
FooBarBazHogeBarFugaPiyoSecond
FooBarBazHogeBarFugaPiyoFirst
FooBarBazHogeBarFugaPiyoSecond
FooBarBazHogeBarFugaPiyoThird
FooBarBazHogeBarFugaPiyoSecond
FooBarBazHogeBarFugaPiyoFirst
FooBarBazHogeBarFugaPiyoThird

上記は非常によく似た文字列が並んでて最後だけ違うというケースです。

Javaなんかではよく見るケースですね・・・。

それぞれ Bar という文字列が途中に2回入っており、

最後はそれぞれFirst, Second, Thirdとなっています。

さて、この内 最後がSecondなBarだけを全てFooに変えたい 場合はどうすれば良いでしょう?

まともにやってしまうと以下のようになってしまいますね。

:%s/\(.*\)Bar\(.*\)Bar\(.*Second\)/\1Foo\2Foo\3/

き、汚い・・・。

それにこのやり方では、Barが登場する度に()の数と後方参照が増えていくことになり、

Barが3回以上現れた場合等に対応できませんね。

先ほどのVery Magicを使えばこうなります。

:%s/\v(.*)Bar(.*)Bar(.*Second)/\1Foo\2Foo\3/

少し綺麗になりましたが、やはり非常に微妙ですね・・・。

先ほどの例と同じように、Barが何度も再登場する場合には相変わらず対応できません。


マッチ範囲を限定できる\zs, \zeを利用する

非常に便利なのに意外と知られていない機能がVimにはいくつもありますが、

そのうちの一つが正規表現の \zs\ze です。

これはそれぞれStartとEndという役割を持っており、

正規表現を書いた中にこの指定がある場合は、記述した正規表現全体を検索対象としつつ、

マッチする範囲のみを限定させることができます。

とは言ってみたもののどういうことか分からないと思うので、

先ほどの変換をこの \zs \ze を用いて記述したものが以下です。

:%s/\zsBar\ze.*Second/Foo/g

どうでしょう?

驚くほどスッキリと書けました、括弧がひとつもありません!

しかもルールさえ知っていれば非常に理解しやすいと思います。

さらにこの方法であれば途中にいくつ Bar が出てきても、確実に置換できます。

これは置換の前に一度検索してみると分かりやすいのですが、

先ほどまでの :%s/\v(.*)Bar(.*)Bar(.*Second)/\1Foo\2Foo\3/ のような検索では

FooBarBazHogeBarFugaPiyoFirst

FooBarBazHogeBarFugaPiyoSecond

FooBarBazHogeBarFugaPiyoSecond

FooBarBazHogeBarFugaPiyoFirst

FooBarBazHogeBarFugaPiyoSecond

FooBarBazHogeBarFugaPiyoThird

FooBarBazHogeBarFugaPiyoSecond

FooBarBazHogeBarFugaPiyoFirst

FooBarBazHogeBarFugaPiyoThird

のように行全体がハイライトされてしまうのに対し、

/\zsBar\ze.*Second のような検索では

FooBarBazHogeBarFugaPiyoFirst

Foo Bar BazHoge Bar FugaPiyoSecond

Foo Bar BazHoge Bar FugaPiyoSecond

FooBarBazHogeBarFugaPiyoFirst

Foo Bar BazHoge Bar FugaPiyoSecond

FooBarBazHogeBarFugaPiyoThird

Foo Bar BazHoge Bar FugaPiyoSecond

FooBarBazHogeBarFugaPiyoFirst

FooBarBazHogeBarFugaPiyoThird

このような状態でハイライトされるはずです。

Vimが置換するのはこのハイライトされる範囲だけ なので、

前述の事前検索を組み合わせれば非常に安全な置換が行えます。


最後に

正規表現に関わらず、Vimは非常に奥が深いですね!

お互いVimのカッコイイ正規表現をマスターして、

カッコイイテキストエディットができる

カッコイイエンジニアになりたいですね!