Perl/CGIプログラムのファイル読み書き

今回は、Perl/CGIプログラムから別のファイルを操作する方法について学習していきます。

  1. 編集前記
  2. ファイルの読み書き
  3. ファイルの読み取り
  4. ファイルの上書き
  5. ファイルの追加書き込み
  6. 編集後記

編集前記

今回の編集前記は、本編でファイル処理を学習するので、 絶対パスと相対パスについて お話します。

わざわざいまさら取り上げるべきテーマではないような気もしますが…。

Perl/CGIプログラムを作成 していく以上、というかそれ以前にパソコンを使いこなしていく以上必要な知識ですから。

ここで再確認の意味を込めて、改めて絶対パスと相対パスについて覚えておきましょう。

もし、パス指定についてよくわかっていなかった場合には、今回学習する内容がいまいち使いこなせないということになるので、ここでしっかり覚えていってくださいね。

それでははじめましょう。

まずは、パスという言葉についてはいいですよね。

パスとは、目的のファイルやフォルダの場所を特定するために使われる道筋のことです。

このパスという考え方がわかっていないと、せっかく作成したファイルの行方がわからなくなってしまいます。

パソコン初心者が、デスクトップ以外の場所にファイルを作成してしまったがために、次回アクセスできなくなってしまう現象が発生するのは、このパスという考え方を理解していないからですね。

あなたの周りを見渡しても何人かいるはずです。

でもこれは単純に、自分が中心となって目的のファイルやフォルダを探しているだけですからそんなに難しい話ではありません。

もう少しレベルが上がってくると、話の中心が自分ではなく、ひとつのファイルから見たもうひとつのファイルの場所を表すという話に変わります。

わかりやすいところでは、 ホームページ作成 に使われる リンク という考え方がそうですね。

リンクタグのパスを組み立てるとき、現在いるページから相手ページまでのパスを考えますよね。

まぁ何も考えずに、直接URLで指定することもありますが(苦笑)。

そうではなくあるファイルを起点に、別のファイルまでの道筋を考えてパスを組み立てることもあるわけですよ。

これらのパスの組み立て方の違いのことをそれぞれ、絶対パスと相対パスといいます。

ほかにも、仮想パスという用語もあります。

しかしあまり登場しないので、パスします。

パスなだけに(笑)。

今、読者さんが半分ぐらい減ったような…。

気のせいですよね(苦笑)。

話を戻しまして、絶対パスと相対パスについてお話していきますね。

いきなりそれぞれのパスの違いについてお話してもよいのですが、まずは感覚的につかんでいただきたいので、こんなたとえ話を用意しました。

感覚的にでも理解しておけば、後に学習したときの吸収が早いです。

では想像の世界へ(笑)。

あなたは小学5年生の子どもを持った親だとします。

今日は授業参観なので、保護者であるあなたは学校に行きました。

校舎は6階建てで…。

1年生は1階。

2年生は2階。

3年生は3階。

こんな感じで5年生は5階です。

思いっきり単純な構成にもかかわらず、あなたは目的の5年生の教室がわからなくなってしまいました。

あなたが3階でうろうろしていると、人が通りかかったので、あなたは5年生の教室までの道をたずねました。

そのときに…

「5年生の教室は、この2階上ですよ。」

という道の教え方で教えられた場合、それは相対パス的な教え方をされたことになります。

いまあなたがいる場所を基準として、目的地を示していますよね。

あなたが3階で訊いたからこそ「この2階上ですよ」という答えが返ってきたのです。

もしあなたが3階ではなく、2階でたずねたとしたら…

「5年生の教室は、この3階上ですよ。」

となるはずです。

このように訊いた場所によって、教えられ方も変わりますよね。

これが相対パス的な感覚です。

そうではなく…

「5年生の教室は、5階ですよ。」

という道の教え方をされた場合、これは絶対パス的な教え方をされたことになります。

この教え方であれば、あなたがどこの階にいても、同じ教えられ方が通用します。

ちょっとわかりにくかったでしょうか?

