YAPC::Asia::2008

こういった場合のTipです。

ファイル演算子「 -s 」で、ファイルサイズを取得する。 - サンプルコードによる Perl 入門
if( -f $file ){
    my $file_size = -s $file;
    print "$file のファイルサイズは、$file_size バイトです。\n\n";
}
else{
    print "$file は、存在しませんでした。\n\n";
}

_ ファイルハンドル

実は、-X演算子は、その演算子に期待された結果を返すだけではなく、その他のファイル情報を特殊ファイルハンドル_にセットします。これを使うと、上のコードは

if( -f $file ){
    my $file_size = -s _; # ここが使いどころ
    print "$file のファイルサイズは、$file_size バイトです。\n\n";
}
else{
    print "$file は、存在しませんでした。\n\n";
}

と書き直せます。

なぜこうなっているかというと、-X演算子はlstat()ないしfstat()システムコールを使ってファイルの情報を取得するのですが、このシステムコールはディスクにアクセスするということもあって比較的重く、そして一つのファイルに対して複数の-X演算子を適用する機会というのが少なくないからです。

この_ファイルハンドルは、stat cacheとも呼ばれています。

stat()とlstat()

このstat()は、Perlの組み込み関数としても存在しています。

perldoc -f stat
($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
 $atime,$mtime,$ctime,$blksize,$blocks)
 = stat($filename);

それぞれの意味はこうなります。

 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

lstat()も一点を除いて全く同様です。それは何かというと symbolic link の扱い。stat()は引数が symbolic link だった場合、link先のファイルの情報を取得しますが、lstat()ではlinkそのものの情報を取得します。

なお、fstat()はPerlのコマンドとしては存在しません。その代わりstat(FILEHANDLE)で同様の機能が提供されています。

-X演算子と同じく、_が更新されるのは想定の範囲内でしょう。

「配列でなくて、OOでアクセスしたい」という人には、File::statというモジュールが標準で用意されています。これを使うと(stat($filename))[9]ではなくstat($filename)->mtimeと書けます。ただし、オブジェクト化のためのコストがかかるので、速度が必要な場合には組み込みのstat()を用いた方がよいでしょう。

BSD::stat

なお、拙作BSD::statを使うと、4.4 BSD系のシステムで追加された拡張属性にもアクセスできるようになります。

13 atimensec  nsec of last access
14 mtimensec  nsec of last data modification
15 ctimensec  nsec of last file status change
16 flags      user defined flags for file
17 gen        file generation number

こちらで拡張されたstat()はcontextualなので、

my @stat = stat($filename); # 通常のstatと上位互換
my $stat = stat($filename); # File::statと上位互換

にもなっています。OOの場合もFile::statよりも高速です。BSD系の場合はこちらを利用も検討してみるとよいでしょう。

Perl 5.10の拡張

Perl 5.10からは、-Xを重ね打ちすることもできるようになりました。

% perl -le 'print (-f -x shift)' /usr/local/bin/perl
1

重ね打ちした場合、全ての条件が1の場合に1になります。すなわち

my $is_executable = -f -x $filename;

my $is_executable = -f $filename && -x _;

と等価ということです。

Enjoy!

Dan the Man with Too Many Files to stat()

See Also: