f

アーカイブ

2016-05-24

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通りある。

  1. bashのdotglobやzshのglob_dotsなどシェル機能を活用。
  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. .*は1階層上のディレクトリを意味する..にもマッチし,ファイルシステム全体を再帰的に検索してしまう。
  2. 隠しファイル以外のファイルにマッチしない。

この2点をカバーするには,以下のように入力する。

grep -r "file" .[!.]* *

仕組みは以下のとおりだ。

  1. 最初の.[!.]*..を除く,.で始まる隠しファイルを検索対象とする。
  2. 最後の*.で始まらないファイル(通常の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.

Shell Command Language

まとめ

元々は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オプションに依存しないので,より確実だが,これらのオプションを考慮するのはやりすぎなような気もする。お好みで選べばよいだろう。

参考:

0 件のコメント:

コメントを投稿