まぁでも感覚的に説明するとこんな感じです。

次は、具体例を挙げつつ絶対パスと相対パスについてお話していきますね。

絶対パスとは、大本の部分から目的のファイルやフォルダの位置を表現するパスの指定方法のことです。

大本の部分から順に表現されているので、奥まった部分にあるファイルを指定するときなどは、どうしてもパスが長くなってしまいます。

書き方の例としては、下記のようなものがあります。

例1: http://www.xxx.com/xxx/xxx.html

例2: /xxx/xxx.html

例1はURLで、インターネット上のどの位置から見ても変わることのないパス指定方法です。

例2では、同じサイト(同じサーバー)内のファイルに限りどの位置から見ても変わることのないパス指定方法です。

前提条件は異なりますが、どちらも大本からたどっているので絶対的な力があるわけです。

これが絶対パスです。

それに比べて相対パスとは、自分の今いる位置を基準にし、目的のファイルやフォルダを指定するパスのことです。

ですから、自分と相手の位置関係が近ければパスは短くなりますし、遠ければ長くなってしまいます。

相対パスは相互関係によって、書き方が変わってきますから、いくつか例を紹介しておきますね。

1. 同じレベル内でのリンク

同じフォルダ内で違うファイルにリンクするときは、ファイル名のみを指定するか、ファイル名の前に「./」をつけます。

例えば、

「http://yourdomain.com/index.html」

から

「http://yourdomain.com/index2.html」

に相対パスでパスを組み立てるとしたら、以下の2つの方法があります。

1つ目は、「index2.html」のように、直接ファイル名のみを指定する方法。

2つ目は、「./index2.html」のように、同じ位置にファイルが存在するという意味の「./」を頭に付ける方法。

どちらでもOKです。

2. 下の階層へリンク

下の階層(奥まったフォルダ内のファイル)にリンクするときは、それぞれのフォルダ名またはファイル名とのあいだに、「/」をつけます。

例えば、

「http://yourdomain.com/index.html」

から、

「http://yourdomain.com/sample/cgi/index.html」

に相対パスでパスを組み立てる場合は以下のような感じになります。

1つ目は、「sample/cgi/index.html」。

2つ目は、「./sample/cgi/index.html」。

どちらも、起点となるファイルの位置からフォルダをたどって、目的のファイルまでを表しています。

もちろん、どちらでもOKです。

3. 上の階層へリンク

上の階層(親のフォルダ)のファイルにリンクする場合は、上位に上がるフォルダの分だけ「../」をつけます。

「../」ひとつにつき1フォルダです。

例えば、

「http://yourdomain.com/sample/cgi/index.html」

から、

「http://yourdomain.com/index.html」

に相対パスでパスを組み立てる場合は以下のようになります。

「../../index.html」となります。

「cgi」と「sample」の2つのフォルダを上がった先の「index.html」ファイルを指定しています。

4. 別のフォルダへリンク

全く別のフォルダ内のファイルを相対パスで指定するには、

「../」で一度共通の派生元まで戻って、そこから新たにフォルダ名とファイル名を指定します。

例えば、

「http://yourdomain.com/sample/cgi/index.html」

から、

「http://yourdomain.com/lineup/index.html」

に相対パスでパスを組み立てる場合は、以下のようになります。

「../../lineup/index.html」。

「../../」で「cgi」と「sample」フォルダを上がって、その後に「lineup」フォルダ内の「index.html」ファイルを指定しています。

以上が相対パスの具体例です。

絶対パスはパス指定を間違えにくい反面、使用する環境が限定されてしまいます。

これに対して相対パスは、慣れるまでは少し複雑ですが、ファイルやフォルダ構成さえ変更しなければいろいろな環境で使えます。

Perl/CGIプログラムでファイル処理を行う際は、絶対パスよりも相対パスを使う場合が多いので、なるべく相対パスを使えるようにしておきましょう。

それでは、Perl/CGIプログラム学習に移りましょう。

今回は、Perl/CGIプログラムから別のファイルを操作する方法について学習していきます。

