findを極める 

findは沢山のオプションがあり、ちょっとやそっとじゃ全体を覚えて日頃活用しきることが難しいコマンドの一つではないか。とかって思ったので少しずつ調べてみることにした。

-print0
-printと同様にパス付きのファイル名を出力して真を返すが、ファイル名の後ろに\0(ヌル文字)をつける。こうすることでファイル名に改行などを含む特殊なファイルを処理することができる。xargsのオプション-0と対応している。

man findによれば、ファイル名には'\0'と'/'以外のどんな文字も使用できることになっているので、何も考えずに-printによる出力を改行区切りで処理すると意図したことを実現できない場合があるわけだね。(つまり、出力したファイルのファイル名に改行が含ま れていた場合、ファイル名の途中までを一つ目のファイル名として扱い、改行の後を2つ目のファイル名として扱うことになってしまうわけだ)

さて、改行を改行文字(LF, CR)以外で示すファイルフォーマットは(最近は)それほど見かけなくなってきているので、自分がよく使うperlで'\0'を改行文字として扱うための手順がよくわからない。多分、改行文字を示す特殊変数に'\0'を代入してやって

while(<>){}

を回す、とかすることになるのではないだろうか。

-maxdepth DEPTH
findで探索するディレクトリのトップから何階層下までを探索対象とするかを指定。これだけみると簡単なのだけれど、こんなシンプルなmaxdepthにも落とし穴がある。以下のように実行するのは実用上誤りなのね。

find /dir -type f -name filename -maxdepth 5


/dir配下にある、ファイル名が"filename"であるファイルを-printすることを意図しているのだけれども、多分期待したものよりもずっと実行時間が長く掛かるはず。
findは検索条件チェックを、コマンドラインの先頭から順に行うようになっているらしいので、コマンドラインでの指定順によって実際に行う動作が変わってくることがあるのね。今回の例だと、/dirからディレクトリエントリを一つずつ拾っていき、各ディレクトリエントリに条件"-type f"と"-name filename"のチェックをした後に"-maxdepth 5"のチェックを行うことになる。多分、/dir配下の全ディレクトリに対して探索を行うんじゃないだろうか。
正しくは以下のようにする。

find /dir -maxdepth 5 -type f -name filename


これで5階層以上深いディレクトリであれば何も考えずに探索をやめるため、探索対象がぐっと減ることになる。
-exec
-execは結構必須でいつも使うオプションなのだけれど、いつも気にしないところでちょいと役に立つ知識が埋まっていた。
配下に沢山のファイルが存在する場所を対象として、

find / -exec ls -ld {} \; | head


とかって実行すると、多数のエラーメッセージが出力されて-execで指定したコマンドlsがシグナル13で死んだと報告される。
シグナル13は"SIGPIPE"で、読み手の無いパイプへの書き出しをしようとした時に発生する。実際には、lsが起動されて書き出しをしようとした時にはそれを読むべきコマンドheadが終了していたということ。(もちろんheadで出力される最初の数回のコマンド実行は正常に完了する)
多分、コマンドfindは高速化のために色々と工夫をしていて、パイプの後ろのheadコマンドが生きているか死んでいるかを確認せずにファイルを見つける度にlsを起動、出力をheadへのパイプに接続していくんだろう。この結果、大量のエラーメッセージが出力されるというわけだ。たぶん、find自体が標準出力へ出力する必要が出るまでは、findが読み手のないパイプへ書きだそうとして終了することはないのだろう。
このエラーメッセージはかなり鬱陶しいし、実用上も必要な画面出力を流してしまうため出力されるのを避けたい。いろいろ調べて見つけた方法は次のように、オプション-execのターミネータとして\;ではなく、+を使用する方法。

find / -exec ls -ld {} + | head


-execのターミネータとして\;の代わりに+を使用することで、見つけたファイル一つごとにlsが実行されるのではなく、1回のlsコマンド実行で複数のファイルをまとめて指定して実行される。しかも引数文字列が長すぎる場合には、コマンドラインの長さ制限の許す範囲で切り、分割して実行してくれる。
このように実行すると何故かエラーメッセージの出力が1回だけに抑えられ、鬱陶しさがかなり抑止される。なぜこうなるのかはよく分かっていないのだけれども、どうしてなんだろう?

-mtimeと未来のタイムスタンプ
-mtimeなどでファイルのタイムスタンプでフィルタする場合に、検索対象ディレクトリに未来のファイルが存在すると何が起きるのか。以下のようにして"N日より新しいファイルを処理する"と指定すると、未来のファイルも含めて処理される(当たり前)。

find . -mtime -1 -exec ls -l {} +


このコマンドを以下のように変えて、"昨日更新されたファイルを処理する"ということを意図して1日1回実行すると、未来のファイルが何回も処理されることになり都合が悪い。

find . -daytime -mtime -2 -exec ls -l {} +



1ヶ月先のタイムスタンプを持つファイルAは、今日も明日もその次も毎日処理されることになる。昨日更新されたファイルでもないのに。明日以降のタイムスタンプを持つファイルを除外するためには以下のようにすればよい。

find . -daytime -mtime -2 -mtime +0 -exec ls -l {} +


findの引数は登場順にテストされるので、まず "-mtime -2" で昨日以降に更新されたファイルであるか確認し、そうでなければ次のファイルへ進む。
次に "-mtime +0" で明日より前に更新されたファイルであるか確認し、そうであれば-execを処理するということになる。

それでふと疑問に思ったのだけれど、今日も除外したい場合にはどのようにオプション指定すればいいのだろう?

find . -daytime -mtime -2 -mtime +1 -exec


ムムムッ!?

あれ?、なんだこれ、

-mtime -+N とか、
-mtime +-N とか指定できる。

更に出力される内容が異なる。どんな指定になっているんだ? ワカラン

あ、ちなみに未来のタイムスタンプを持つファイルを作ることは結構簡単にできる。(mtimeやatimeなら) こんな感じ。

$ touch -t 4512312359 /tmp/test
$ ls -alF --time-style=+"%Y/%m/%d %H:%M:%S" /tmp/test
-rw-r--r-- 1 user users 0 2045/12/31 23:59:00 /tmp/test
$


調子に乗って2099年を指定したつもりになったらtouchコマンドでは駄目だった。

$ touch -t 9912312359 /tmp/test
$ ls -alF --time-style=+"%Y/%m/%d %H:%M:%S" /tmp/test
-rw-r--r-- 1 user users 0 1999/12/31 23:59:00 /tmp/test
$


そしてファイルのタイムスタンプのctimeは簡単には変更できない

コメント

コメントの投稿















管理者にだけ表示を許可する

トラックバック

この記事のトラックバックURL
http://haginov.blog35.fc2.com/tb.php/271-9ee1b6c7