How to match grep command for all files including hidden files
grep
コマンドでドット(.)で始まる隠しファイルを含む全ファイルを検索対象にするには,以下のどれかの書式を使う。
grep -r "pattern" .[!.]* *
grep -r "pattern" .* * --exlucde-dir=..
## for bash dotglob and zsh glob_dots option enabled
grep -r "pattern" .[!.]* [!.]*
grep -r "pattern" * --exclude-dir=..
Introduction
ファイル内の文字列を検索するgrepコマンドをよく使う。ファイル名のパターンマッチであるglobを活用し,ワイルドカードの*
と組み合わせて大量のファイルから文字列を検索できる。
しかし,globの*
では.
から始まる隠しファイルは検索対象にならない。つまり,以下ではドット(.
)から始まる.foo
,.baz/baz
,.baz/.baz
は検索にヒットしない。
mkdir -p bar .baz
echo "1. file dot" > foo
echo "2. file dot foo" > .foo
echo "3. file bar/dot bar" > bar/bar
echo "4. file bar/dot bar" > bar/.bar
echo "5. file dot baz/baz" > .baz/baz
echo "6. file dot baz/dot baz" > .baz/.baz
grep -r "file" *
bar/bar:3. file bar/dot bar bar/.bar:4. file bar/dot bar foo:1. file dot
globの*
でこれらの隠しファイルにマッチさせる方法は2通りある。
grepの隠しファイルへのマッチ方法
シェル機能を活用
bashとzshでは,ドット(.)から始まる隠しファイルにもglobの*
がマッチできるようにするオプションがある。
以下のコマンドで機能をオンにできる。
## bash (~/.bashrc)
shopt -s dotglob
## zsh (~/.zshrc)
setopt glob_dots
grep -r "file" *
.baz/baz:5. file dot baz/baz .baz/.baz:6. file dot baz/dot baz .foo:2. file dot foo bar/bar:3. file bar/dot bar bar/.bar:4. file bar/dot bar foo:1. file dot
ただ,これらの機能を設定ファイルで有効にしておくと,ターミナルからコマンドを入力したり,シェルスクリプトにも影響を与えるリスクがある。そのため,あまりこれらの機能を前提にしないほうがよいだろう。
ワイルドカードを駆使
シェルの設定に頼らずともワイルドカードを駆使すれば隠しファイルも検索対象に含めることはできる。例えば,.
から始まるファイルは以下でマッチする。
grep -r "file" .*
しかし,上記コマンドは以下2点の理由から実行しないほうがいい。
.*
は1階層上のディレクトリを意味する..
にもマッチし,ファイルシステム全体を再帰的に検索してしまう。- 隠しファイル以外のファイルにマッチしない。
この2点をカバーするには,以下のように入力する。
grep -r "file" .[!.]* *
仕組みは以下のとおりだ。
- 最初の
.[!.]*
で..
を除く,.
で始まる隠しファイルを検索対象とする。 - 最後の
*
で.
で始まらないファイル(通常のglobの*
の動作)を検索対象とする。
なお,前述の..
にマッチしてしまい,ファイルシステム全体を再帰的に検索することを回避するためにはgrepの--exclude-dir
オプションで..
を指定してもよい。
grep -r "file" .* * --exclude-dir=..
トラブルシューティング
bashのdotglobとzshのglob_dots有効時の不具合
grepでは検索対象を何回も指定できるが,間違えるとその分重複も発生するので注意する。つまり,ここでbashのdotglob
のやzshのglob_dots
を有効にしていると不都合が起きる。これらの機能を有効にすると,*
が隠しファイルにもマッチするので,.
で始まる隠しファイルが2回マッチしてしまう。
例えば,以下の.a.dat
ファイルには2回ヒットする。
bash
shopt -s dotglob
echo "return" > .a.dat
grep -r "return" .[!.]* *
.a.dat:return
.a.dat:return
これを避けるには,grepの最後の*
で隠しファイルを明示的に除外する。
grep -r "return" .[!.]* [!.]*
.a.dat:return
補足だが,grepでは-e
オプションで一度に複数のキーワードを検索(OR検索)できる。
grep -r -e "pattern1" .[!.]* * -e "pattern2"
POSIXでの[^...]
は未定義
当初,以下の様にglobでのパターンマッチングに!
ではなく^
を使っていた。bashとzshでは動作したが,sh
でうまく動作しなかった。
grep -r "file" .[^.]* [^.]*
気になって確認したところ,POSIXではワイルドカードにおける開き角括弧[
の開始のサーカムフレックス^
の動作([^...]
)は未定義のようなので,使用を控えたほうがよいだろう。
A bracket expression starting with an unquoted <circumflex> character produces unspecified results.
まとめ
元々はVimでのgrep検索について調べていたのだが,気づいたらgrep検索についてはまってしまっていた。時間を余計に使ってしまったが,POSIXの仕様も確認できて勉強になった。
冒頭でも記載した通り,現在ディレクトリ以下の隠しファイルを含む全ファイルを対象に検索したければ,以下のどちらかの書式を使う。
grep -r "pattern" .[!.]* *
## for bash dotglob and zsh glob_dots option enabled
grep -r "pattern" .[!.]* [!.]*
2番目の書式の方がbashのdotglob
とzshのglob_dots
オプションに依存しないので,より確実だが,これらのオプションを考慮するのはやりすぎなような気もする。お好みで選べばよいだろう。
参考:
.@senopen grepで一回で隠しファイルも含めて全てのファイルを検索対象にする方法を思いついた。
— せのぺん (@senopen) 2016年5月24日
grep -r hoge .[^.]* [^.]*
..を含むと再帰してとんでもないことになるので省かないといけない。
少し考えたらすぐに出てくるような内容だけどね。