ここで言う別のファイルというのは、同じサイト内でアクセスの許可されている記録ファイルなどのことだと思ってください。

ようするに、Perl/CGIプログラムから何らかのデータを記録ファイルに書き出したり、記録ファイルを読み取って、その内容を修正したり追加書き込みできるようなことを学習していくということです。

絶対パスと相対パスが必要だと言ったのはこのためですね。

今回学習するようなファイル処理ができるようになると、Perl/CGIプログラムでできることの幅がぐっと広がって、さらに楽しくなりますよ。

それでは、ファイル処理に使用する関数やプログラム作成方法について学習していきましょう。

ファイルの読み書き

Perl/CGIプログラム 本体から外部ファイルを操作するには、ファイルハンドルという仕組みを使います。

ファイルハンドルとは、イメージ的にはその名のとおり、ファイルを操作するハンドルのことです。

Perl/CGIプログラム本体から、外部ファイルを指定して開くとき、同時に任意のファイルハンドル名を作成し関連付けます。

そしてその後のファイル処理には、ファイルハンドル+命令で、外部ファイルの操作ができるようになるという仕組みになっています。

さらに、ファイルハンドルという仕組みは外部ファイルにかかわらず、Sendmailなどのプログラムを操作するときなどにも使います。

Sendmailというのは、電子メールを送信するプログラムのことです。

多くのインターネットサーバーにインストールされており、Perl/CGIプログラムから電子メールを送信するときに使います。

このSendmailについても書きだしたらきりがないので、また別の機会に説明します。

話を戻しまして、ここではファイルハンドルを使って、外部ファイルを操作する方法についてみていきましょう。

書式

ファイル処理の基本的な流れはこんな感じです。

open (ファイルハンドル, 外部ファイルのオープンモードとパス指定)

ファイルハンドルを利用した読み込みや書き込み処理

close (ファイルハンドル)

open関数

まず、外部ファイルを開くためにはopen関数を使います。

open関数の引数には、ファイルハンドル、ファイルのオープンモードと外部ファイルまでのパスを指定します。

ファイルハンドルには半角英数が使えます。

でもなぜかは知りませんが、半角英大文字のみを使って指定している場合がほとんどです。

まぁそうすると、ほかの関数や変数などと区別しやすくなるのでそうするのだと思うのですが。

本当のところはよくわかりません。

今回のPerl/CGI学習でもその流れに従って、半角英大文字のみで指定していきましょう。

ファイルのオープンモードというのは、読んだ意味そのままでファイルを開くモードのことです。

つまり、どんな使用用途でファイルを開くのかを、あらかじめ指定しておかなくてはいけないということです。

内容を読むだけなのか?

内容を変更したいのか?

などを、あらかじめ指定しておきなさいということです。

ファイルのパスには、操作したいファイルの絶対パスまたは相対パスを指定します。

絶対パスといっても、URLは使えないので注意です。

絶対パスと相対パスについてはいいですよね。

open関数は、成功時には「0」以外を返し、失敗時には未定義値を返します。

この性質を利用し、 ifやor と組み合わせてopen関数の動作状態を管理するのが常套手段です。

open関数については以上です。

次は、open関数で開いたファイルに対してさまざまな処理を記述していくわけなのですが、これはファイルのオープンモードや、具体的にどんなことをしたいのかによって変わってくるので後述しますね。

close関数

そして最後は、close関数についてです。

close関数はその名のとおり、ファイルを閉じる関数です。

すでにopen関数で外部ファイルまでのパスとファイルハンドルが関連付けられていますから、close関数の引数には直接ファイルハンドルを指定するだけで、ファイルを閉じることができます。

まれにですが、close関数を使わない人がいます。

開けたものは必ず閉めておきましょう(笑)。

それでは次は、具体的なファイル処理を見ていきましょう。

<戻る>

ファイルの読み取り

まずは、外部ファイルの内容を読み取って表示させるところからはじめましょう。

その準備として、Perl/CGIプログラム本体が読み込む外部ファイルを作成します。

