vimgrepとQuickfix知らないVimmerはちょっとこっち来い

  • 893
    いいね
  • 1
    コメント
この記事は最終更新日から1年以上が経過しています。

「vim入門」系記事で解説されないためか、意外と使い方が知られていないvimgrep。

  • ファイルを開いては検索、開いては検索ってしてる?
  • grepするためにvimから出てる?
  • grep結果を見て改めてvimで開き直してる?

それ、vimgrep使えば256倍早くなる(かも)よ。

簡単なまとめ

vimgrepは…

  • ファイルをまたいで検索できる
  • grepやgit-grepよりは遅いので巨大プロジェクトでは検索対象を絞ったほうがいい
    • ワイルドカード使うと簡単に絞り込める
    • 繰り返し同じ対象から検索する場合はargument listを使うと捗る
    • gitリポジトリではgit-ls-filesと組み合わせる
  • 該当箇所に素早く移動&編集できる
    • quickfix-windowと組み合わせると更に捗る

この記事読むと分かること

  • :vimgrepコマンドの使い方
  • :cwindowコマンドの使い方
  • :argsコマンドの使い方
  • gitリポジトリで便利に使う方法

この記事読んでも分からないこと

  • vimの正規表現
  • Quickfixの詳細
  • :grepコマンドの使い方

関連するvim-help

より詳しいことは以下のヘルプを読むこと。

  • quickfix.txt
    • :vimgrep
    • :cwindow
  • :args
  • cmdline-special
  • pattern-overview
  • wildcards

vimgrepの使い方

vimgrepは名前に grep が含まれていることからも分かるように、パターンに一致する箇所をファイルから検索するためのコマンドだ。書式は以下のとおり。(蛇足だと思うが:vim[grep][grep]が省略可能である、つまり:vimでもよいという意味。)

:vim[grep] {pattern} {file} ...

要するに{file} ...で指定したファイルの中から{pattern}で一致する箇所を検索する 。

{pattern}には正規表現も使える。vimの正規表現については:help pattern-overview参照。

{file}にはいくつかの特殊文字があり、ひとつひとつ指定しなくてもワイルドカードで特定のディレクトリ以下のファイル全て、のように指定することなどもできる。いくつか後述するが、より詳しくは:help cmdline-special参照。

このとき検索にヒットした箇所をQuickfixのerror listという形式で管理する。これがどういう風に使えるかは後述する「Quickfixと組み合わせる」を参照。

ちなみにvimには:grepもある。こちらはシステムのgrepコマンドをvimから呼び出すためのもの。詳細は:help :grepを参照。

vimgrepと/の違い

ところでvimで検索と言えば/(スラッシュ)もあるが一体何が違うのか。

  / vimgrep
正規表現使える yes yes
検索対象 カレントバッファ 引数で指定する
ヒット件数 分からない 分かる
次の検索結果へ移動する n :cn[ext]
前の検索結果へ移動する N :cN[ext] :cp[revious]
Quickfix使える no yes

/(スラッシュ)を使うと現在開いているファイルの中で検索できることは、vimmerなら当然知っているだろう。この現在開いているファイルをvimではカレントバッファ(current buffer)と呼ぶ。

/ではヒット件数は分からないが、vimgrepを使うと(1 of 5)のように表示されるので何件ヒットしたのかが一目で分かる。この場合は5ヶ所ヒットした内で1つ目の結果を表示していることを意味する。
:cnextを実行すればカーソルが次の位置に移動し(2 of 5)と表示される。

私はいちいち:cnextとか打つのがだるいので次のように設定している。qはQuickfixのqのつもり。

.vimrc
nnoremap [q :cprevious<CR>   " 前へ
nnoremap ]q :cnext<CR>       " 次へ
nnoremap [Q :<C-u>cfirst<CR> " 最初へ
nnoremap ]Q :<C-u>clast<CR>  " 最後へ

カレントバッファに対してvimgrepする

vimgrepはファイルを横断して検索できるのが便利だが、カレントバッファを検索するために/の代わりに使うのも有りだ。カレントバッファを対象にするには{file}%と指定する。
つまり/hogeの代わりに:vim hoge %を実行すれば、現在開いているファイルを対象にしたvimgrepになる。

" カレントバッファを対象にする
:vim {pattern} %

ワイルドカードで複数指定する

{file} ...で複数のファイルを指定できるとは言っても、一つ一つ列挙するなんてやっていられないだろう。それに特定のディレクトリ以下のファイルを対象に検索したい、ということが大半だ。

こういうときはワイルドカードを使う。以下の例を見ればだいたい雰囲気はつかめるだろう。より詳しくは:help wildcards参照。

" カレントディレクトリ以下のあらゆるファイルを対象にする
:vim {pattern} **
" app/views以下のあらゆるファイルを対象にする(ディレクトリを再帰的に検索)
:vim {pattern} app/views/**
" app/views/users内のファイルを対象にする
:vim {pattern} app/views/users/*
" app/views以下のerbファイルを対象にする
:vim {pattern} app/views/**/*.erb
" app/views以下で_で始まるerbファイルを対象にする
:vim {pattern} app/views/**/_*.erb

また{file}はカレントディレクトリからの相対パスで指定する。カレントディレクトリは:pwdで確認可能。また:cdでvim起動後に変更することも可能。

ワイルドカードで出来る限り絞り込むのはパフォーマンス的に重要だ。vimgrepで対象となったファイルは全て一旦vimによってメモリに載せられたのちに検索が実行される。不要なファイルを外しておくことでメモリ消費を抑えることができる。またvimgrepは通常のgrepコマンドやgit-grepよりは遅い。

何度も同じ検索対象を使う場合

