ファイル入出力関連
2020/04/20更新
目次
モダンなopen関数の書き方
かつて、Perlでファイルを開くときのopen
関数は、以下のように書いていた。
open(FH, ">$file");
このような書き方は現在ではレガシーとなっている。ファイルハンドルに使われている裸の文字列(Bareword)は変数としての取り扱いが難しく、また、読み書きモードとファイル名を一体化させて渡しているため、安全性の問題もあった。
上記の問題があったため、現在(Perl5.6以降)では以下のように3引数のopen
関数を使って書くのが一般的となっている。
open(my $fh, '>', $file);
まず、ファイルハンドルには、定義したばかりの(中身がundef
な)ローカル変数を使う。そして、3引数にすることで、<
、>
、>>
などの読み書きモード指定とファイル名とを分ける。なお、外部コマンドへ出力を渡す場合は|-
、外部コマンドから入力を受ける場合は-|
とする。
ローカル変数のファイルハンドルは、従来のファイルハンドルとほぼ同じように使用できる。
print $fh "hoge\n";
また、サブルーチンなどにそのまま渡すことができる。
sub my_output { my($fh, $data) = shift; print $fh $data; } open(my $fh, '>', $file); my_output($fh, "hoge\n");
ファイルハンドルの謎
前述の通り、現在ではファイルハンドルを指定する部分には、通常のスカラー変数を渡すのが普通であるが、それではかつてファイルハンドルとして指定していたFH
などの文字は一体何だったのだろうか?Perlを学び始めた頃、これは大きな謎だった。$
が付いていないのでスカラー変数ではないし、クォートされていないので文字列でもない。
Perlにおいて、パッケージ内でグローバルに使われるスカラー変数やサブルーチンなどの「名前」は、シンボルテーブルというもので管理されている。シンボルテーブルでは、例えばFH
という名前(シンボル)に対して、スカラー変数(SCALAR)、配列(ARRAY)、ハッシュ(HASH)、サブルーチン(CODE)などのいろいろなスロットがあり、それぞれ、$FH
、@FH
、%FH
、&FH
という変数やサブルーチンへのリファレンスを格納している。
ところが、ファイルハンドルというのは、上記のいずれのスロットでもなく「IO」という専用のスロットに格納される。このスロットは、$
や@
のようなそれを指し示す専用のシジルが無いため、open
関数などに渡す際は、FH
のようにシジル無しの裸の文字列(bareword)でシンボル自体を書くか、*FH
のように*
を付けて全てのスロットを総称する型グロブ(またはそのリファレンス\*FH
)として記述するしかなかったのである。
このようにbarewordや型グロブでは取り扱いに難があるし、そもそもグローバルであることも問題であった。そこで、Perl 5.6.0以降では、open
関数などにファイルハンドルとして未定義のスカラー変数が渡されたときは、内部で自動的に新しいファイルハンドルを生成し、その型グロブのリファレンスをスカラー変数に割り当ててくれるようになった。これにより、通常の変数として取り扱うことができるようになった上、グローバルに影響を及ぼすファイルハンドルそのものについて考える必要が無くなったのである。
このように、ファイルハンドルの型グロブがスカラー変数に入っていることを理解すると、例えば標準入出力のファイルハンドルSTDIN
、STDOUT
、STDERR
などを変数に代入したいときは以下のようにすればよいことが分かる。
# $fileがあれば開き、無ければ標準出力を$fhに設定 my $fh; if ($file) { open($fh, '>', $file); } else { $fh = *STDOUT; } print $fh "Hello, world!\n";
ファイルの内容を一度に読み込む方法
Perlにおいて、テキストファイルを読み込むには、行入力演算子<~>
を使うが、これは通常、改行ごとに区切られて入力されるため、ファイルの内容を一度に読み込みたい場合は使い勝手が悪い。
open(my $fh, '<', $file); my @lines = <$fh>; my $content = join('', @lines); close($fh);
そのようなときは、特殊変数$/
によって行の区切り文字を一時的に変更すればよい。
open(my $fh, '<', $file); my $content = do { local $/; <$fh> }; close($fh);
このように、local
で$/
の値を一時的にundef
にすることで、区切り文字無しとなり、全体が1行という扱いになって、結果的に一度の<~>
で全てを読み込むことができる。
なお、do { ~ }
は、local
のブロックスコープを作りつつ、<$fh>
の値を返すために使っているものである。もし、スクリプト全体で$/
を無効にしたいのであれば、スクリプトの冒頭でundef $/;
などとすることもできる。
外部コマンド実行時のエラー処理
open
で外部コマンドを起動した場合、そのコマンドが成功したか失敗したかは、open
の時点ではなく、close
の時点で判明する。
# 通常のファイル入出力の場合 open(my $fh, '<', $file) or die "ファイルを開けません。"; : close($fh); # コマンド実行の場合 open(my $cmd, '-|', "ls $dir"); : close($cmd) or die "コマンドを実行できません。";