ウィンドウズ付属のメモ帳などのテキストエディタを使って、「log.txt」という名前のテキストファイルを作成してください。

そしてその中身は、以下のような文字列にします。

Taro
Jiro
Saburo

「Saburo」の後は、しっかり改行してくださいね。

少し怪しげなファイルですが、これで外部ファイルの準備ができました。

次に、外部ファイルを読み取るPerl/CGIプログラム本体を作成します。

#!/usr/bin/perl

use strict;

my $file = 'log.txt';
open(IN,"$file");
my @file = <IN>;
close(IN);

print "Content-type: text/html\n\n";
foreach (@file) {
print $_, "<br>\n";
}
exit;

このPerl/CGIプログラムと、データが格納されている「log.txt」ファイルは、同じフォルダ内に入れ、今までと同じようにASCIIモードでアップロードします。

そして、このPerl/CGIプログラムを実行すると、以下のように表示されます。

Taro
Jiro
Saburo

「log.txt」の中身がそのまま表示されました。

「log.txt」の内容を 配列 「@file」に代入し表示させています。

もう少し細かく解説しますと…。

まずはopen関数「open(IN,"$file");」で、open関数で外部ファイルまでのパスを格納した 変数 「$file」と、ファイルハンドル名「IN」を関連付けしつつ、「log.txt」を読み込みモードで開いています。

ここでは、外部ファイル「log.txt」のオープンモードを省略しています。

その場合、自動的に読み込みモードとなります。

オープンモードを省略せずに、読み込みモードで指定したい場合は「open(IN,"<$file");」というように「<」をつけます。

次は、実際にデータを読み込む部分「my @file = <IN>;」について見ていきましょう。

外部ファイルをオープンした後、その内容を読み出すには行入力演算子「<>」でファイルハンドルをはさみます。

この例では、ファイルハンドル「IN」に関連付けられた外部ファイル「log.txt」の内容を、配列「@file」にすべて代入させています。

このように書くと一気にすべての行を代入しているような感じがしますが、もう少し細かく説明すると、「<>」は行入力演算子ですから、「log.txt」の内容を一行ずつ読み込みます。

すると、1行ずつ受け取った配列「@file」は、新たな末尾の要素を作成し順次追加していくという仕組みになっているのです。

つまり…

$file[0]には1行目の「Taro」。
$file[1]には2行目の「Jiro」。
$file[2]には3行目の「Saburo」。

それぞれこのような文字列が代入されていくのです。

もっと正確に言うと、それぞれの文字列の末尾には改行コードもつくのですが、その話をしだすと長くなってしまうので、今は気にしなくていいです。

今はとにかくこのように、ファイルハンドルからの値を配列で受け取るようにすると、外部ファイルの内容をすべて読み込むことができると覚えておいてください。

ちなみに、配列ではなく「my $line = <IN>;」というように単純変数を使用した場合には、1行目の文字列「Taro」と改行コードしか読み込まれません。

最後はclose関数でファイルハンドルを指定し外部ファイルを閉じます。

「close(IN);」という部分がそうですね。

これでファイル処理部分は終了です。

あとは、外部ファイルのデータを読み込んだ配列「@file」を foreachループ を使って1行ずつ表示させています。

このあたりはわかりますよね。

もし忘れてしまった場合は復習しておいてください。

ファイル処理の省略

ファイル処理中で省略できる部分があるので、ここで紹介しておきます。

行入力演算子「<>」はファイルハンドルに関連付けられた外部ファイルを行単位で読み込みます。

そして読み込んだ行というのは本来、特殊変数「$_」に代入するようになっています。

なので、前述の例はこのように書き換えることができます。

#!/usr/bin/perl

use strict;
print "Content-type: text/html\n\n";

my $file = 'log.txt';
open(IN,"$file");
while (<IN>) {
print $_, "<br>\n";
}
close (IN);
exit;

「log.txt」の行数分だけ whileループ が実行されます。

すべての行を読み込んだらループ終了です。

なので実行結果は…。

