ファイルのすべてを飲み込む方法

原稿を落としたら,すしをおごりにアメリカに行かないといけないとの噂にgkbrしているid:hakobe932です. 932は草津の932です.こんにちは.

ファイルの中身をすべて読み込む処理というのは,非常によくある処理です.TIMTOWTDIが信条のPerlでは,ファイルの中身をすべて読み込む方法もたくさんあります.ここでは,どんな方法があるのか見てみましょう.

行単位で読み込む

もっともシンプルなのは行入力演算子(<>)を使って行毎にデータを読み込み,それを連結する方法です.

open my $fh, '<', './inputfile'
    or die "failed to open: $!";
my $content = '';
while (my $line = <$fh>) {
    $content .= $line;
}
print $content;

もう少し工夫して後置whileを使うと1行で書くことができます.

open my $fh, '<', './inputfile'
    or die "failed to open: $!";
my $content = '';
$content .= $_ while <$fh>;
print $content;

行入力演算子はリストコンテキストで評価すると,すべての行のリストが返ってくるので,次のように書くこともできます.

open my $fh, '<', './inputfile'
    or die "failed to open: $!";
my $content = join '', <$fh>;
print $content;

$/を利用して読み込む

行入力演算子でファイルの内容をすべて読み込むのは,シンプルで比較的わかりやすいですが,行を一度保存するのでメモリを食いがちです.もう少し効率の良い方法として$/変数を使うやりかたがあります.

グローバル変数$/をローカル化すると行入力演算子でファイルの内容をすべて読み込むことができます.

open my $fh, '<', './inputfile'
    or die "failed to open: $!";
my $content = '';
{
    local $/;
    $content = <$fh>;
}
print $content;

これでは少しかっこわるいのですが,doブロックを使えばもう少しきれいです.

open my $fh, '<', './inputfile'
    or die "failed to open: $!";
my $content = do { local $/; <$fh> };
print $content;

これでコードもずいぶんスッキリしました.しかし,おまじない的なコードで,ぱっとみたときに何をやっているのかわかりにくいですね.

ファイルのすべてを飲み込む

そこで,ファイルのすべてを飲み込むためにPerl6::Slurpを使いましょう.Perl6::Slurpは名前の通りファイルの内容をすべて飲み込みます.

use Perl6::Slurp;
my $content = slurp './inputfile';
print $content;

Perl6::Slurpでexportされるslurp関数を使えば,おまじない的な部分がなくなって,とってもシンプルでわかりやすいコードが書けます.しかも,ファイルハンドルをopenする手間もへって良いとこづくしですね! 似たようなモジュールにFile::Slurpがあります.

他にPath::Classを使うという手もあります.

use Path::Class;
my $inputfile = file('./inputfile');
my $content = $inputfile->slurp;
print $content;

Path::Classはslurpするのに使う以外にも,ファイル操作に関する便利なメソッドがたくさん用意されています.モダンなPerlコードでは定番の のファイル操作のモジュールですね.

slurp系のメソッドや関数は,リストコンテキストで評価されると行のリストを返すので注意が必要です.以下のようなはまり方をすると非常にやっかいです.

use Perl6::Slurp;
my $obj = { data => slurp './inputfile' }; # このslurpはリストコンテキストで実行される!

このように,すでにあるモジュールを使うとすっきり簡潔にコードが書けます.モジュールの充実しているPerlの醍醐味かもデスネ.

おまけ: readシステムコールで直接読み込む

sysread関数をつかえばreadシステムコールを発行して指定サイズ文だけファイルからデータを読み込めます.-s 演算子をファイルハンドルに対して使えばファイルサイズが取れるので,以下のようにすればファイルの内容をすべて読み込むことができます.

open my $fh, '<', './inputfile'
    or die "failed to open: $!";
my $content;
sysread $fh, $content, -s $fh;
print $content;

事前にサイズを指定してシステムコールを発行するので,非常に高速なはずです.ただし,生のシステムコールを叩くのでいろいろな例外事項の対処を自分でしないといけないため,あまりおすすめできません!

まとめ

というわけで,いろいろな方法でファイルの中身をすべて読み込んで見ました.多くの選択肢があってなかなかPerlらしい感じですね.わかりやすさや,覚えやすさ,書いているプログラムの性質などに合わせて,良さそうなのを選べば良いと思います.個人的には,Perl6::Slurpや$/を使った方法をよく使います.これからは,Path::Classを使うのがオシャレかもしれませんね.

もし,このほかにもファイルを飲み込む方法があればぜひ教えてクダサイ.

現在つぎにお願いする人をさがしてます…> Yuichi Tateno に