How to exclude binary file or directory in Vim internal/external grep
テキストエディターVimでは,起動中に内部grep(:vimgrep
)と外部grep(:grep
)により複数のファイルから文字列を検索できる。この検索からバイナリーファイルや特定のディレクトリを除外する方法をまとめる。
大量のファイルから特定文字列を探していると,検索に時間がかかり,不要なマッチがあると重要なマッチが埋もれてしまうので,無関係のファイルは検索対象から除外したい。特に,バイナリーファイルや,.git
や.svn
などのバージョン管理のためのディレクトリは,文字列を検索する文字列を検索する意味がないので検索対象から除外したい。
調べてみたが,あまりまとまった情報がなかったのでまとめてみた。設定一覧は最後のまとめの節に掲載している。
目次
内部grepと外部grep
まず,Vimの内部grep(:vimgrep
)と外部grep(:grep
)について簡単に説明する。
内部grep(internal grep,:vimgrep
)とは,Vimの内部コマンドによるgrep検索のことである。利点と欠点は以下となる。
- 利点
-
- Vimで直接ファイルを一つずつ開いて検索をかけるため,Vimがもつ文字エンコーディングの自動判別や正規表現が使える。
- Vim標準機能であるため,どのOSでも利用可能で,操作や結果も同じ。
- 検索後の問い合わせがなく検索結果にシームレスにアクセスできる。
- 欠点
-
- ファイル数が増えると検索に時間がかかる。
外部grep(external grep,:grep
)とは,Vimから外部のコマンドを使用するgrep検索のことである。既定ではUnix系OSではgrep
コマンドが使われ,Windowsだとfindstr
コマンドが検索に使われる。利点と欠点は以下となる。
- 利点
-
- 外部の専用検索コマンドを使うことで,ファイル数が多くても検索速度が早い。
- 欠点
-
- 外部コマンドに依存するため,OSによりコマンドや操作・結果に若干の違いが生じる。
- 文字エンコーディングの自動判定などの機能が内部grepよりは貧弱。
- 検索実行後,Press Enter or type commmand to continue問い合わせメッセージが表示されるので,頻繁に検索を実行する場合煩わしい。
使ってみた印象だと,検索対象ファイル数が数十ファイルを越えるなら,確実に外部grepを使ったほうがよい。ただ,外部grepを使うと,検索後に検索結果一覧を表示してメッセージが表示される。頻繁に検索を繰り返す場合,これが煩わしく感じるかもしれないので,検索対象ファイル数が少なければ,内部grepでもよい。
Vimからgrepを使うことで,Vimを離れることなく検索したファイルにアクセスができる。QuickFix-windowと組み合わせることで,大量のファイルから該当文字列を含むファイルだけをつぎつぎとアクセスでき,とても作業効率がよくなる。
内部grep(:vimgrep)
検索対象外ファイル・ディレクトリの設定
Vimの内部grep(:vimgrep
)でバイナリーファイルやディレクトリを検索対象外にするには,wildignore
で設定する(参照:5.1 Vimの内部grepの使い方 – quickfix – Vim日本語ドキュメント)。
設定はVimの正規表現の形式で設定する。例えば,ファイルを対象外にするには以下の通りに拡張子で指定したり,ファイル名で指定する。
set wildignore=*.dll,*.exe,tags
:vimgrep
ではファイルを自動的に判別して,バイナリーファイルを検索対象外にすることはできないようなので,考えられるバイナリーファイルの拡張子を複数指定して検索対象外にする。
ディレクトリを対象外にするには,vimやbash,zshで特有の正規表現である**
を使い,ディレクトリ配下の全ファイルを無視するように指定する(参考:vim – How to exclude file patterns in vimgrep? – Stack Overflow)。
set wildignore+=obj/**,.git/**,.svn/**
wildignore設定時の注意点
wildignore
で値を設定するときは,help: wildignore
で以下のとおりにすることが推奨されている。
リストにパターンを追加するときにはコマンド |:set+=|、リストからパターンを除くときにはコマンド |:set-=| を使うのがよい。こうすると将来のバージョンで異なった既定値が使われるようになったときに、問題が起きるのを防げる。
上記の通り,設定するときは既定の設定を壊さないように,:set+=
で追加し,:set-=
で解除するのがよいだろう。
併せて,「内部grepでバイナリファイルを対象外にする – 座敷牢日誌」で言及されているように,wildignore
の設定は,Vimのgrep以外にも影響を与えるので,そのまま設定するのは危険だ。
前述の記事でなされている通り,Vimでgrep検索するときに発生するイベントであるquickfixをautocmd
で捕捉する。:vimgrep
を使うときだけwildignore
で検索対象外ファイルをsetlocal
で追加設定し,grep検索が終了したらすぐにwildignore
で追加した項目を除外するのがよいだろう。
例えば,以下のように設定することになるだろう。
autocmd QuickFixCmdPre *
\ setlocal wildignore+=*.o,*.obj,*.exe,*.dll,*.bin,*.so,*.a,*.out,*.jar,*.pak
\| setlocal wildignore+=*.zip,*gz,*.xz,*.bz2,*.7z,*.lha,*.deb,*.rpm
\| setlocal wildignore+=*.pdf,*.png,*.jpg,*.gif,*.bmp,*.doc*,*.xls*,*.ppt*
autocmd QuickFixCmdPost *
\ setlocal wildignore-=*.o,*.obj,*.exe,*.dll,*.bin,*.so,*.a,*.out,*.jar,*.pak
\| setlocal wildignore-=*.zip,*gz,*.xz,*.bz2,*.7z,*.lha,*.deb,*.rpm
\| setlocal wildignore-=*.pdf,*.png,*.jpg,*.gif,*.bmp,*.doc*,*.xls*,*.ppt*
Vim script構文を使ったスマートな設定方法
上記で一応設定できたが,この書き方だと,Vimのgrepでの検索前後であるQuickFixCmdPre
とQuickFixCmdPost
とで,2回wildignore
を設定する必要がある。片方への書き忘れがあったり,検索対象外ファイルが増えてきたら行数が増えるので,賢いやり方ではないだろう。Vim scriptの構文を少し使えばスマートに設定できる。
以下の通りに,スクリプトローカル変数s:ignore_listに検索対象外ファイルを記述し,execute
で文字列を実行してやれば検索対象外ファイルの記述の重複がなくなる。それに加えて,古いVimではwildignroe
が使えないこともあるので,Vimのwildignore
機能が有効なときだけwildignore
を設定するようにすれば完璧だろう。
"" vim grep
""" ignored files in vimgrep
let s:ignore_list = ',.git/**,.svn/**,obj/**'
let s:ignore_list .= ',tags,GTAGS,GRTAGS,GPATH'
let s:ignore_list .= ',*.o,*.obj,*.exe,*.dll,*.bin,*.so,*.a,*.out,*.jar,*.pak'
let s:ignore_list .= ',*.zip,*gz,*.xz,*.bz2,*.7z,*.lha,*.lzh,*.deb,*.rpm,*.iso'
let s:ignore_list .= ',*.pdf,*.png,*.jp*,*.gif,*.bmp,*.mp*'
let s:ignore_list .= ',*.od*,*.doc*,*.xls*,*.ppt*'
if exists('+wildignore')
autocmd QuickFixCmdPre * execute 'setlocal wildignore+=' . s:ignore_list
autocmd QuickFixCmdPost * execute 'setlocal wildignore-=' . s:ignore_list
endif
vimgrepで気づいたこと
Vimの内部grepである:vimgrep
を試していて気づいたことがあるので参考までに記載しておく。
Linuxのgrep
コマンドでは,とくに指定なければ,検索対象ファイルに隠しファイル.*
を含めて再帰的に検索すると,1階層上を示す..
が検索対象ディレクトリにヒットしてしまい,ファイルシステム全体を検索することになって危険だった。
grep -r "pattern" .* *
これを防ぐには,正規表現で..
を除外させるか,オプション--exclude-dir
で..
を除外すれば回避できた(参考:My Future Sight for Past: How to match grep command for all files including hidden files)。
grep -r "pattern" .[!.]* *
grep -r "pattern" .* * --exlucde-dir=..
しかし,:vimgrep
では以下の通りに検索してもヒットするのは1階層上のディレクトリ配下までであり,ファイルシステム全体を検索してしまうようなことはなかった。:vimgrep
の方がgrep
コマンドより安全にできているのかもしれない。
:vimgrep 'pattern' .**/
外部grep(:grep)
外部grep(:grep
)でバイナリーファイルやディレクトリを検索対象外にするには,grepprg
を設定する。grepprg
は外部grepを使うときに実際に実行されるコマンドとオプションとなっている(参考:grepprg – options – Vim日本語ドキュメント)。
grepprg
の既定値はOSごとに以下となっている。
OS | 既定値 |
---|---|
Unix | "grep -n $* /dev/null" |
Win32 | "findstr /n" か"grep -n" |
このように,OSごとに使われるコマンドが異なるので,設定を分岐させる必要がある。grep
コマンドはPOSIXでも規定されており,LinuxやMac,WindowsにおいてもCygwinやMSYS2など使える可能性が高い。そこで,grep
コマンドが存在すれば,grep
コマンドのオプションとして設定し,そうでなければWindowsとみなしてfindstr
コマンドのオプションとして設定するのがよいだろう。
if executable('grep')
set grepprg=grep\ -n
else
set grepprg=findstr\ /n\ /p
endif
findstr(Windows)の設定
Windowsでは標準で外部grepコマンドにfindstr
コマンドが使われる。
このコマンドはWindowsにおけるgrep
コマンドに相当するもので,正規表現を使ってファイル内の文字列を検索できる。しかし,grep
と異なり検索対象外のファイルやディレクトリやを設定することはできない。
しかし,/p
オプションをつけることで,バイナリファイルを検索対象外ファイルに指定できる。したがって,grepprg
には以下のとおりに設定するのが最善だろう。
set grepprg=findstr\ /n\ /p
その他,findstr
コマンドではディレクトリを再帰的に検索するのに/s
オプションを使うなど,grep
とはコマンドの体系が異なっている。ヘルプを確認して使い方を習熟しておく必要があるだろう。
個人的には,findstr
コマンドを使うくらいなら,コマンド体系を統一できる内部grepである:vimgrep
コマンドを優先したい。
GNU grepコマンドのオプション
Windows以外であれば,基本的には外部grep(:grep
)にはgrep
コマンドが使われる。POSIXで規定されているgrepには検索対象外のファイルやディレクトリを指定できないが,多くの環境で使われていると思われるGNU grepであれば,オプションが用意されている(参照:Man page of GREP)。
オプション | 説明 |
---|---|
–binary-files=without-match, -I | バイナリーファイルを無視する。 |
–exclude-dir | 指定したディレクトリを無視する。 |
–exclude | 指定したファイルを無視する。ファイルはパスなしのファイル名であるベースネームのみで判定する。 |
–exclude-dirと–exludeではマッチングにglobまたはワイルドカードとして,*
,?
,[...]
が使える。また,一度に複数の項目を設定するには,値を波括弧{}
で囲み,項目はカンマ,
で区切る。または,–exclude-dirなどのオプションを複数回繰り返して指定してもよい。ただし,波括弧で囲めるのは値が複数個あるときだけ。1個しかないのに波括弧を使うと,その設定は無視される。値はでも有効なので,
をつけよう。なお,波括弧を引用符で囲むと波括弧自体が文字列として解釈されるので注意しよう。
grep "pattern" * --exclude-dir={..,.git}
grep "pattern" * --exclude-dir=.. --exclude-dir=.git
grep "pattern" * --exclude-dir={..} # これは無視される
grep "pattern" * --exclude-dir={,..} # これはOK
grep "pattern" * --exclude-dir="{,..}" # これはNG
例えば,以下のように設定すればよいだろう。
set grepprg=grep\ -n\ -I\ --exlucde-dir={..,.git,.svn,.obj} --exclude={tags,GTAGS,RTAGS,GPATH}
なお,--exclude-dir
オプションはGNU grep 2.5.2から導入されたらしく,それ以前のgrepでは–excludeを使えば代用できるとの情報を見かけた(参考:grepで.svnディレクトリを除外して再帰検索 – 日々の報告書)。しかし,--exclude=*.svn
などとしてもディレクトリを除外できなかったので,2.5.2以降のバージョンでは--exclude
オプションの仕様が変わったのかもしれない。
Unixでのgrepprgの既定値の/dev/null
Unixでのgrepprg
の既定値は,以下となっている(参考:grepprg – options – Vim日本語ドキュメント)。
grepprg=grep\ -n\ $*\ /dev/null
grep
では検索対象を指定しなければ,標準入力から読み込まれるとみなされ動作が停止してかのようにみえてしまう。Unixだけ後ろに$*\ /dev/null
が付いているのは,検索対象の指定を忘れたときの予防のためだろう。検索対象に/dev/null
が指定されるようにすることで,標準入力からの入力を待ち続けることを回避できる。
しかし,この指定があると通常のgrep
と挙動が変わってくる。それは,-r
オプションで再帰的にディレクトリを指定するときだ。
echo "backup" > a.sh
## これはヒット
grep -r --include=*.sh backup
## vim
vim
## これはヒットしない
:grep -r --include=*.sh backup
grepprg=grep\ -n\ $*\ /dev/null
となっていれば,ディレクトリに/dev/null
が指定されたとみなされ,現在ディレクトリを探しにいかない(参考:
Differences when using grep in terminal and :grep in vim – Stack Overflow)。
grepprg
の既定値から$*\ /dev/null
を削除した。また,次の節で説明するが:grep
の実行後のQuickFix-Windowにおいて,ステータスラインに表示されるスペースを省略するためにも有効だ。
Vimの外部grep(:grep)とシェルのgrepコマンドのオプションの共通化
grepprg
の値としてgrepコマンドを使う時の設定例として以下を掲載した。
set grepprg=grep\ -n\ -I\ --exlucde-dir={..,.git,.svn,.obj} --exclude={tags,GTAGS,RTAGS,GPATH}
これには以下2点の問題がある。
:grep
実行後のQuickFix-Windowでのステータスラインの逼迫。- 通常の
grep
コマンドとVimの外部grepとで設定の重複。
1.の:grep
実行後のステータスラインが逼迫されることについて説明する。
:grep
を実行後のQuickFix-Windowでは,検索コマンドがステータスラインに表示される。しかし,grepprg
が長いと画面に入りきらず,一部が省略されて表示されてしまう。例えば以下の画像のように先頭部分が>
で省略されてしまっている。見た感じが悪いのでできれば回避したい。
2.について説明する。基本的には検索対象外ファイルの設定は通常のgrep
コマンドでもVimの:grep
でも同じ設定になるはずだ。片方の設定忘れなど保守が悪いので,共通化したい。
この2点の問題を解決する方法がある。それは,以下だ。
grep
にオプションを追加したaliasを設定しておいて,vimから使えるようにする。
こうすれば,Vimのgrepprg
で設定する必要がなくなりbash
などと設定を共通化できる。また,ステータスラインの表示スペースも節約できる。
Vimからシェルのaliasを使う方法は「My Future Sight for Past: Use of alias for Vim external command」に詳しくまとめている。
例えば,~/.bashenv
ファイル(~/.bashrc
でも可)に共通設定を記入を記入しておき,そのファイルをVim起動中に環境変数BASH_ENVに設定する。
## .bashenv
## grep 2.21 later, deprecated GREP_OPTIONS environmental variable
GREP_OPTIONS="--color=auto -I --exclude-dir={.,..,.git,.svn,obj}"
GREP_OPTIONS="${GREP_OPTIONS} --exclude={tags,GTAGS,GRTAGS,GPATH}"
alias grep="grep ${GREP_OPTIONS}"
## .vimrc
if executable('grep')
"" シェルと共通化させない場合の設定
" set grepprg=grep\ -n\ -I\ --exlucde-dir={..,.git,.svn,.obj} --exclude={tags,GTAGS,GRTAGS,GPATH}
set grepprg=grep\ -n
else
"" for Windows
set grepprg=findstr\ /n\ /p
endif
"" Enable alias for external command
if filereadable(glob('~/.bashenv')) | let $BASH_ENV=expand('~/.bashenv') | endif
こうすることで,grep
のオプションをVimとシェルとで共通化でき,ステータスラインのスペースも確保できる。
まとめ
Vimの内部grep(:vimgrep
)と外部grep(:grep
)でバイナリーファイルや特定のディレクトリを検索対象外にする方法をまとめた。これでVimでファイルを検索するのが快適になるだろう。
最後に,今回の設定をまとめて掲載する。
## .vimrc
"" vim grep
""" ignored files in vimgrep
let s:ignore_list = ',.git/**,.svn/**,obj/**'
let s:ignore_list .= ',tags,GTAGS,GRTAGS,GPATH'
let s:ignore_list .= ',*.o,*.obj,*.exe,*.dll,*.bin,*.so,*.a,*.out,*.jar,*.pak'
let s:ignore_list .= ',*.zip,*gz,*.xz,*.bz2,*.7z,*.lha,*.lzh,*.deb,*.rpm,*.iso'
let s:ignore_list .= ',*.pdf,*.png,*.jp*,*.gif,*.bmp,*.mp*'
let s:ignore_list .= ',*.od*,*.doc*,*.xls*,*.ppt*'
if exists('+wildignore')
autocmd QuickFixCmdPre * execute 'setlocal wildignore+=' . s:ignore_list
autocmd QuickFixCmdPost * execute 'setlocal wildignore-=' . s:ignore_list
endif
if executable('grep')
"" シェルと共通化させない場合の設定
" set grepprg=grep\ -n\ -I\ --exlucde-dir={..,.git,.svn,.obj} --exclude={tags,GTAGS,GRTAGS,GPATH}
set grepprg=grep\ -n
else
"" for Windows
set grepprg=findstr\ /n\ /p
endif
"" Enable alias for external command
if filereadable(glob('~/.bashenv')) | let $BASH_ENV=expand('~/.bashenv') | endif
## .bashenv
## grep 2.21 later, deprecated GREP_OPTIONS environmental variable
GREP_OPTIONS="--color=auto -I --exclude-dir={.,..,.git,.svn,obj}"
GREP_OPTIONS="${GREP_OPTIONS} --exclude={tags,GTAGS,GRTAGS,GPATH}"
alias grep="grep ${GREP_OPTIONS}"