[PR]NI50餅攵渺:FSヤI

ファイルの読み書き

=Perl, =結果


僕の写真がいっぱいです!いやでも見てください!!写真リスト」や「きょうは何の日」ではファイルの読み込みについて説明をしました。しかし、既にあるファイルの内容を表示しているだけで、ファイルを新規に作成したり、既にあるファイルを修正したり、追加書き込みすることはできません。このページではファイルの読み込み、書き込みについて説明をし、最後にファイルの削除にも触れておきます。


(1) ファイルの読み込み

先ずデータをそのまま表示します。(l_open1.cgi)
#!/usr/local/bin/perl

print "Content-type: text/html\n\n";
open(NENGAPPI, './data/l_open01.txt');
while (defined($line = <NENGAPPI>)) { print "$line<br>"; }
close(NENGAPPI);

exit;

print "Content-type: text/html\n\n";
ブラウザに送るデータ形式はHTMLだと指示しています。

open(NENGAPPI, './data/l_open01.txt');
ファイルハンドル(NENGAPPIとしました)とCGIを配置しているディレクトリの下の階層 \data にある l_open01.txt を結び付けました。

while (defined($line = <NENGAPPI>)) { print "$line<br>"; }
先ほど関連付けたNENGAPPIを一行ずつ$lineに読み込み、表示しています。ファイルが最後まで読み込まれると、繰り返しの処理は終了します。

close(NENGAPPI);
オープンしたNENGAPPIをクローズしています。

結果は下のように表示されました。
2002/04/16 15:40:38
2002/04/16 15:41:17


(2) ファイルの複写 【>】

ファイルを読み込むことができましたので、次はこのファイルをコピーして、別のファイル名で保存してみましょう。(l_open2.cgi)

#!/usr/local/bin/perl

print "Content-type: text/html\n\n";
open(NENGAPPI, './data/l_open01.txt');
@file = <NENGAPPI>;
close(NENGAPPI);

open(NEWFILE, '>./data/l_open02.txt');
print NEWFILE @file;
close(NEWFILE);

print "複写が終了しました";

exit;
l_open2.cgi を実行すると「複写が終了しました」と表示されるだけですが、l_open01.txt の内容が l_open02.txt に複写されています。

@file = <NENGAPPI>;
関連付けされたファイルハンドルNENGAPPIを、一気に配列@fileに代入しています。

open(NEWFILE, '>./data/l_open02.txt');
ファイル名の前にある > に注目してください。新規にファイルを作成するには > を使います。ここでは l_open02.txt を、書き込み出力用としてオープンしています。
(> の他にもいろいろな記号があります。このページの下に(7)ファイルを扱う記号一覧を用意しました)

しかし実際に > を使う時には、充分に注意が必要です。> は、ファイルが存在していないときには、新規作成になります。しかし同名のファイルが存在するときには、そのファイルを消して(削除ではなく0バイトにする)出力用にオープンするのです。指定を誤ると大事なファイルをなくしてしまうことになりますので注意が必要です。

print NEWFILE @file;
先ほど代入した @file の値を、ファイルハンドルに書き込んでいます。この場合は、全く同じ内容を複写したことになります。
print NEWFILE, @file; とファイルハンドルの後にコンマ(,)を付けるとエラーとなりますので注意してください。

ファイルの書き込みや削除するときには、ファイルの実行権限(パーミッション)を考慮する必要があります。PCの環境では問題が起こらなくてても、サイトにFTPするとうまく結果が得られないときはパーミッションが原因かもしれません。今回は新しい名前のファイルを作成するので、そのデータを入れるディレクトリのパーミッションは777にしてあります。


(3) エラー処理

ファイルが存在せずにエラーとなったときに、エラー表示して終了するには下のような書き方ができます。

open(NENGAPPI, './data/l_open01.txt') or die "$!";
ファイルが存在するとき(真)にはオープンし、存在しない(偽のとき)には or 以降を実行します。die は処理の終了、"$!" は直前に発生したエラーメッセージを現す特殊な変数です。

その他、下のように書くこともできます。
if (!open(NENGAPPI, './data/l_open01.txt')) { die "$!"; }

