入出力(2) ファイルの読み書き・基礎

ファイルハンドルの事を勉強したので、次はファイルの読み書きを勉強します。ファイルの内容を読み書きするには、以下のような流れになります。

  1. ファイルを開いて、ファイルハンドルに関連付ける(open)
  2. ファイルハンドルを通じて、内容を読み書きする(print, 行入力演算子など)
  3. ファイルハンドルを閉じる(close)

以下、それぞれのステップについての説明です。1エントリとしてはちょっと長くなってしまいました。(^_^;)

open 関数でファイルを開く

まず、open 関数で入出力先となるファイルを開いて、ファイルハンドルに関連付けます。「perldoc -f open」によると、open 関数に渡す引数は以下のようになっています。

    open FILEHANDLE,EXPR
    open FILEHANDLE,MODE,EXPR
    open FILEHANDLE,MODE,EXPR,LIST
    open FILEHANDLE,MODE,REFERENCE
    open FILEHANDLE
            Opens the file whose filename is given by EXPR, and associates
            it with FILEHANDLE.

EXPR として指定されたファイル名のファイルを開いて、FILEHANDLE に関連付けます。FILEHANDLE は通常「TXT」や「BODY」などのように、頭に何も付けていない大文字の識別子を用います。

open TXT, "test.txt";

また、未定義のスカラー変数や配列・ハッシュの要素などの左辺値を FILEHANDLE として渡すと、開かれた無名ファイルハンドルへのリファレンスが代入されます。逆に定義されている変数や式を FILEHANDLE として渡すと、一種のシンボリックリファレンスとして処理され、その値がファイルハンドルの名前として使われます。use strict すると、誤爆が防げますね。(^_^)
MODE は「オープンモード」を指定します。ファイル名 EXPR に含める事もできます。
例えば '<' を指定するか、何も指定しなかった場合は「読み込みモード」としてファイルが開かれます。読み込みモードではファイルは自動的に作成されず、存在しない場合はエラーとなります。また、print などによる書き込みは行なえません。逆に '>' を指定すると「書き込みモード」としてファイルが開かれます。既に同名のファイルが存在する場合は上書きされます。ファイルが存在しなければ自動的に作成されます。

open TXT, "> out.txt";     # (1)
open TXT, ">", "out.txt";  # (2)(1) と同じ意味
open TXT, "< in.txt";      # (3)
open TXT, "in.txt";        # (4)(3) と同じ意味 

他にも '>>' はファイルの内容を残したまま追記する「追記モード」、'+>' はほとんど '>' と同じですが、書き込んだ内容を読み出せます。'+<' は既存ファイルを自由に読み書きできます。
表にまとめておきました。

記号 読み 書き 追記 作成 上書
< × × × ×
> × ×
>> × ×
+< × × ×
+> ×
+>> ×

「作成」が○のものは、ファイルが存在しない場合、自動的にファイルを作成します。「上書」が○のものは、既存ファイルの内容を上書きします。


open 関数は、ファイルのオープンに成功すると 1 を、失敗すると undef を返します。これを利用した Perl の常套句として open or die があります。

open TXT, "in.txt" or die "Can't open in.txt : $!";

die 関数は渡されたメッセージを STDERR(標準エラー出力)に出力して、プログラムを終了します。$! には open に失敗した理由などが入ります。つまり、この文は「in.txt を open し、失敗すればエラーメッセージを出して終了する」という意味です。なぜこんな書き方ができるかというと、Perl の論理演算子は「ショートサーキット演算子」(短絡演算子)だからですね。(^_^)
「A or B」と書いた時に A が真ならこの式全体の値も真となるのがわかるので、そこで式の評価が終了され、B は評価されません。逆に A が偽だと B を評価するまでは式の値がわかりませんので、B が評価されます。同じように「A and B」なら、A が偽ならこの式全体の値が偽となるので B が評価されません。A が真だとやはり B が評価されます。
このように不必要な評価がされない論理演算子を「ショートサーキット演算子」と呼びます。この性質を利用すると、if 文を使わずにシンプルに書く事ができるので、よく使われるそうです。覚えておいた方がいいですね。
ちなみに、「or」の代わりに「||」を使うと、優先順位の問題から die は open 関数への引数の一部として評価されてしまうので、有無を言わさず終了してしまいます。注意しなくては。
「open or die」は、英文として読めて判りやすいうえに、とても簡潔で素晴らしいですね。しかし、「die」はちょっと乱暴な気もします。(^_^;)

