Vimの文字列検索(/ と ?)と正規表現 行頭・OR・\v・\V
Vimの検索は/を打つだけでも十分使えますが、正規表現を少し知っているだけで検索の精度が大きく変わります。この記事は/と?で検索語を打ち込む検索と、そこに書ける正規表現を扱います。どの検索にも共通する大文字小文字の決まり方と、VimとNeovimで既定値が違うところもここで扱います。打たずにカーソル下の単語を探す*と#はカーソル下の単語を検索する記事に分けてあり、複数ファイルやファイル名を探す道具は最後の節で行き先を並べています。
検証環境:Vim 9.1.1752とNVIM v0.12.6(macOS)。正規表現の表の件数は、下に載せた9行のバッファに対してsearchcount()が返した値です。大文字小文字の表は別の4行のバッファで同じように数えました。VimとNeovimで全パターンとも同じ件数になりました。
目次(11項目)
| キー | これだけ覚える |
|---|---|
| / / ? | 下方向 / 上方向へ検索 |
| n / N | 同方向 / 逆方向に次のマッチへ |
\v | 括弧や|をエスケープせずに書く |
基本の検索方向
/{文字列} " 下方向(カーソルより後ろ)へ検索
?{文字列} " 上方向(カーソルより前)へ検索
検索後はnで同じ方向に次のマッチへ、Nで逆方向に移動します。/で検索したときと?で検索したときではnの実際の移動方向が変わる点に注意してください。nは常に「最初に指定した検索方向」を維持します。
set hlsearch のあと /config で検索したところ。一致した箇所が全部光る一致箇所が光るかどうかと、打っている途中から候補へ飛ぶかどうかは、hlsearchとincsearchの2つで決まります。この既定値はVimとNeovimで違い、Vimは起動のしかたによっても変わります。違いと、光ったハイライトの消し方は後ろの「VimとNeovimで既定値が違うところ」で比べています。
覚えておきたい正規表現パターン
件数を数えるのに使ったのは次の9行です。configXyamlとfoofooは、パターンが拾いすぎていないかを見るためにわざと入れた行です。
INFO: start
ERROR: code 404
config.yaml
configXyaml
user = 1
user_id = 2
foo bar
foofoo = 3
end of log
| パターン | 意味 | 件数 |
|---|---|---|
/^user | 行頭が「user」 | 2件 |
/yaml$ | 行末が「yaml」 | 2件 |
/foo\|bar | 「foo」または「bar」 | 4件 |
/\d\d\d | 連続する3桁の数字 | 1件 |
/\<user\> | 単語として独立した「user」 | 1件 |
/config.yaml | .は任意の1文字 | 2件 |
/config\.yaml | .そのもの | 1件 |
いちばん間違えやすいのは.です。ファイル名のつもりで/config.yamlと打つと、.が「何か1文字」と解釈されてconfigXyamlの行まで拾います。点そのものを探すなら\.と書くか、先頭に\V(very nomagic)を付けて/\Vconfig.yamlとします。\Vのあとは\以外の記号がただの文字になるので、.や*の混じった文字列を貼り付けて探すときに向いています。どちらも1件に減りました。ただし/だけは\Vを付けても検索の区切りとして働きます。/\Vsrc/app.jsと打つと、検索レジスタに入ったのは\Vsrcだけでした。パスを探すときは\/と書くか、?で検索します。
\<と\>は単語の先頭と末尾です。素の/userがuser_idまで含めて2件拾うのに対し、/\<user\>は1件だけになります。カーソルの上にある単語なら、この境界付きのパターンを*が自動で組み立ててくれます。
行頭行末の^と$は他の言語の正規表現と同じ感覚で使えます。一方で、括弧やOR、回数指定は\でエスケープしないと特殊な意味になりません。\v(very magic)を先頭に付けると、エスケープなしで他言語に近い書き方ができます。同じ意味の書き方を並べると次のとおりで、件数はどれも左右で一致しました。
| 既定の書き方 | \vを付けた書き方 | 件数 |
|---|---|---|
/foo\|bar | /\v(foo|bar) | 4件 |
/\d\{3} | /\v\d{3} | 1件 |
/\<user\> | /\v<user> | 1件 |
/\(foo\)\{2} | /\v(foo){2} | 1件 |
注意したいのは、\vのもとでは英数字と_以外のほぼすべての記号が特殊な意味を持つことです。たとえば=は「直前が0回か1回」の意味になります。a=bとabの2行で試すと、/a=bは1件なのに/\va=bは2件拾いました。代入の=のような記号を含む文字列を探すときは、\vより\Vのほうが安全です。
INFO: start
ERROR: code 404
INFO: end
/\d\d\dINFO: start
ERROR: code 404
INFO: end
カーソルは行単位ではなく、マッチした文字列の先頭に正確に移動します。止まる位置をずらしたいときは\zsが使えます。/code \zs\d\+は「code 」に続く数字を探しますが、一致の始まりが\zsの位置になるので、カーソルはcodeではなく404の先頭(13桁目)に止まりました。前後の文字で場所を絞りつつ、飛ぶ先は中身にしたいときに効きます。
大文字小文字は、パターンのどこかに\cを入れると区別せず、\Cを入れると区別します。上のバッファでは/errorが0件、/error\cが1件でした。ignorecaseとsmartcaseの設定でどう変わるかは次の節で数えています。.*が長く取りすぎるときは最短マッチの.\{-}を使います。
\|でOR検索、\dで数字を検索します。.は任意の1文字なので、点そのものは\.か\Vで探します。\vを使えば普段慣れた正規表現の書き方に近づけられます。
ログファイルから特定のエラーコード(3桁の数字)だけを含む行を探したいとき、/\d\d\dで一発で絞り込めることに気づいてから、検索コマンドを正規表現込みで使う機会が一気に増えました。\vを知ってからはエスケープの数を数える手間も減りました。
大文字小文字がどう決まるか
ignorecaseとsmartcaseを組にする設定は定番ですが、この組み合わせは*には効きません。次の4行のバッファに対して、設定を変えながら検索してsearchcount()の件数を記録しました。
error one
Error two
ERROR three
error four
| 設定 | /error | /Error | Errorの上で* |
|---|---|---|---|
既定(noignorecase) | 2件 | 1件 | 1件 |
ignorecase | 4件 | 4件 | 4件 |
ignorecase + smartcase | 4件 | 1件 | 4件 |
最後の行を見てください。同じErrorという語を探しているのに、自分で打った/Errorは1件しか拾わず、単語の上で*を押すと4件すべてを拾います。smartcaseが働くのは自分で打った検索語に限られ、*が組み立てた\<Error\>は対象外だからです。Vim 9.1とNeovim 0.12の両方で同じ結果になりました。
設定に関係なくその場で決めたいときは、前の節の\cと\Cを混ぜます。こちらは設定を上書きし、パターンの先頭でも末尾でも同じように効きます。
| 打ち方 | 既定のとき | smartcaseのとき |
|---|---|---|
/Error\c | 4件 | 4件 |
/error\C | 2件 | 2件 |
普段はignorecaseとsmartcaseを入れておき、*で拾いすぎたと感じたときだけ/で打ち直す、という運用が現実的です。厳密に区別したい検索を頻繁にするなら、\Cを付ける癖をつけるほうが確実です。
VimとNeovimで既定値が違うところ
同じ/を打っても、VimとNeovimでは見え方が違います。一致箇所を光らせるhlsearchと、打っている途中から候補へ飛ぶincsearchの既定値がそろっていないためです。Vimはさらに、defaults.vimが読まれるかどうかでincsearchが変わります。3通りの起動で:set hlsearch? incsearch?を確かめました。
| 起動のしかた | hlsearch | incsearch | Ctrl-lでハイライトが消えるか |
|---|---|---|---|
Vim 9.1(vim --clean) | オフ | オン | 消えない |
| Vim 9.1(自分のvimrcがあるとき) | オフ | オフ | 消えない |
NVIM v0.12.6(nvim --clean) | オン | オン | 消える |
vim --cleanのincsearchがオンなのは、:verbose set incsearch?が示すとおりdefaults.vimの50行目が入れているからです。defaults.vimは自分のvimrcが無いときにしか読まれないので、空のvimrcを1つ置いただけでオフに戻りました。macOSに最初から入っている/usr/bin/vimはさらに特殊で、/usr/share/vim/vimrcにlet skip_defaults_vim=1が書いてあるため、vimrcを置いていなくてもオフでした。「Neovimでは打ちながら飛ぶのにVimでは飛ばない」と感じたら、vimrcにset incsearchを足せばそろいます。
光ったハイライトは:nohlsearch(:noh)で消えます。NeovimにはCtrl-lにnohlsearchを足した既定の割り当てがあり(:help CTRL-L-default)、押すとv:hlsearchが0になりました。VimのCtrl-lは画面を描き直すだけで、押しても1のままです。Vimでも同じにしたいなら次の1行を入れます。手元のVim 9.1で、押したあとv:hlsearchが0になるのを確かめました。
nnoremap <C-L> <Cmd>nohlsearch<CR><C-L>
消しても、次にnや*で検索すればv:hlsearchは1に戻ります。いちいち消さずに済ませる設定は検索ハイライトを消す記事にまとめました。
検索履歴を再利用する
q/
q/を実行すると検索履歴のウィンドウが開き、過去に検索したパターンを一覧から選んで再実行できます。長い正規表現を何度も打ち直したくないときに便利で、コマンドラインモードで上下キーを押しても同じように履歴をたどれます。履歴のウィンドウの中では普通のバッファと同じように編集できるので、前回のパターンを少しだけ書き換えて流すこともできます。操作の詳細はコマンドラインウインドウの記事にあります。
検索中にカーソルを画面中央へ
検索でヒットした行が画面の端に寄っていて見づらいときは、zzでカーソル行を画面中央に、ztで画面上部に、zbで画面下部に再配置できます。検索直後にこれらを組み合わせると、周辺の文脈を見ながら次の作業に移りやすくなります。nで次のマッチへ移るたびに中央へ寄せたいなら、nnoremap n nzzのように割り当てておく方法もあります。
検索をそのまま範囲として使う
Vimの検索は移動のキーなので、削除や書き換えの範囲としてそのまま渡せます。行数を数えずに「次にこの文字列が出てくるまで」を指定できます。
d/return<Enter> " 次の return の手前まで消す
d/return/e<Enter> " return を含めて消す
y?function<Enter> " 前の function までコピー
検索モーションは見つかった位置を含まないので、その語ごと消したいときは末尾に /e を付けます。この指定は検索そのものにも効き、/return/e だけを打てば語の末尾にカーソルが止まります。/eの仲間の指定は検索オフセットの記事に、cやyと組み合わせた例はオペレータ+検索モーションにあります。
ファイルの端で折り返さないようにする
既定の検索は、末尾まで行くと先頭に戻って続きます。便利な反面、オペレータと組み合わせたときにこれが起きると、カーソルより前の位置が範囲の端になり、意図しない方向が消えます。
set nowrapscan " 端まで行ったら止まる
折り返さない設定にすると、見つからなければ何も起きません。範囲の指定に検索を使うことが多い人は、こちらのほうが事故が起きにくくなります。
探す範囲で道具が変わる
/はいま開いているバッファの中を、打ち込んだ語で探します。同じ行の1文字やカーソルの下の単語、複数のファイルやファイル名を探すときは別の道具のほうが速いので、行き先を範囲の狭い順に並べておきます。
| 探す範囲 | 使うもの | 詳しい使い方 |
|---|---|---|
| 同じ行の中の1文字 | f F t T | 1文字検索して移動する |
| カーソルの下の単語 | * # | カーソル下の単語を検索する |
| 開いている全バッファ | :bufdo + :vimgrep | 全バッファを検索する |
| プロジェクトのファイル群 | :vimgrep :grep | vimgrepとgrepで横断検索する |
| ファイルの名前そのもの | :find | ファイル名からファイルを探す |
まとめ
基本の//?に、行頭行末の指定とOR検索、そして.のエスケープを覚えるだけで検索の実用性が大きく上がります。括弧や回数指定が増えてきたら\v、記号の多い文字列をそのまま探すなら\Vです。大文字小文字はignorecaseとsmartcaseで決まりますが、*にはsmartcaseが効かないので、その場で決めたいときは\cと\Cを混ぜます。hlsearchとincsearchの既定値はVimとNeovimで違うので、乗り換えて見え方が変わったらそこを疑います。カーソル下の単語を打たずに探したい場合は* と # の記事を、複数ファイルを横断して探すときはvimgrepとgrepの記事を参照してください。
ここで扱ったキーは、ブラウザ上で動くVim道場の検索でジャンプするでそのまま試せます。インストールもログインも要りません。