特定ディレクトリ以下を除いた find をする方法

find . -name "foo" -prune -o -print

同僚から、あるディレクトリ配下で特定ディレクトリ配下を排除したファイルリストが欲しい、何か方法はない? と訊かれた。find の !使えば良いじゃんと思ったが、前提として環境はAIX*1だ。-pathオプションがない。-pathオプションがあったとしても、find . ! -path "*/foo*"では排除したくないfoobarディレクトリも排除してしまう。
悩みつつはじき出したのがこれ。
まぁ悩んだと言っても実はManpage of FIND-pathの項に書いてあったのを使っただけだが。ただ、上記例でうまくいく理由を考えることででfindの奥深さを味わった。思わぬ課題を出してくれた同僚に感謝。

findの奥深さ

-pruneというのは条件に一致したディレクトリ配下は検索しないオプションだ。ただし、そのディレクトリ(上記例ではfooディレクトリ)自体が排除されるわけではない。
find . -name "foo" -pruneを実行するとfooディレクトリとファイル*2のみのリストとなる。
では、find . -name "foo" -prune -o -printの場合、fooディレクトリとそのディレクトリ配下以外のファイルと-name "foo"の条件に偽となったもののリストとして表示される様に思える。が、しかし、そうはならない。fooディレクトリとその配下を排除したリストとなる(つまりfooディレクトリが表示されない)。
なぜか。
man find の冒頭にある

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

が答えだ。
find . -name "foo"というコマンドで上記を考えると、アクションに属するオプション指定が無いので内部的にはfind . -name "foo" -printとなる。逆に-print等のアクションの指定がなければ、評価が真であっても出力されないと考えられる。
もう一度find . -name "foo" -prune -o -printを見ると-printが明示されている。よって、-name "foo" -pruneには暗黙的な-printが付加されず、出力されない。
find . -name "foo" -prune -o -name "*"だと、暗黙的に-printが付加されて、fooディレクトリも表示される。

ふぅ、言葉にすると長ったらしいな。

まとめ

  • アクション指定がない場合は-printが暗黙的に付加される
  • 特定ディレクトリとその配下を排除したい場合はfind . \( -name .svn -o -name .CVS \) -prune -o -printでOK

余談

300ものブックマークが付いているけど、コマンド例に若干の不安が。。。

できることならxargsを使え
find /tmp -type f -exec rm -f {} \;

じゃなくて

find /tmp -type f -print0 | xargs -0 /usr/bin/rm

排除したいのは.svnディレクトリじゃなくて?

find . -type f -ctime -30 -not -regex ‘.*svn.*' -exec ls {} -al \;

svnと付くもの全てがlsされなくなるけど、本当にそれで良いの?(該当ページの説明は間違っていないが、よく使う事例からは外れる気がする)
ついでに、どうでも良いことかもしれないが、lsの-aオプションは無駄だろ

*1:IBMUNIX

*2:-pruneがあるのでfooディレクトリ配下のfooファイルは検索されない