find コマンドを使うときに、.gitのようなディレクトリは検索対象に入れたくないです。 そこで特定のディレクトリを対象から外す方法を説明します。


普通のfindの使い方

カレントディレクトリ以下のすべてのファイルを検索するなら下記のようにしますね。

1
2
3
4
$ find ./ -type f
./foo/hoge.txt
./bar/fuga.txt
./bar/piyo.txt

ディレクトリを検索したいなら下記の通り。

1
2
3
$ find ./ -type d
./foo
./bar

すごく便利なんですが、git のワークツリーで実行した場合にすこし困ったことになります。 Gitの管理ディレクトリである .git まで検索してしまいます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ git clone https://github.com/nagayasu-shinya/gnu-make-framework-zen.git
$ cd gnu-make-framework-zen.git
$ find ./ -type f
./LICENSE.txt
./README.md
./.git/hooks/pre-push.sample
./.git/hooks/pre-rebase.sample
./.git/hooks/pre-receive.sample
./.git/hooks/applypatch-msg.sample
./.git/hooks/update.sample
./.git/hooks/post-update.sample
./.git/hooks/prepare-commit-msg.sample
./.git/hooks/pre-commit.sample
# 略....

.git ディレクトリは管理ファイルの場所なので通常は直接触ることはないと思います。 そこで find.git を除外する方法を説明します。

-path オプションと -prune オプション

探索パスを指定するオプション -path と、真を返したうえでディレクトリの中へ入っていかないようにする -prune オプションを使います。下記のようになります。.git が含まれていません。期待通りの挙動ですね。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ find . -path ./.git -prune -o -type f -print
./LICENSE.txt
./README.md
./README_jp.md
./GNUmakefile
./build/create_executable.mk
./build/create_library.mk
./build/define_suffix_rule.mk
./build/define_macro.mk
./build/set_toolchain.mk
./build/clean_all.mk
./build/clear_local_variable.mk
# 略

上記のコマンドを少しわかりやすくしてみました。区切りがわかりやすいようにカッコで囲みました。あと -o-or にしました、同じ意味です。

1
find . \( -path ./.git -prune \) -or \( -type f -print \)

まず最初のカッコの中を見てみます。これは「パス ./.gitの中を除外する」という意味です。 -prune が除外するという意味です。 man の説明では “do not descend into it” となっていました。 またこの prune が実行されたとき(除外したとき)は真を返します。

つぎは -orです。論理和なので、C言語などと同様に左の式が真ならその時点で真が確定するので、右の式は実行されません。 先程述べたようにprune は除外した場合は真を返すので .git 以下は pruneによって除外され真を返します。 そのためその時点で真が確定するので右の式は実行されません。

一方pruneで除外されなかった場合は左の式が偽になるので右の式が評価されます。 結局、pruneで除外されなかったファイルだけが右の式にてファイル名表示されます。 これで期待の結果が得られます。

右の -print は必要なの?

さきほど見た下記のコマンドですが、ぱっと見、一番右の -print は必要ないように見えます。

1
find . -path ./.git -prune -o -type f -print

しかし -print を削除すると下記のように除外しようとした .gitディレクトリが表示されてしまいます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ find . -path ./.git -prune -o -type f
./LICENSE.txt
./README.md
./.git ★ ← なぜか表示される!!!
./README_jp.md
./GNUmakefile
./build/create_executable.mk
./build/create_library.mk
./build/define_suffix_rule.mk
./build/define_macro.mk
./build/set_toolchain.mk
./build/clean_all.mk
# 略

この理由は man ページに記載がありました。 式 EXPRESSION の章に「-prune-quit 以外のアクションが存在しない限り、式全体の結果が真になったすべてのファイルに対して -print が実行される」と記載があります。 先程のコマンド例の場合-path-type はアクションではないので 「-prune-quit以外のアクションが存在しない」ことになり、真となったファイルすべてに -print が実行されることになります。

ここで思い出してほしいのは「pruneは真を返す」ということです。 つまりprune で除外された .gitも真となるので、結局 .git も表示されてしまうわけです。 これを回避するためには「-prune-quit以外のアクションが存在」すればいいので、 明示的に -print をつけているわけですね。

まとめ

今回は find で検索するときに .gitディレクトリを除外する方法を説明しました。 オプションの -path-prune さらに -printを使えば実現できます。 これをベースにいろいろ複雑な処理にも拡張できると思います。