open

ファイル、パイプ、ディスクリプタを開く

構文

解説

open は、EXPR に指定された外部ファイルを内部的な FILEHANDLE と関連付けます。 これによって、ファイルハンドルを通してファイルの I/O 操作、つまり、ファイルの読み書きができるようになります。 ファイル名の代わりに外部コマンド(任意に引数リストを加えることもできます)やスカラー参照を指定することもできます。 これによって、コマンドやメモリー上のスカラーのファイルハンドルを開くことができます。 open は成功するとゼロでない値を返し、失敗すると undef を返します。

ファイル操作

open を使ってファイルを開く場合は、3 つの引数を与えます。 1 つ目は FILEHANDLE (通常は空のスカラー変数) を与えます。これは必須です。 2 つ目は MODE (通常はファイルハンドルが利用する I/O モードを記述した文字列) を、 3 つ目は ファイルハンドルが参照するファイル名を与えます。

次の例は、ファイルを開いて行単位で処理を行います。

open( my $fh, "<", "sample.txt" )
  or die "ファイルの読み取りに失敗しました: $!";

# 行単位で処理する
while ( my $line = readline($fh) ) {
    # 行に対して何かしらの処理を行う
}

次の例は、テキストをファイルに書き込みます。

open( my $fh, ">", "sample.txt" )
  or die "ファイルの書き込みに失敗しました: $!";

print $fh "このテキストがファイルに書き込まれます。\n";

MODE は通常は特殊文字から成る文字列ですが、生成されるファイルハンドルが読み取り専用なのか読み書き可能なのかといった I/O の役割を定義します。MODE に指定できる特殊文字は下表の通りです。

MODE 説明
< 読み取りモードでファイルを開きます。ファイルが存在しなければエラーになります。ファイルを開いた時点のファイルポインタの位置はファイルの先頭になります。
> 書き込みモードでファイルを開きます。ファイルが存在すれば切り詰められて空になります。ファイルが存在しなければ新規に生成されます。ファイルを開いた時点のファイルポインタの位置はファイルの先頭になります。
>> 追加書き込みモードでファイルを開きます。ファイルが存在しなければ新規に生成されます。ファイルを開いた時点のファイルポインタの位置はファイルの末尾になります。
+< 読み書きモードでファイルを開きます。ファイルが存在しなければエラーになります。ファイルを開いた時点のファイルポインタの位置はファイルの先頭になります。
+> 読み書きモードでファイルを開きます。ファイルが存在すれば切り詰められて空になります。ファイルが存在しなければ新規に生成されます。ファイルを開いた時点のファイルポインタの位置はファイルの先頭になります。
+>> 読み書きモードでファイルを開きます。ファイルが存在しなければ新規に生成されます。ファイルを開いた時点のファイルポインタの位置はファイルの末尾になります。

上表のようにモードには <, >, >> の 3 つの基本のモードがあります。 そして、手前に + を加えることで、読み取りと書き込みの両方が可能となるモードになります。 +<, +>, +>> の違いは、 ファイルを開いた時点で、既存ファイルの中身をクリアしてしまうか、そして、ファイルポインタの位置が先頭か末尾か、です。

この読み書きモードの違いを別の言い方で説明すると、 ファイルの内容を変更せずに先頭にポインタを置いてファイルを開きたいなら +<、 ファイルの内容をクリアして先頭にポインタを置いてファイルを開きたいなら +>、 ファイルの内容を変更せずに末尾にポインタを置いてファイルを開きたいなら +>> を使います。

読み書きモードでファイルを開いた場合、必要に応じてポインタ位置を seek で移動します。 また、データを書き込む場合、ポインタ位置がフィルの途中であれば、その位置からデータを上書きすることになります。 ファイルの長さが足りなければ、後ろに追加する形で書き込まれます。 書き込まれた後、そのポインタは書き込んだデータの後ろに置かれます。