unless (open(NENGAPPI, './data/l_open01.txt')) { die "$!"; }

このように書くこともできます。
if (!open(NENGAPPI, './data/l_open01.txt')) {
	print "ファイルが開けません";
	exit;
}


(4-1) ファイルの修正 【>】

データを読み込んで修正後、同じファイルに上書きをしてみましょう。 先ほど(2)で複写した l_open02.txt を修正してみます。l_open02.txt は、下のようなデータです。
2002/04/16 15:40:38
2002/04/16 15:41:17

修正内容は、行の先頭に行番号を付けてみることにします。(l_open21.cgi)
#!/usr/local/bin/perl

print "Content-type: text/html\n\n";
open(NENGAPPI, './data/l_open02.txt') or die "$!";
@record = <NENGAPPI>;
close(NENGAPPI);
$j = scalar(@record);
for($i = 0;$i < $j;$i++) {
	$record[$i] = $i + 1 . "." . $record[$i];
}
open(NENGAPPI, '>./data/l_open02.txt') or die "$!";
print NENGAPPI @record;
close(NENGAPPI);

print "処理が終了しました";

exit;

open(NENGAPPI, './data/l_open02.txt') or die "$!";
@record = <NENGAPPI>;
close(NENGAPPI);
./data/のディレクトリにある l_open02.txt をファイルハンドルNENGAPPIに関連付けてオープンしています。もしオープンできないときはエラー表示をします。読み込んだNENGAPPI全てを@recordに代入し、ファイルをクローズします。

$j = scalar(@record);
for($i = 0;$i < $j;$i++) {
	$record[$i] = $i + 1 . "." . $record[$i];
}
scalar(@record) は @record の配列の数を示します。先ほどファイルを読み込み、内容を代入した @record の中に、何行のデータがあるか数えています。
for($i = 0;$i < $j;$i++) は ( )内の条件が満たされているときに、{ }内の処理を行なうという条件式です。この場合の条件は「$i が 0 から始まり、$i が $j よりも小さい間に { }内の処理をする。そして処理毎に $i に 1 を加算する」ことです。$i は配列の添え字、$jは配列の数を示しています。
処理内容では、文字列を連結する意味の ピリオド(.) を使っています。$iに1を加算(付け加えたい「行番号」は添え字に1を足したもの。[0]なら1としたい)したものに、ピリオド(.)を連結し、さらに$record[$i]に連結しています。
書換え前書換え後
@record$record[0]2002/04/16 15:40:381.2002/04/16 15:40:38
$record[1]2002/04/16 15:41:172.2002/04/16 15:41:17

open(NENGAPPI, '>./data/l_open02.txt') or die "$!";
print NENGAPPI @record;
close(NENGAPPI);
再度l_open02.txtをオープンし、先ほど書き換えた@recordをファイルハンドルNENGAPPIに置換え、ファイルをクローズしています。