Taro
Jiro
Saburo

となり同じ結果が得られます。

ファイルの上書き

次は、Perl/CGIプログラム本体から、上書きモードで外部ファイルをオープンし、実際に文字を書き込むプログラムを作成してみます。

まずは同じように、データ記録用の外部ファイルを準備します。

ウィンドウズ付属のメモ帳などのテキストエディタで、「log.txt」というテキストファイルを作成してください。

中身も同じでOKですので使い回しで問題ないです。

Taro
Jiro
Saburo

「Saburo」の後は、しっかり改行してくださいね。

これで外部ファイルの準備ができました。

次は、Perl/CGIプログラム本体を作成します。

#!/usr/bin/perl

use strict;

my $file = 'log.txt';

open(OUT,">$file");
print OUT "shiro\n";
close(OUT);

open(IN,"$file");
my @file = <IN>;
close(IN);

print "Content-type: text/html\n\n";
foreach (@file) {
print $_, "<br>\n";
}
exit;

このPerl/CGIプログラムと、データが格納されている「log.txt」ファイルは、同じフォルダ内に入れ、ASCIIモードでアップロードします。

このPerl/CGIプログラムを実行すると、どうなると思いますか?

Taro
Jiro
Saburo
shiro

となりそうな転回なのですが、実はそうはなりません(苦笑)。

「shiro」とだけ表示されます。

なぜこうなってしまったのかというと、「log.txt」に「shiro」という文字列を上書きしているだけだからです。

つまり、「log.txt」の中身を一度すべて消してから、新たに書き込んでいるのです。

もう少し具体的に説明すると…。

open関数の上書きモードを使うと、外部ファイルの内容をすべて書き換えます。

「open(OUT,">$file");」上書きモードでオープンするときには、このようにファイルパスの前に「>」を付けます。

次は実際にデータを書き込みます。

外部ファイルにデータを書き込むときには、print関数でファイルハンドルと書き込みたい内容を指定します。

「print OUT "shiro\n";」

printは何かに出力する関数ですから、このような外部ファイルへの書き込みにも使えます。

そして「\n」は改行を表していますから、文字列「shiro」と改行を書き込んだということになります。

つまり、何も書かれていないファイルに「shiro」と書いて、ポンと改行をしたという感じになるわけですね。

外部ファイルへの書き込み処理が終了したら、close関数でファイルを閉じます。

「close(OUT);」

ファイルハンドルが変わっただけで、やっていることは先ほどと同じですからわかりますよね。

これが、外部ファイルへの上書き処理です。

あとは確認のために再び外部ファイルを開いて、その内容を読み込んで表示させているだけです。

open関数も、上書きモードではなく読み込みモードを使います。

このあたりは、前述しました例と同じですから省略しますね。

以上がPerl/CGIプログラム例の解説です。

open関数の上書きモードを使うと、外部ファイルの内容がすべて書き変わってしまうことはわかりましたが…。

では…。

Taro
Jiro
Saburo
shiro

このように、最後に一行追加したい場合はどうすればよいと思いますか?

ここまでの知識をフル活用するのであれば、一度外部ファイルの中身を読み込んでから最後に一行足して、最後にすべて上書きするしかありません。

例えばこんな感じにします。

#!/usr/bin/perl

use strict;

my $file = 'log.txt';

open(IN,"$file");
my @file_in = <IN>;
close(IN);

push (@file_in, "shiro\n");

open(OUT,">$file");
print OUT @file_in;
close(OUT);

open(IN,"$file");
my @file = <IN>;
close(IN);

print "Content-type: text/html\n\n";
foreach (@file) {
print $_, "<br>\n";
}
exit;

このように、最初にすべて外部ファイル内のデータを読み込んでその内容を書き換えてから、再び同じファイルに上書きしています。

これなら最後に一行追加できますよね。

ただしこの方法だと…。

読み込みに1回。

書き込みに1回。

再び読み込みに1回。

合計3回のファイル処理を組まなければいけなくなりますよね。

これでは少し面倒なので、次は、もう少し短くすむような処理を紹介します。

