find(1)ねたというのは、定期的にblogosphereを賑わせるものだし、それはそれでよいし、私自身いくつか書いているのだけど....

さすがに何年もblogosphereにいると、いいかげん見飽きてくる。ましてや「極めたい」ともなると、findばっか見ていても絶対無理なのだし。

というわけで、findとは何かを改めて説明したあと、その背後にあるstatを抑えてみることにする。

findとは何か

findは、その名のとおり「ファイルを見つける」コマンド--ではない。ファイルを見つけることも出来るが、それはfindの本質ではない。そのことは、実はfindの man page にもきちんと書いてある。

man find
NAME
     find -- walk a file hierarchy

「ファイル階層をたぐる」。本質はこちらにある。具体的にfindのやっていることを見るとこうなる

  1. 指定されたノードおよびその下にあるノード全てに対し
  2. 指定された表現で評価し
  3. 真になったノードに、指定された操作を適用する

3.のデフォルトが「そのノードのパスを表示」することであるのは、一度でもfindしてみた人であればわかるだろう。そしてfindの難しい部分が、2であることも。

この2.の部分のキモとなるのが、statなのである。

statとは何か

ノードには、大きく分けて二種類の属性がある。データとメタデータだ。データが「ファイル」の中味であり、それ以外はメタデータということになる。ファイルがいつ作成され、いつ更新されたか。サイズはどれくらいなのか。ファイルの中味以外の情報は、全てメタデータということになる。

findが確認するのは、そのうちメタデータのみである。findは中味を一切吟味しない。中味まで検索したかったら別のコマンドがいる。たとえば Mac OS X ならSpotlightや、そのコマンドライン版である mdfind(1) がこの目的に使えるし、それ以外でも Google Desktop がある。

そのfindがパス以外に見ているのが、stat()、厳密にはlstat()の結果なのだ。ちなみにlstatstatの違いは、ノードが symlink だった場合に、symlinkそのものの情報を提示するか(lstat)、symlink先の情報を提示するか(stat)である。

それでは、statでわかるメタデータはどうなっているのだろうか?実はOSによってその中味は微妙に異なるのだが、perlの組み込みコマンドであるstat()が、よき最大公約数となっている。

perldoc -f stat
   Not all fields are supported on all filesystem types.  Here are
   the meanings of the fields:

     0 dev      device number of filesystem
     1 ino      inode number
     2 mode     file mode  (type and permissions)
     3 nlink    number of (hard) links to the file
     4 uid      numeric user ID of file's owner
     5 gid      numeric group ID of file's owner
     6 rdev     the device identifier (special files only)
     7 size     total size of file, in bytes
     8 atime    last access time in seconds since the epoch
     9 mtime    last modify time in seconds since the epoch
    10 ctime    inode change time in seconds since the epoch (*)
    11 blksize  preferred block size for file system I/O
    12 blocks   actual number of blocks allocated

それでは、順を追って解説していこう。

  1. dev
  2. ino

    devはヴォリュームごとに固有に割り当てられるIDであり、inoはそのボリューム内で固有に割り当てられるIDである。Unixのファイルシステムにおいて「同じファイル」というのは、この二つが一致していることを意味する。

    findにおいては-xdev-inumがこれに関連する。

  3. mode

    ファイルの種類とパーミッションがここに格納される。下12バイトがパーミッション、残りがファイルの種類である。パーミッションに関しては404 Blog Not Found:unix - permissionあれこれを参照のこと。

    findに関連するオプションは、-type-permとなる。

  4. nlink

    そのノードにハードリンクがいくつあるかを示す。Unixの場合、一つのファイルに対し複数のパスを持たせることが出来、そしてこのリンク数がゼロ、かつそのファイルを開いているプロセスがなくなった時点でファイルが消去されたこととなる。Unixにおけるファイル削除のシステムコール名がdeleteではなくunlinkとなっているのはそのためだ。

    findにおいては-linksがこれに関連する。

  5. uid
  6. gid

    ファイルの所有者IDおよびグループID。statの返す値はIDであり、名前でない点に注意が必要である。ユーザー名からUIDを調べるには、perlでは(getpwnam($username))[2]とし、グループ名からGIDを調べるには(getgrnam($groupname))[2]とすればよい。

    findでは、-user-groupがこれに対応する。ありがたいことに、名前とIDの双方を引数として受け付ける。このことがあるので、IDと紛らわしい名前、例えばユーザー名404などというのは避けるべきである。

  7. rdev

    デバイス識別子。おそらくstatの戻り値の中で最も用いられないフィールドではあるが、存在ぐらいは知っておいてもよいだろう。findに対応するオプションはない。ちなみにこれが何かというのは、/dev以下をls -lしてみればわかる。

  8. size

    ファイルサイズ。findでは-sizeがこれに対応。後述のblksizeとblocksも参照のこと。

  9. atime
  10. mtime
  11. ctime

    それぞれ「最後にアクセスされた時間」「データ最終更新時間」、「メタデータも含めた最終更新時間」を、Unix Epoch = 1970.01.01 00:00:00 GMTからの経過秒数で。

    findでは-atime,-mtime,-ctimeがそれぞれに相当する。ただしatimeは、パフォーマンス優先のヴォリュームではnoatimeオプション付きでマウントされていたりするので、あまりあてにしない方がよいだろう。

    なお、atimeとmtimeは、utime(3)で変更可能である。perlだとutime $atime, $mtime, @pathsとする。

    もう一つの注意点として、findにおけるこれらのオプションの引数は、stat()が返す絶対時間ではなく、findコマンドが実行された時間からの相対時間だということがある(perlだと-A, -M, -C演算子相当)。絶対時間を使いたかったら、素直にperlなどでスクリプトを書く方がよいだろう。

  12. blksize
  13. blocks
  14. この二つをかけたものが、実際にファイルシステム上でそのノードが使っているディスク領域の大きさということになる。たとえ 1 byte しかないファイルでも、実際には4K byteほどの領域を使ってしまうのはそのためだ。逆に sparse file をサポートしている場合、sizeが1GBとかあったとしても1 blockしか使っていないということもありうる

    残念ながらfindはこれに対応していないようだ。その代わりduコマンドをこの目的に使うことが出来る。

以上を抑えておけば、findを極めるどころか、findを再実装しなおすことも出来るだろう。enjoy!

Dan the Man with Too Many Files to stat