ファイル

■ ファイルを読む(open, close)

◆ ファイルをオープンして読み込む

open() はファイルをオープンします。オープンされたファイルは、ファイルハンドル IN という変数で参照されるようになります。ファイルハンドルには好きな名前をつけることができますが、通常大文字の変数名を用います。

ファイルを読み込むには <IN> を用います。

close() はファイルをクローズして、ファイルハンドルを無効化します。

file1.pl
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文字読み込み

文字を1文字ずつ読みこむには getc() を用います。

open(IN, "data.txt");
while ($c = getc(IN)) {
    print "[$c]";
}
close(IN);

■ ファイルに書きこむ(print)

◆ 書き込みモードのオープン

ファイルに書きこむときは、open(ファイルハンドル, "> ファイル名") とします。open() を実行した時点でファイルの内容が全て削除されます。書き込みはファイルの先頭から行われます。書き込みには print()printf() などを用います。

file2.pl
open(OUT, "> data.txt");
print OUT "AAA\n";
print OUT "BBB\n";
close(OUT);
◆ print の詳細

print FILEHANDLE message は、FILEHANDLE で指定した ファイルハンドル に値を出力します。ファイルハンドルの後ろにカンマ(,)はありません。

print STDOUT "Hello World!!\n";

ファイルハンドルを省略すると、現在選択されているファイルハンドルに出力します。通常は 標準出力 STDOUT が選択されていますが、select() で変更することもできます。

print "Hello World!!\n";

値を省略すると、省略時変数 $_ の値を書き出します。

$_ = "Hello World!!\n";
print;

■ ファイルの末尾に追記する(>>)

◆ ファイル末尾への追加書き込み

open(ファイルハンドル, ">> ファイル名") の形式を用いると、ファイルの末尾に追加書きすることができます。これを、アペンドモード で開くとか、ファイルの末尾にアペンドするなどと言います。

file3.pl
open(OUT, ">> data.txt");
print OUT "AAA\n";
print OUT "BBB\n";
close(OUT);

■ ファイルの先頭に追記する(+<)

◆ ファイルの先頭への追加書き込み

ファイルの先頭に追加書き込みするには、一度すべてのデータを読み出し、先頭にデータを追加、再度、すべてのデータを書きこむという手順を用います。"+< ファイル名" は、ファイルを読み書き両モードでオープンすることを意味します。

file4.pl
open(FILE, "+< data.txt");         # ファイルを読み書きモードで開く
@lines = <FILE>;                   # すべての行を読み込む
unshift(@lines, "AAA\n", "BBB\n"); # 配列の先頭に行を追加
seek(FILE, 0, 0);                  # 書き込み位置をファイルの先頭に移動
print FILE @lines;                 # 配列をすべて書き込む
close(FILE);                       # クローズする

■ 読み書きする位置を移動する(seek)

◆ 読み書き位置の移動

seek() は、指定したファイルハンドルへの読み書き位置を移動するのに用います。seek(FILEHANDLE, n, from) の形式で利用し、from が 0 の時はファイルの先頭から n バイト目の位置へ、from が 1 の時は現在の位置から n バイト目の位置へ、from が 2 の時はファイルの末尾から n バイト目の位置へ移動します。n には負の値も指定できます。ファイルの先頭の文字を 0 バイト目と数えます。

file5.pl
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)

◆ 読み書き位置を知る

tell() は、指定したファイルハンドルの現在の読み書き位置を、ファイルの先頭からのバイト数で返します。ファイルの先頭の文字を 0 バイト目と数えます。

file6.pl
open(FILE, "data.txt");
seek(FILE, 100, 0);  # ファイルの先頭から100バイト目の位置に移動
$pos = tell(FILE);
close(FILE);

print "pos = $pos\n";

実行結果は次のようになります。

pos = 100

■ ファイルのサイズを得る(-s, lstat)

◆ -s を用いる方法

ファイルテスト演算子の -s は、指定したファイルのサイズをバイト数で返します。これにより、ファイルサイズを求めることができます。

file7.pl(1)
# ファイルのサイズを求める(方法1)
$size = -s "data.txt";
print "size = $size\n";
◆ lstat()を用いる方法

別の方法として、lstat() を用いてもファイルサイズを求めることができます。ファイルがすでにオープン済みであれば、lstat() を用いた方が効率はよいようです。

file7.pl(2)
# ファイルのサイズを求める(方法2)
open(IN, "data.txt");
$size = (lstat(IN))[7];
print "size = $size\n";
close(IN);

■ ファイルのサイズを変更する(truncate)

◆ ファイルサイズの変更

ファイルサイズを増やすには、ファイルに追加書きをすることで可能です。ファイルサイズを小さくするには、ファイルを最初から作りなおすか、truncate() を用います。truncate(FILEHANDLE, size) は、指定したファイルハンドルのファイルサイズを size バイトに変更します。

file8.pl
open(FILE, "+< data.txt");
truncate(FILE, 1024);
close(FILE);

ファイルは書き込みモード、または読み書き両用モードでオープンされている必要があります。

■ 標準の出力先を変更する(select)

◆ 標準の出力先の変更

print()、printf() などの出力関数では、出力先のファイルハンドルを省略した場合、STDOUT に書き出します。select() を用いることにより、これを変更することができます。select() の戻り値には、それまでの出力先ファイルハンドルが返されます。

file9.pl
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 の バッファリング機能 によります。

file10.pl
open(OUT, "> data.txt");
print OUT "AAA\n";
    open(IN, "data.txt");   # AAAはまだバッファリング
    print <IN>;             # された状態なので、
    close(IN);              # 読み出すことができない。