同じ{file}に対して{pattern}を変えながら何度も検索する、という場合を考えてみる。例えば特定のディレクトリ内にアプリケーションコードが格納されており、その中のファイルだけを対象に検索したい、というような感じだ。愚直にやるなら次のような感じでコマンドを実行することになる。vimが多少は補完してくれるとは言っても、何度も何度もpath/to/search/dir/**と入力するのはつらすぎる。

:vim foo path/to/search/dir/**
:vim bar path/to/search/dir/**
:vim baz path/to/search/dir/**
:vim hoge path/to/search/dir/**
:vim piyo path/to/search/dir/**

こういう場合は:ar[gs]コマンドを使う。さきほどの例は次のように置き換えられる。つまるところ:argsで入力された値を##で指定できるわけだ。ちなみに:argsはvimが内部で管理しているargument listを置き換えるためのコマンドで、argument listは最初vim起動時にvimコマンドに引数で渡されたファイルが入っている。

:ar path/to/search/dir/**
:vim foo ##
:vim bar ##
:vim baz ##
:vim hoge ##
:vim piyo ##

この例を見れば分かるように:argsにはワイルドカードを指定できる。これはこれで極めて便利だが、シェルコマンドを指定してその標準出力を受け取ることも可能だ。これを利用するには`を使う。これは工夫次第で更に便利になる。後述する「Gitリポジトリで使う場合」などはその典型だ。

:ar `find . -name \\*.rb`

Gitリポジトリで使う場合

Gitプロジェクトで下記を実行するとインデックスされていないファイルも含めて全て検索される。

" gitリポジトリのルートでこれを実行してはいけない
:vim {pattern} **

Gitで管理しているプロジェクトではインデックスされているファイルだけを対象にしたいことが大半だ。
そういうときはgit-ls-filesと:argsを組み合わせる。git-ls-filesについて詳しくはman git-ls-filesを参照してほしいが、簡単な話がGitでインデックスされているファイルの一覧を標準出力する。パスを指定することで絞り込むこともできることも合わせて言っておく。こうすれば以後そのvimプロセス内では ## で必要なファイルに対して検索ができる。

標準出力を使ったファイルの指定は :vimgrep{file}にも指定できるので、一回だけならわざわざ:argsを使う必要はない。

" インデックスされている全てのファイルを対象にする
:vim {pattern} `git ls-files`

" appディレクトリ内でインデックスされているファイルを対象にする
:vim {pattern} `git ls-files app`

" appディレクトリ内でインデックスされている.htmlファイルを対象にする
:vim {pattern} `git ls-files app/**/*.html`

こうすればgit-grepするためにいちいちvimから出て行く必要はなくなる。

バッファされている全てのファイルに対して検索する

現在バッファに乗っているファイルを対象に検索したいという場合もあるだろう。そういうときは:bufdo:vimgrepa[dd]を組み合わせる。:bufdoは開いているバッファに対して後続のコマンドを実行して回るコマンド。:vimgrepaddは新しいQuickfixのリストを作るのでなく追加する。

:bufdo vimgrepa {pattern} %

複数ウィンドウを開いていて、それらを対象にしたいなら:bufdoの代わりに:windoを使う。また:vimgrepaddは追加するものであることから、連続実行すると前の検索結果が残ってしまう。一旦Quickfixをリセットするには:cex[pr]コマンドに””を渡す。

:cex ""

Quickfixと組み合わせる

vimgrepの基本的な使い方は掌握されたことだろう。

ところでvimgrepは検索結果を一覧表示できたら嬉しいとは思わないだろうか?:cnextを連打して一つ一つ見て回るのは、見たいのが検索結果の一部だった場合は特に、不毛だ。

ここで使うのが:cw[indow]コマンドだ。実行すると新しいウィンドウが作られ、そこにvimgrepの検索結果の一覧が表示される。一行が一件の検索結果に対応し、それぞれファイルのパス、ヒットした箇所のそのファイル内での位置、その行全体が表示される。
このウィンドウ上で特定の行へ移動しEnterを押すとその検索位置が表示されてカーソルがそこに移動する。このとき作られたウィンドウは消えずに残っているので、Ctrl-wを使って移動して改めて他の検索結果を選択することもできる。

vimgrepしてから:cwindowしたのでは二回コマンドを実行しなければならず若干面倒くさい。次のように一回で書いてしまうのがよい。以下を実行するといきなり検索結果一覧のウィンドウが表示され、カーソルがそのウィンドウの最初の行に移動する。

:vim {pattern} {file} | cw

ちなみにここで作られたウィンドウはvim用語ではquickfix-windowと呼ばれる。
QuickfixというのはとあるCコンパイラが出力するコンパイルエラー形式のこと(らしく)、それをファイルに保存してvimから読み込んでエラー箇所を簡単に飛べると嬉しいよね、という動機で作られた機能の総称。vimgrepの検索結果は自動的にこの形式で読み込まれるため、Quickfixの結果を一覧表示するquickfix-windowで一覧表示できる、というわけだ。
詳しくは:help quickfixを参照。あと:help 30.1なんかも読むとよい。

それとQuickfixは+quickfixなvimでないと使えないので注意。

自動的にquickfix-windowを開く

| cw を書くのが面倒くさい場合はvimがQuickfixにフックするためのイベントを用意しているので、それを使うとよい。
下記設定の場合:vimgrepに加えて:grep:Ggrepでも自動的にquickfix-windowを開くようになる。

.vimrc
autocmd QuickFixCmdPost *grep* cwindow

まとめ

vimgrepを使えばvimから出ること無くファイルを横断して検索することができる。もうCtrl-Zで抜けてfgで戻るなどという人生の浪費をしなくてよいのだ。