findコマンドで特定のディレクトリ以下を無視する方法

なんかCLIマジック:使って役立つワンライナー入門なんてのがあって気分が盛り上がってきたので、僕もワンライナーっぽいのを書いてみるよ。

困ったこと

例えば、カレントディレクトリ以下にあるファイルの名前を全部見たいとき、素直にfindコマンドを使うとこうなるね。

find . -type f

でも出力はこうなる。

./locale/.svn/entries
./locale/.svn/format
./locale/ja/switch_page_locale.dtd
./locale/ja/.svn/entries
./locale/ja/.svn/format
./locale/ja/.svn/text-base/switch_page_locale.dtd.svn-base
./locale/en-US/switch_page_locale.dtd
./locale/en-US/.svn/entries
./locale/en-US/.svn/format
./locale/en-US/.svn/text-base/switch_page_locale.dtd.svn-base
./content/overlay.xul
./content/switch_page_locale.js
./content/.svn/entries
./content/.svn/format
./content/.svn/text-base/switch_page_locale.js.svn-base
./content/.svn/text-base/overlay.xul.svn-base
./content/.svn/prop-base/switch_page_locale.js.svn-base
./content/.svn/prop-base/overlay.xul.svn-base
./.svn/entries
./.svn/format

わっ、余計なのが出てきた。.svn ディレクトリ以下はSubversionが勝手に作るファイルなので、出力してほしくないよ。こんな時のfindコマンドの使い方メモ。

解答例

これでOK。

find . -type d -name '.svn' -prune -o -type f -print

出力はこうなる。

./locale/ja/switch_page_locale.dtd
./locale/en-US/switch_page_locale.dtd
./content/overlay.xul
./content/switch_page_locale.js

うん。 ./locale/.svn/entries とかが出てきてない。いい感じだ。

解説

まず、カッコをつけて優先順位をはっきりさせてみよう。

find . \( -type d -and -name '.svn' -and -prune \) -or \( -type f -and -print \)

or の方が優先順位が低いんだね。ついでに -o を -or にして、-and を補った。で、findの評価の仕方だけど、プログラム言語によくある評価の仕方と同じだ。

つまり、A -and B -and C -and ... は、

  • 「A, B, C と左から順に評価(実行)する。結果が真である限り次々と右側にうつる。一つでも偽だったらその時点で終わり」
  • 「全体としての評価結果は、A, B, C ... のうち最後に実行したものの結果」

A -or B -or C -or ... は、

  • 「A, B, C ... と左から順に実行する。結果が偽である限り次々と右側にうつる。一つでも真だったらその時点で終わり」
  • 「全体としての評価結果は、A, B, C ... のうち最後に実行したものの結果(andと同じ)」

となる。こういうのをショートサーキットとも言うね。

さらに、-prune というのは「評価結果は真で、それより下にディレクトリを降りない」という意味だ。

なので、左側のカッコの部分は「'.svn'という名前のディレクトリだったら真で、その下には降りない」という意味になる。-or でつないでいるので、もし本当に'.svn'だったらその時点で終わり(ファイル名出力もしない)、ということになる。

そうでない場合は右側のカッコにうつる。こっちはシンプルで、単に(ディレクトリとかではなく)ファイルだったらその名前を出力するだけだ。

これで'.svn'以下を無視して、それ以外のファイルの名前を出力することになる。ついでにその下には降りなくなるので、余計な時間もかからなくてうれしい。

別解

これでもOK。

find . -path '*.svn' -prune -o -type f -print

悪い例

色々試してたんだけど、以下はダメだった。

find . -type d -name '.svn' -prune -o -type f

要するに最後の-printを除いただけなんだけど、出力はこうなる。

./locale/.svn
./locale/ja/switch_page_locale.dtd
./locale/ja/.svn
./locale/en-US/switch_page_locale.dtd
./locale/en-US/.svn
./content/overlay.xul
./content/switch_page_locale.js
./content/.svn
./.svn

おしいけど ./locale/.svn とかが入ってる。最初なぜだろうと思ったけど、manを見たらその理由がわかったよ。

prune以外のアクションが評価式に含まれていない場合は、評価式の結果が真となったファイルに対して -print が実行される。

Manpage of FIND

むぅ、なんかまわりくどい表現だ。意味わかりにくいけど要するにこういうこと。

  1. 評価式の中にアクションが全く含まれていない場合、評価式の結果が真になったファイルに対しては-printが実行される。
  2. 評価式の中に含まれるアクションがpruneだけだった場合も同様で、評価式の結果が真になったファイルに対しては-printが実行される。

ダメな例は2番目の場合に含まれて、./locale/.svn みたいなファイルも真になるので出力されてしまったんだよ(type, name とかはアクションじゃない)。

というわけで、要するにfindコマンド分かりにくいね、って話でした。