open(NENGAPPI, '>> は (2)のファイルの複写でも紹介しました。ファイルが存在する時には、ファイルを上書きし、ファイルが存在しないときには新規にファイルを作成します。ただしファイルが存在するときには、ファイルをオープンした瞬間に、そのファイルを0バイトにしてしまいます。ファイル名は残るのですが、ファイル内容は消えてしまっています。そして print NENGAPPI @record と close(NENGAPPI) で0バイトになったファイルに上書きをしています。処理を誤ったり、処理の途中で電源が切れることも考慮して、重要なファイルはバックアップをしてから扱うことをお勧めします。

これを実行すると下のようになります。
1.2002/04/16 15:40:38
2.2002/04/16 15:41:17


(4-2) ファイルの修正 【+<】

> だけではなく、他の方法でも同様のことはできます。次は +< を使ってファイルの修正をしてみます。処理内容は先ほどと同じく行番号付けです。(l_open22.cgi)
#!/usr/local/bin/perl

print "Content-type: text/html\n\n";
open(NENGAPPI, '+<./data/l_open02.txt');
@record = <NENGAPPI>;
$j = scalar(@record);
for($i = 0;$i < $j;$i++) {
	$record[$i] = $i + 1 . "." . $record[$i];
}
print NENGAPPI @record;
close(NENGAPPI);

print "処理が終了しました";

exit;

先ほどのスクリプトとの違いは、ほんの僅かです。先ほどは読み込み専用(記号無し)でいったん読み込み、クローズ。さらに同じファイルを書き込み用(>)として再度オープンし、修正した内容を書き込みました。今度のスクリプトでは一回だけ読み書き両用(+<)でオープンをしています。
実は、このスクリプトではファイルの書換えはできないのです。少し複雑になってしまいますが、説明を続けます。

@record = <NENGAPPI>;

print NENGAPPI @record;
問題の個所はここです。l_open21.cgi では問題は起こらなかったのですが、同じファイルに対して入出力を指定すると問題が発生します。
ファイルを操作しているときには、今どのレコードを処理しているかを示しながら処理が進みます。その情報をファイルポインタといいます。
@record = <NENGAPPI>; でファイルを読み込むには、先ずデータの先頭にファイルポインタがあり、全レコードを読み込み終わったときには最後のデータの後ろにファイルポインタは移っています。次に NENGAPPI @record; を実行するときにも、ファイルポインタは最後のデータの後ろに移ったままになっています。読み込んだファイルに書き込むには、ファイルポインタはデータの先頭になければいけません。
ファイルポインタを指定の位置にセットする関数 seek を使って、スクリプトを書き換えてみます。

先ほどの l_open22.cgi に一行付け加えてみました。(l_open23.cgi)
#!/usr/local/bin/perl

print "Content-type: text/html\n\n";
open(NENGAPPI, '+<./data/l_open02.txt');
@record = <NENGAPPI>;
$j = scalar(@record);
for($i = 0;$i < $j;$i++) {
	$record[$i] = $i + 1 . "." . $record[$i];
}
seek(NENGAPPI, 0, 0);
print NENGAPPI @record;
close(NENGAPPI);

print "処理が終了しました";

exit;

CGIを実行してみると、今度は正しく処理ができています。しかし次のような一行を加えてみると、おかしな結果になってしまいます。(l_open24.cgi)
#!/usr/local/bin/perl

print "Content-type: text/html\n\n";
open(NENGAPPI, '+<./data/l_open02.txt');
@record = <NENGAPPI>;
$j = scalar(@record);
for($i = 0;$i < $j;$i++) {
	$record[$i] = $i + 1 . "." . $record[$i];
}
$record[0] = "ABCD\n";
seek(NENGAPPI, 0, 0);
print NENGAPPI @record;
close(NENGAPPI);

print "処理が終了しました";

exit;

ABCD
2.2002/04/16 15:41:17
16 15:41:17
処理結果は上のようになってしまいました。赤字の部分が、どうたら重複しているようです。なぜこのような結果になったのか、下を見てください。
バイト数1234567891
0
1
1
1
2
1
3
1
4
1
5
1
6
1
7
1
8
1
9
2
0
2
1
2
2
2
3
2
4
2
5
2
6
2
7
2
8
2
9
3
0
3
1
3
2
3
3
3
4
3
5
3
6
3
7
3
8
3
9
4
0
4
1
4
2
元データ2002/04/16 15:40:382002/04/16 15:41:17
現データABCD2.2002/04/16 15:41:17
改行コードは2バイトです。先ほどの問題は、元のデータと書き換えられたデータとの長さが影響していました。l_open23.cgi で正常に処理されたように見えたのは、処理前より処理後のデータが長かったから問題が発生しなかった訳です。
それでは元データの不要な残りのデータを切り詰めるにはどうしたらいいのでしょうか。

先ほどの l_open24.cgi に赤字の行を加えて tell関数を調べてみましょう。
$record[0] = "ABCD\n";
seek(NENGAPPI, 0, 0);
print NENGAPPI @record;
print tell(NENGAPPI);
close(NENGAPPI);
結果は29と表示されました。29バイト目の後ろにファイルポジションがあることが分かりました。このポジションから後ろを truncate関数で切り詰めれば解決です。

これでやっと完成です。(l_open25.cgi)。
#!/usr/local/bin/perl

print "Content-type: text/html\n\n";
open(NENGAPPI, '+<./data/l_open02.txt') or die "$!";
@record = <NENGAPPI>;
$j = scalar(@record);
for($i = 0;$i < $j;$i++) {
	$record[$i] = $i + 1 . "." . $record[$i];
}
$record[0] = "ABCD\n";
seek(NENGAPPI, 0, 0);
print NENGAPPI @record;
truncate(NENGAPPI, tell(NENGAPPI));
close(NENGAPPI);

print "処理が終了しました";

exit;
ABCD
2.2002/04/16 15:41:17

ファイルの読み書き両用を扱う時には、書き出す前にファイルポジションを先頭に戻してから、現在のファイルハンドルの長さ以降を切り詰める必要があります。


(5) ファイルの追加書き込み 【>>】

ファイルを読み込んで、新しいデータを追加書き込みしてみましょう。
#!/usr/local/bin/perl

($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time);
$logtime = sprintf("%04d/%02d/%02d %02d:%02d:%02d", $year + 1900, $mon +1, $mday, $hour, $min, $sec);

print "Content-type: text/html\n\n";
if (!open(NENGAPPI, '>>./data/l_open03.txt')) {	print "ファイルが開けません"; exit; }
print NENGAPPI "$logtime\n";
close(NENGAPPI);

print "書き込み終了しました";

exit;

($sec, $min,・・・の行では localtime関数を使って現在の時刻を取得しています。
$logtime = sprintf(・・・の行では、取得した時刻を表示しやすい形にして、変数に代入しています。詳しい説明は「年月日や曜日・時刻の取得」をご覧ください。

if (!open(NENGAPPI,・・・の行では >> を使用して、l_open03.txt を追加書き込み用としてオープンしています。

print NENGAPPI "$logtime\n";
localtime関数で取得した時刻を代入した変数、$logtimeをファイルハンドルNENGAPPIに書き込みます。

これは簡単なアクセスログのようなものです。CGIを実行したときの年月日と時分秒をデータに追加しています。

CGIを実行しますl_open4.cgi 追加書き込み

CGIを実行しますl_open03.txtを見る

アクセスログらしくするには、時間だけでなくリモートアドレスなどを書き込めばいいですね。
print NENGAPPI "$logtime\;$ENV{'REMOTE_ADDR'};\n";
これでアクセス時間とIPアドレスが書き込まれるようになります。


(6) ファイルの削除

最後にファイルの削除について書いておきます。(2)の「ファイルの複写」でも説明しましたが、削除するためには削除する権限が与えられていなければなりません。

  • unlink
    • unlink LIST
      LISTで指定されたファイルを削除します。LISTを省略したときには、$_ で指定されたファイルを削除します。

      【→Perl Reference: unlink

      unlink('./data/l_open03.txt');
      これで l_open03.txt が削除されます。

      ディレクトリを削除するには unlinkではなく rmdir を使用します。そのときにはディレクトリの中に、ファイルやディレクトリがないことが前提となります。


    (7) ファイルを扱う記号一覧

    記号入出力使用例同名ファイル
    存在あり
    同名ファイル
    存在なし
    なし読み込みopen(COCOFILE, 'coco.txt')
    open(COCOFILE, '<coco.txt') と同じ
    変わらずエラー
    <読み込みopen(COCOFILE, '<coco.txt')
    open(COCOFILE, 'coco.txt') と同じ
    変わらずエラー
    >書き込みopen(COCOFILE, '>coco.txt')0バイトに新規作成
    >>追加書き込みopen(COCOFILE, '>>coco.txt')最終行に追加新規作成
    +<読み書きopen(COCOFILE, '+<coco.txt')変わらずエラー
    +>読み書きopen(COCOFILE, '+>coco.txt')0バイトに新規作成
    +>>読み書きopen(COCOFILE, '+>>coco.txt')最終行に追加新規作成
    その他。 | はファイル名の先頭に置くとパイプの役をし、最後に置くと標準出力内容をファイルハンドルから読み込む。またSTDINを表す - 、STDOUTを表す >- もあります。


    | Back | Coco's Home | ココとPerlで遊びませんか |

    Copyright 2001-2002 Coco's Home. All rights reserved.

  • [PR]liLOハ^:LOハ^lCハ^lョハ^