次の例は、abcd と書かれたファイルを +< モードで開いています。 ポインタを先頭から 2 バイト移動し、そこから 1234 を書き込みます。 その後、ポインタを先頭に戻し、全体を読み取ります。

open( my $fh, "+<", "sample.txt" );
seek $fh, 2, 0;          # ポインタを先頭から 2 バイト移動
print $fh "1234";        # 1234 を書き込む
seek $fh, 0, 0;          # ポインタを先頭に移動
read $fh, my $str, 6;    # ファイルの内容を先頭から 6 バイト分だけ読み取る
print $str, "\n";

なお、一般的にこの読み書きモードはテキストファイルでは使いません。 それは、1 文字 1 バイトとは限らないからからです。 たとえば UTF-8 なら半角英数字は 1 文字 1 バイトですが、日本語の文字は 1 文字 3 バイトの文字もあれば 4 バイトの文字もあります。 一方、読み書きの際に seek でポインタを移動するには、 文字数単位ではなくバイト数を指定して移動することになります。 都合よく文字の区切りに移動するのは難しいと言えます。 また、上書きする際にも、文字の区切り位置で正確に上書きが終了するとは限りません。 その場合、文字が中途半端な位置で切り取られてしまい、文字化けの原因になりますので注意してください。

MODE に I/O レイヤを指定する

引数 MODE に I/O レイヤを指定してファイルハンドルを開くことができます。 これは入出力の方法に作用します。 次の例は、UTF-8 で保存されたテキストファイルを内部文字列として読み取ります。

open( my $fh, "<:encoding(UTF-8)", "sample.txt" ); # "あいうえお"

my $num = 0;
while ( my $line = readline($fh) ) {
    $num += length($line);
}
print $num, "\n"; # 5

上記の例では I/O レイヤを指定してファイルハンドルを開いたおかげで、読み取ったテキストは内部文字列となります。 そのため、length はバイト数ではなく文字数を返します。 結果的に、「あいうえお」と書かれたテキストファイルであれば 5 が出力されます。 もし I/O レイヤを指定せずにファイルハンドルを開いた場合は、バイト数である 15 が出力されることになります。

一時ファイルに undef を使う

特殊なケースではありますが、読み書きモードで第三引数に undef を指定することができます。

open(my $tmp, "+>", undef) or die ...

これは空の無名一時ファイルを新規に生成し、ファイルハンドルを開きます。 これで仮想的なファイルに読み書きすることができます。

メモリー上のスカラーのファイルハンドルを開く

ファイルの代わりに直接 Perl のスカラーのファイルハンドルを開くことができます。 次のように、open の第三引数にスカラーの参照を与えます。

my $var;
open my $fh, ">", \$var;
print $fh "Hello!";
close $fh;
print $var, "\n";    # Hello!

コマンドへのファイルハンドルを開く

MODE-| なら、そのファイル名はコマンドと解釈され、 コマンドの出力がファイルハンドルへパイプされます。 MODE|- なら、同様にそのファイル名はコマンドと解釈されますが、 先ほどとは逆に、ファイルハンドルへの出力がコマンドへパイプされます。

次の例は、コマンド ls -l /usr/bin の出力結果をファイルハンドルにパイプし、 そのファイルハンドルを読み取ってコンソールに出力します。

open my $fh, "-|", "ls -l /usr/bin";
while ( my $line = readline($fh) ) {
    print $line;
}

コマンドは引数をリストとして与えることも可能です。次の例は前述の例と同じです。

open my $fh, "-|", "ls", "-l", "/usr/bin";

第二引数と第三引数をマージすることも可能です。次の例も前述の例と同じです。

open my $fh, "ls -l /usr/bin |";

次の例は、ファイルハンドルへの出力をコマンド grep にパイプします。2.two と出力されます。

open my $fh, "|-", "grep '2.'";
print $fh "1.one\n2.two\n3.three\n";

この例は、次のように記述することもできます。

open my $fh, "|-", "grep",  "2.";
open my $fh, "| grep '2.'";