close(OUT);

これを実行すると、AAA はまだメモリ中にバッファリングされているので、読み出すことができず、何も表示されません。

◆ バッファリングを禁止する

バッファリングされたデータを実際に書きこむことを フラッシュ と呼びます。書き込みのたびにフラッシュさせるためには、前節で紹介した、ファイルハンドル OUT に対するバッファリング禁止のテクニックを用います。

file11.pl
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)

◆ 低レベル入出力関数

sysread()syswrite()sysseek() は、バッファリングを行わない、低レベルの読み込み、書き込み、位置変更を行います。Perl のプログラミングではあまり使用されることはありません。

open(FILE, ">+ data.txt");
sysread(FILE, $buf, 10);
sysseek(FILE, 0, 0);
syswrite(FILE, $buf, 10);
close(FILE);

■ ファイルの末尾を判断する(eof)

◆ ファイル末尾の判断

eof() は、End Of File の略で、ファイルの読み込みが末尾まで達したかどうかをチェックします。ただし、通常は while (<IN>) の形式で事足りるので、これもあまり用いられることはありません。

open(IN, "data.txt");
while (<IN>) {
    print;
    if (eof(IN)) {
        print "---END---\n";
    }
}
close(IN);

■ ファイルディスクリプタを得る(fileno)

◆ ファイルディスクリプタを得る

fileno() はファイルハンドルに関連付けられた ファイルディスクリプタ という整数値を返します。このファイルディスクリプタは、select() などで使用されます。

open(IN, "data.txt");
$fdes = fileno(IN);
print "fdes = $fdes\n";
close(IN);

■ ファイルをロックする(flock)

◆ ロックの必要性

複数のプロセスがひとつのファイルに対して同時に読み書きを行うと、ファイルの内容が壊れてしまうことがあります。

lock1.pl
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 ファイルを作成しておいてください。

lock2.pl
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によるファイルロック(flock)

◆ flock の基本的使用方法

flock() は、Perl でファイルロックを行う最も優れた方法です。flock() の基本的な使用方法を示します。

lock3.pl
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 の第二引数

flock() の第二引数には下記の値を指定します。これらの値を使用するには、use Fcntl ':flock'; を宣言しておく必要があります。use Fcntl を使用できない場合は、LOCK_XX の代わりに直接数値を指定してください。

フラグ数値説明
LOCK_SH1共有ロック。ファイルを読みこむ際に使用。他のプロセスが共有ロックすることはできるが、排他ロックは禁止される。
LOCK_EX2排他ロック。ファイルに書きこむ際に使用。他のプロセスは共有ロックも排他ロックも禁止される。
LOCK_UN8ロック解除。通常は close() の時点で自動的に解除されるので、明示的に解除しなくてもよい。
LOCK_NB4ノンブロッキングモード。LOCK_SH | LOCK_NB や、LOCK_EX | LOCK_NB のように使用。ロックが禁止されている場合、通常はブロックするが、LOCK_NB 指定時は、ブロックする代わりに flock() がエラーを返す。
◆ flockの注意点(1) - flockをサポートしていないOS

flock() は Windows 98 など一部の OS ではサポートされていません。未サポート OS で flock() を使用すると致命的エラーとなり、プログラムが異常終了してしまいます。flock() をサポートしていない場合の対処方法は「mkdirによるファイルロック」を参照してください。

◆ flockの注意点(2) - ロックするまえのファイル更新

ファイルをロックする際に open(OUT, "> file"); や open(OUT, "+> file"); の形式は用いてはなりません。flock() でファイルをロックする前にファイルの内容が消去され、この状態のファイルを読み込んでしまうプロセスが発生してしまいます。

# ロックがうまく機能しない例
open(OUT, "> data.txt");
flock(OUT, LOCK_EX);
    :
close(OUT);
◆ flockの注意点(3) - フラッシュするまえのロック解除

古いバージョンの perl では、close() の前に flock(OUT, LOCK_UN) を呼び出してしまうと、バッファリングされたデータがフラッシュされる前にロックが解除されてしまい、他のプロセスが中途半端な内容のファイルを読み込んでしまうという問題がありました。

■ mkdirによるファイルロック(LockFile)

◆ mkdirによるロック

flock() がサポートされていないシステム用では、別の方法を用いてロックを実現する必要があります。下記では、mkdir() を用いたロックの方法を紹介します。

lock4.pl
$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);           # 作成する
}
◆ 注意事項(1)

flock() によるロックは、プロセスが異常終了した時でも自動的にアンロックされますが、mkdir() によるロックは、プロセス異常終了時にロックフォルダが残ってしまうことがあります。うまく動作しない場合は、ロックフォルダが残った状態になっていないか、確認してください。

◆ 注意事項(2)

ロックを行うことで複数プロセス同時書き込みによるファイルの破壊を減らすことはできますが、書き込みの最中にプロセスが異常終了するなど、ファイル破壊を完全に防ぐことはできません。

■ 改行コードを調べる

◆ 改行コードを調べる

指定したファイルの 改行コード が Windows形式(\r\n)か、UNIX 形式(\n)か、Mac OS 9 までの Macintosh形式(\r)かを調べます。

getretcode.pl
$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;
}
一口メモ

UNIX の perl や ActivePerl では、\r\n や \n を改行として読み込みますが、\r だけでは改行とみなされません。Macintosh 形式のファイルを $line = <IN> で読み込むとすべての行が 1行として読み込まれます。

■ スクリプトの末尾にデータを埋め込む(__END__)

◆ スクリプトの末尾にデータを埋め込む

ファイルハンドル 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>

Copyright (C) 2002 杜甫々