open() はファイルをオープンします。オープンされたファイルは、ファイルハンドル IN という変数で参照されるようになります。ファイルハンドルには好きな名前をつけることができますが、通常大文字の変数名を用います。
ファイルを読み込むには <IN> を用います。
close() はファイルをクローズして、ファイルハンドルを無効化します。
open(IN, "data.txt"); while ($line = <IN>) { print $line; } close(IN);
$line = <IN>; とするとファイルを 1行だけ読み込みますが、@lines = <IN>; とするとすべての行を読み込み、それぞれの行を配列として返します。
@lines = <IN>
$line = <IN> の $line を省略すると、省略時変数 $_ に行の内容が代入されます。print $line; の $line を省略すると、省略時変数 $_ の値を書き出します。
open(IN, "data.txt"); while (<IN>) { # while ($_ = <IN>) { と同じ print; # print $_; と同じ } close(IN);
文字を1文字ずつ読みこむには getc() を用います。
open(IN, "data.txt"); while ($c = getc(IN)) { print "[$c]"; } close(IN);
ファイルに書きこむときは、open(ファイルハンドル, "> ファイル名") とします。open() を実行した時点でファイルの内容が全て削除されます。書き込みはファイルの先頭から行われます。書き込みには print() や printf() などを用います。
open(OUT, "> data.txt"); print OUT "AAA\n"; print OUT "BBB\n"; close(OUT);
print FILEHANDLE message は、FILEHANDLE で指定した ファイルハンドル に値を出力します。ファイルハンドルの後ろにカンマ(,)はありません。
print STDOUT "Hello World!!\n";
ファイルハンドルを省略すると、現在選択されているファイルハンドルに出力します。通常は 標準出力 STDOUT が選択されていますが、select() で変更することもできます。
print "Hello World!!\n";
値を省略すると、省略時変数 $_ の値を書き出します。
$_ = "Hello World!!\n"; print;
open(ファイルハンドル, ">> ファイル名") の形式を用いると、ファイルの末尾に追加書きすることができます。これを、アペンドモード で開くとか、ファイルの末尾にアペンドするなどと言います。
open(OUT, ">> data.txt"); print OUT "AAA\n"; print OUT "BBB\n"; close(OUT);
ファイルの先頭に追加書き込みするには、一度すべてのデータを読み出し、先頭にデータを追加、再度、すべてのデータを書きこむという手順を用います。"+< ファイル名" は、ファイルを読み書き両モードでオープンすることを意味します。
open(FILE, "+< data.txt"); # ファイルを読み書きモードで開く @lines = <FILE>; # すべての行を読み込む unshift(@lines, "AAA\n", "BBB\n"); # 配列の先頭に行を追加 seek(FILE, 0, 0); # 書き込み位置をファイルの先頭に移動 print FILE @lines; # 配列をすべて書き込む close(FILE); # クローズする
seek() は、指定したファイルハンドルへの読み書き位置を移動するのに用います。seek(FILEHANDLE, n, from) の形式で利用し、from が 0 の時はファイルの先頭から n バイト目の位置へ、from が 1 の時は現在の位置から n バイト目の位置へ、from が 2 の時はファイルの末尾から n バイト目の位置へ移動します。n には負の値も指定できます。ファイルの先頭の文字を 0 バイト目と数えます。
open(FILE, "data.txt"); seek(FILE, 0, 0); # ファイルの先頭に移動 seek(FILE, 100, 0); # ファイルの先頭から100バイト目の位置に移動 seek(FILE, 100, 1); # 現在の位置から100バイト後ろに移動 seek(FILE, -100, 1); # 現在の位置から100バイト前に移動 seek(FILE, 0, 2); # ファイルの末尾に移動 seek(FILE, -100, 2); # ファイルの末尾から100バイト目の位置に移動 close(FILE);
tell() は、指定したファイルハンドルの現在の読み書き位置を、ファイルの先頭からのバイト数で返します。ファイルの先頭の文字を 0 バイト目と数えます。
open(FILE, "data.txt"); seek(FILE, 100, 0); # ファイルの先頭から100バイト目の位置に移動 $pos = tell(FILE); close(FILE); print "pos = $pos\n";
実行結果は次のようになります。
pos = 100
ファイルテスト演算子の -s は、指定したファイルのサイズをバイト数で返します。これにより、ファイルサイズを求めることができます。
# ファイルのサイズを求める(方法1) $size = -s "data.txt"; print "size = $size\n";
別の方法として、lstat() を用いてもファイルサイズを求めることができます。ファイルがすでにオープン済みであれば、lstat() を用いた方が効率はよいようです。
# ファイルのサイズを求める(方法2) open(IN, "data.txt"); $size = (lstat(IN))[7]; print "size = $size\n"; close(IN);
ファイルサイズを増やすには、ファイルに追加書きをすることで可能です。ファイルサイズを小さくするには、ファイルを最初から作りなおすか、truncate() を用います。truncate(FILEHANDLE, size) は、指定したファイルハンドルのファイルサイズを size バイトに変更します。
open(FILE, "+< data.txt"); truncate(FILE, 1024); close(FILE);
ファイルは書き込みモード、または読み書き両用モードでオープンされている必要があります。
print()、printf() などの出力関数では、出力先のファイルハンドルを省略した場合、STDOUT に書き出します。select() を用いることにより、これを変更することができます。select() の戻り値には、それまでの出力先ファイルハンドルが返されます。
open(OUT, "> data.txt"); # ファイルを開く $old = select(OUT); # 標準の出力先を OUT に変更 print "HEHE\n"; # 標準の出力先(OUT)に書きこむ select($old); # 標準の出力先を元に戻す close(OUT); # ファイルを閉じる
これは、$|、$^、$~ など、現在の出力先に対するパラメータの値を変更する時にも使用されます。たとえば、ファイルハンドル OUT に対して、バッファリングを禁止する際には、次のようにします。これは、select() で現在の出力先を OUT に変更、現在の出力先に対するバッファリングを禁止($| = 1)、現在の出力先を元に戻す。という手順を行っています。
$old = select(OUT); $| = 1; select($old);
下記の例では data.txt に "AAA\n" を書きこんでいますが、データはしばらく書き込みバッファと呼ばれるメモリ上に溜められ、実際のファイルへの書き込みは、ある程度データが溜まるか、最後に close() するまで行われません。これは、入出力処理をまとめて行うことにより処理を高速化するといった、perl の バッファリング機能 によります。
open(OUT, "> data.txt"); print OUT "AAA\n"; open(IN, "data.txt"); # AAAはまだバッファリング print <IN>; # された状態なので、 close(IN); # 読み出すことができない。 close(OUT);
これを実行すると、AAA はまだメモリ中にバッファリングされているので、読み出すことができず、何も表示されません。
バッファリングされたデータを実際に書きこむことを フラッシュ と呼びます。書き込みのたびにフラッシュさせるためには、前節で紹介した、ファイルハンドル OUT に対するバッファリング禁止のテクニックを用います。
open(OUT, "> data.txt"); $old = select(OUT); $| = 1; select($old); # OUTのバッファリングを禁止 print OUT "AAA\n"; open(IN, "data.txt"); # AAAはすでにフラッシュ print <IN>; # されているので、 close(IN); # 読み出すことができる。 close(OUT);
これを実行すると次のようになります。
AAA
sysread()、syswrite()、sysseek() は、バッファリングを行わない、低レベルの読み込み、書き込み、位置変更を行います。Perl のプログラミングではあまり使用されることはありません。
open(FILE, ">+ data.txt"); sysread(FILE, $buf, 10); sysseek(FILE, 0, 0); syswrite(FILE, $buf, 10); close(FILE);
eof() は、End Of File の略で、ファイルの読み込みが末尾まで達したかどうかをチェックします。ただし、通常は while (<IN>) の形式で事足りるので、これもあまり用いられることはありません。
open(IN, "data.txt"); while (<IN>) { print; if (eof(IN)) { print "---END---\n"; } } close(IN);
fileno() はファイルハンドルに関連付けられた ファイルディスクリプタ という整数値を返します。このファイルディスクリプタは、select() などで使用されます。
open(IN, "data.txt"); $fdes = fileno(IN); print "fdes = $fdes\n"; close(IN);
複数のプロセスがひとつのファイルに対して同時に読み書きを行うと、ファイルの内容が壊れてしまうことがあります。
for ($i = 0; $i < 1000; $i++) { open(IN, "counter.txt"); $counter = <IN>; # カウンターを読み出す close(IN); $counter++; # カウンターをひとつ増やす open(OUT, "> counter.txt"); print OUT "$counter\n"; # カウンターを書き戻す close(OUT); print "counter = $counter\n"; # カウンターを表示する }
例えば上記のプログラムを普通に動かすと、カウンター値は 1000 増えますが、複数のコマンドプロンプトからプログラムを複数同時に動かすと、ファイル内容の破壊が発生し、すぐに 0 に戻ってしまいます。
上記の問題を回避するためにファイルを ロック することによる 排他制御 の機構を組み込んだ例を以下に示します。あらかじめ counter.txt ファイルを作成しておいてください。
use Fcntl ':flock'; for ($i = 0; $i < 1000; $i++) { open(FILE, "+<counter.txt"); flock(FILE, LOCK_EX); # ファイルをロックする $counter = <FILE>; # カウンターを読み出す $counter++; # カウンターをひとつ増やす seek(FILE, 0, 0); # ポインタを先頭に戻す print FILE "$counter\n"; # カウンターを書き戻す close(OUT); print "counter = $counter\n"; # カウンターを表示する }
flock() によりファイルがロックされ、複数プロセス同時アクセスによってファイルの内容が破壊されるのを防止しています。
flock() は、Perl でファイルロックを行う最も優れた方法です。flock() の基本的な使用方法を示します。
use Fcntl ':flock'; # ファイルを読みこむときのロック open(IN, "data.txt"); flock(IN, LOCK_SH); # 誰も書き込まないで $data = <IN>; close(IN); # ファイルに追加書きする時のロック open(OUT, ">> data.txt"); flock(OUT, LOCK_EX); # 誰も読み書きしないで print OUT "AAA\n"; close(OUT); # ファイルを書きかえるときのロック open(OUT, "+< data.txt"); flock(OUT, LOCK_EX); # 誰も読み書きしないで truncate(OUT, 0); seek(OUT, 0, 0); print OUT "AAA\n"; close(OUT); # ファイルを読み書きするときのロック(カウンター) open(FILE, "+< counter.txt"); flock(FILE, LOCK_EX); # 誰も読み書きしないで $count = <FILE>; $count++; truncate(FILE, 0); seek(FILE, 0, 0); print FILE "$count\n"; close(FILE);
flock() の第二引数には下記の値を指定します。これらの値を使用するには、use Fcntl ':flock'; を宣言しておく必要があります。use Fcntl を使用できない場合は、LOCK_XX の代わりに直接数値を指定してください。
フラグ | 数値 | 説明 |
---|---|---|
LOCK_SH | 1 | 共有ロック。ファイルを読みこむ際に使用。他のプロセスが共有ロックすることはできるが、排他ロックは禁止される。 |
LOCK_EX | 2 | 排他ロック。ファイルに書きこむ際に使用。他のプロセスは共有ロックも排他ロックも禁止される。 |
LOCK_UN | 8 | ロック解除。通常は close() の時点で自動的に解除されるので、明示的に解除しなくてもよい。 |
LOCK_NB | 4 | ノンブロッキングモード。LOCK_SH | LOCK_NB や、LOCK_EX | LOCK_NB のように使用。ロックが禁止されている場合、通常はブロックするが、LOCK_NB 指定時は、ブロックする代わりに flock() がエラーを返す。 |
flock() は Windows 98 など一部の OS ではサポートされていません。未サポート OS で flock() を使用すると致命的エラーとなり、プログラムが異常終了してしまいます。flock() をサポートしていない場合の対処方法は「mkdirによるファイルロック」を参照してください。
ファイルをロックする際に open(OUT, "> file"); や open(OUT, "+> file"); の形式は用いてはなりません。flock() でファイルをロックする前にファイルの内容が消去され、この状態のファイルを読み込んでしまうプロセスが発生してしまいます。
# ロックがうまく機能しない例 open(OUT, "> data.txt"); flock(OUT, LOCK_EX); : close(OUT);
古いバージョンの perl では、close() の前に flock(OUT, LOCK_UN) を呼び出してしまうと、バッファリングされたデータがフラッシュされる前にロックが解除されてしまい、他のプロセスが中途半端な内容のファイルを読み込んでしまうという問題がありました。
flock() がサポートされていないシステム用では、別の方法を用いてロックを実現する必要があります。下記では、mkdir() を用いたロックの方法を紹介します。
$LOCKFOLDER = ""; # ロックフォルダ # ロックする sub LockFile { local($lockfolder, $timeout) = @_; local($i) = 1; if ($LOCKFOLDER ne "") { return(0); # すでにロック実行中 } else { $LOCKFOLDER = $lockfolder; # ロックフォルダ名を覚えておく } # プロセス終了時にロックフォルダが残らないようにする $SIG{'PIPE'} = $SIG{'INT'} = $SIG{'HUP'} = $SIG{'QUIT'} = $SIG{'TERM'} = "UnlockFile"; for (;;) { # ロックフォルダが無ければ作成し、ロック成功とする if (mkdir($lockfolder, 0755)) { return(1); } # ロックフォルダが存在すれば最大 $timeout 秒間待つ elsif ($i < $timeout) { sleep(1); } # $timeout 秒待っても駄目ならあきらめる else { $LOCKFOLDER = ""; return(0); } } } # ロックを解除する sub UnlockFile { if ($LOCKFOLDER ne "") { rmdir($LOCKFOLDER); $LOCKFOLDER = ""; } }
LockFile() にはロックフォルダ名とタイムアウト秒数を指定します。読み込みモード、書き込みモードの区別はありません。ロックが成功すると 1 を、失敗すると 0 を返します。
if (!LockFile("data.loc", 10)) { # ロックをかける print "ロック失敗\n"; exit(1); } open(OUT, "> data.txt"); print OUT "Hello!!\n"; # ファイルを読み書きする close(OUT); UnlockFile(); # ロックを解除する
スクリプトの実行者がロックフォルダを作成できるようにアクセス権を設定しておいてください。UNIX の CGI で用いる場合は lock という名前のフォルダを 700 か 755 か 777 の パーミッション で作成しておき、ロックフォルダ名を "lock/data.loc" のようにするとよいでしょう。
mkdir() によるロックは、ロックフォルダの存在と作成を同時に行うことができるのが最大の特徴です。もし、これをバラバラに行ってしまうと、「ロックフォルダがまだ存在しない」と判断して「ロックフォルダを作成する」までの僅かの間に、他のプロセスが割り込んでしまう可能性が生じます。下記は、誤ったロックの方法です。
# よろしくない例 if (! -d $LOCKNAME) { # ロックフォルダが無ければ mkdir($LOCKNAME, 0755); # 作成する }
flock() によるロックは、プロセスが異常終了した時でも自動的にアンロックされますが、mkdir() によるロックは、プロセス異常終了時にロックフォルダが残ってしまうことがあります。うまく動作しない場合は、ロックフォルダが残った状態になっていないか、確認してください。
ロックを行うことで複数プロセス同時書き込みによるファイルの破壊を減らすことはできますが、書き込みの最中にプロセスが異常終了するなど、ファイル破壊を完全に防ぐことはできません。
指定したファイルの 改行コード が Windows形式(\r\n)か、UNIX 形式(\n)か、Mac OS 9 までの Macintosh形式(\r)かを調べます。
$code = GetRetCode("file.txt"); print "code = $code\n"; # # 改行コードを調べる # GetRetCode($file) # "win", "unix", "mac", "err", "unknown" # sub GetRetCode { local($file) = @_; local(*IN, $line, $mode); open(IN, $file) || return "err"; binmode(IN); $line = <IN>; if ($line =~ /\r\n$/) { $mode = "win"; } elsif ($line =~ /\n$/) { $mode = "unix"; } elsif ($line =~ /\r/) { $mode = "mac"; } else { $mode = "unknown"; } close(IN); return $mode; }
ファイルハンドル DATA は特別な意味を持ちます。スクリプト中に __END__ があるとスクリプトはそこで終わり、続く文字列はデータとみなされます。データの内容は DATA という特殊なハンドルを用いて読み出すことができます。
while ($line = <DATA>) { print $line; } __END__ <html> <head><title>TEST</title></head> <body>Hello world!!</body> </html>
これを実行すると、実行結果は下記のようになります。
<html> <head><title>TEST</title></head> <body>Hello world!!</body> </html>