<戻る>

ファイルの追加書き込み

ここでは、Perl/CGIプログラムから外部ファイルに対して、追加書き込み処理をする方法についてみていきます。

前述したやり方でも、結果として外部ファイルを追加書き込みしているようになっていますが、プログラムコードがちょっと長いですよね。

ここでは、始めから追加書き込みモードで外部ファイルをオープンしますから、少しスマートなPerl/CGIプログラムができます。

まずはその準備として、お決まりの外部ファイルを準備します。

ウィンドウズ付属のメモ帳などのテキストエディタで、「log.txt」を作成してください。

中身もいつものように…。

Taro
Jiro
Saburo

「Saburo」の後は、しっかり改行してくださいね。

これで外部ファイルの準備ができました。

次はPerl/CGIプログラムを作成します。

#!/usr/bin/perl

use strict;

my $file = 'log.txt';

open(OUT,">>$file");
print OUT "shiro\n";
close(OUT);

open(IN,"$file");
my @file = <IN>;
close(IN);

print "Content-type: text/html\n\n";
foreach (@file) {
print $_, "<br>\n";
}
exit;

このPerl/CGIプログラムと、データが格納されている「log.txt」ファイルは、同じフォルダ内に入れ、同じようにASCIIモードでアップロードします。

そして、このPerl/CGIプログラムを実行させると、以下のように表示されます。

Taro
Jiro
Saburo
shiro

「log.txt」に「shiro」という文字列を追加書き込みし、それを確認するために再度「log.txt」を読み込んで表示させています。

それでは、追加書き込み処理についてみていきましょう。

open関数で外部ファイルを追加書き込みモードでオープンするには、ファイルパスの直前に「>>」を付けます。

「open(OUT,">>$file");」

追加書き込み処理の場合は、外部ファイルの末尾に追加連結することになります。

あとは今までやってきたことの繰り返しですから、改めて解説する必要ないですね。

以上が追加書き込みのファイル処理です。

最後にもうひとつだけ補足説明しておきますね。

もし、open関数で指定されたファイルパスに、ファイルが存在していない場合はどうなると思いますか?

その場合は、サーバー設定にもよりますがほとんどの場合「log.txt」が新たに作成されます。

前述した上書きモードで、ファイルをオープンしたときも同じです。

なので、ファイルパスには注意しましょう。

あなたの知らないところで知らないファイルが勝手に作成されてしまいますよ。

そして、最初に説明しました読み込みモードでファイルをオープンしたときは、未定義値が返ります。

なので、open関数を使うときには、エラーになった時の処理も一緒にコーディングしておくのが常套手段です。

まだまだ細かい部分はありますが、基本的なファイル処理のみであれば、このあたりまでを押さえておけば大丈夫でしょう。

編集後記

今回の編集後記は、本編でファイル処理を学習したので、少し変わったopen関数の使用例を紹介して終わりたいと思います。

以下のPerl/CGIプログラムを見てください。

#!/usr/bin/perl

use strict;

open (IN,"ls -l |");
my @list = <IN>;
close (IN);

print "Content-type: text/html\n\n";
foreach (@list) {
print $_, "<br>\n";
}
exit;

今回はわざわざ外部ファイルを用意する必要はありません。

ただし、設置するサーバーの環境によっては動作しない可能性があるので、ここでは眺める程度にしておいてください。

で、このPerl/CGIプログラムは何をしているのかというと、「ls -l」という命令を実行して、その結果を配列@listに格納し表示させています。

「ls -l」とは、カレントディレクトリ内のファイル名やフォルダ名を取得する命令です。

つまり、このPerl/CGIプログラムが実行されたフォルダ内を表示するということです。

このように、「open (ファイルハンドル,"コマンド |")」とすると、実行したコマンドとファイルハンドルを関連付けその結果を得ることができます。

そう頻繁に使うものではないですが、こんな使い方もできるということを頭の隅にでも置いといてください。

今回の学習は以上です。

ありがとうございました。

<戻る>