内容を読み書きする

open で開いたファイルハンドルに対して、入出力を行ないます。
例えば print 関数を使えば、ファイルハンドルに書き込み(出力)できますね。

print TXT "Hello world!\n";

読み込み(入力)については、行入力演算子(山カッコ演算子)と呼ばれる演算子が良く使われるそうです。「<ファイルハンドル>」のように、ファイルハンドルを「<>」(山カッコ)で囲みます。内部で readline 関数を呼び出しています。
この演算子スカラーコンテキストで使う場合、ファイルハンドルから1行読み出して、改行付きでその内容を返します。ファイルハンドル内部の現在位置が読み込んだ分だけ進むので、繰り返し呼ぶ事で各行を処理する事ができます。

my $first  = <TXT>;  # 1行目
my $second = <TXT>;  # 2行目
my $third  = <TXT>;  # 3行目 

空行でも改行文字が返ってくるので、if 文などでは真として判定できます。その代わり、ファイルの終端まで読み終えた時、行入力演算子は undef (未定義値)を返すので、偽として判定されます。これを利用すれば、各行を処理するようなループを簡単に書く事ができますね。(^_^)
ただし、一つ例外があり、while の条件部に「<ファイルハンドル>」とだけ書いた場合は、暗黙的に「defined($_ = <ファイルハンドル>)」として解釈されます。つまり、以下の2つの文は等価です。

while(<TXT>) { print; }
while(defined($_ = <TXT>)) { print; }

2つ目の文の意味を解説すると、まず「$_ = 」という代入文が評価されます。これにより $_ にファイルハンドル TXT から1行読み込んだ値がセットされます。
代入文は代入後の左辺値(この場合 $_)を返すので、次に「defined($_)」が評価されます。行入力演算子はファイルの終端に来ると「undef」を返すので、つまりこの文は「ファイルの終端まできたかどうか」を判定している事になります。
その判定が while の条件部に入っているので、この while ループはファイルの終端まで繰り返される事になります。「print;」は「print $_;」と同じ意味ですね。
つまりこれらの文は、ファイルハンドルの内容を全行読み出して全て print する事になります。とても簡潔に表現できていますね。こういった書き方が出来るのは Perl ならではだと思います。(^_^)


ところで、行入力演算子をリストコンテキストで使うと、ファイルハンドルの終端まで読み出し、1行1行が要素になっているリストを返します。

my @lines = <TXT>;
my $body = join "", @lines;

@lines にファイルの内容を行単位の配列として読み出します。$lines[0] で1行目の内容を参照できますね。やはりこの場合も最後に改行がついているので注意が必要です。
また、$body にはファイルハンドルの内容がまるまる入る事になります。

close 関数でファイルハンドルを閉じる

開いたファイルハンドルは、処理が終わったらなるべく閉じた方が良いですね。開きっぱなしにしておくと、Perl のプロセスが終了するまで、他のプログラムが開けなくなってしまいます。
ファイルハンドルを閉じるには close 関数を使います。使い方はファイルハンドルを渡すだけです。

close TXT;

成功すると真を返します。
もし、すぐにまた open するつもりのファイルなら、close する必要はありません。open する前に自動的に Perl が一度 close してくれます。(個人的には明示的に close しておいても良いと思うのですが、どうでしょうか)

まとめ

例えば、「ファイル readme.txt を開いて全文を標準出力に出力するプログラム」なら以下のようになります。

readme.txt
Hello, I am readme.txt!
Could you please read me?
# readme.pl
use strict;
 
open README, "readme.txt" or die "Can't open readme.txt: $!";
while (<README>) { print; }
close README;

実行結果は以下の通りです。

D:\home\palmo>perl readme.pl
readme.txt
Hello, I am readme.txt!
Could you please read me?

実質3行で書けるとは驚きです。すごい。(^_^)