Linux でファイルやフォルダを検索したい時は find コマンドを使うと思います。
たとえばカレントディレクトリ以下の全てのファルを検索するなら下記のようにしますね。
$ find ./ -type f
./foo/hoge.txt
./bar/fuga.txt
./bar/piyo.txt
ディレクトリを検索したいなら下記の通り。
$ find ./ -type d
./foo
./bar
すごく便利なんですが、git のワークツリーで実行した場合にすこし困ったことになります。
Git の管理ディレクトリである .git まで検索してしまいます。
$ 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 が含まれていません。期待通りの挙動ですね。
$ 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 にしました、同じ意味です。
$ 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 は必要ないように見えます。
$ find . -path ./.git -prune -o -type f -print
しかし -print を削除すると下記のように除外しようとした .git ディレクトリが表示されてしまいます。
$ 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 を使えば実現できます。これをベースにいろいろ複雑な処理にも拡張できると思います。
FYI: man find の日本語訳
下記の翻訳が大変参考になりました。
https://linuxjm.osdn.jp/html/GNU_findutils/man1/find.1.html
コメント