データは「流す」ものではなく、「残す」もの。
こんにちは!「LINUX工房」管理人の「リナックス先生」です。
前回は、HTMLフォームから送信されたデータをPerlで受け取り、画面に表示するところまで進みました。
しかし、前回のプログラムには致命的な弱点がありました。
それは、「せっかく入力したデータが、画面を閉じると消えてしまう」ことです。
これでは、他のユーザーとメッセージを交換する「掲示板」や、記録を残す「アンケート」は作れません。
先生、そうなんです!
自分の画面に「こんにちは」って出ても、他の人には見えないし、リロードしたら消えちゃうし…。
みんなが書き込んだ内容を、ずっと残しておくにはどうすればいいんですか?
やっぱりデータベースってやつが必要なんですか?
本格的なシステムならデータベース(SQL)を使うけど、基本は「ファイルへの読み書き」よ。
サーバー上にあるテキストファイル(data.txtなど)に書き込んでおけば、電源を切ってもデータは消えないわ。
今回は、プログラミングの最重要スキル「ファイル操作(I/O)」をマスターして、自分だけの掲示板を作りましょう!
本記事では、Perlを使ってテキストファイルを読み書きする方法、複数の人が同時に書き込んでもデータが壊れないようにする「排他制御」、そして保存されたデータを一覧表示するための「ループ処理」を解説し、実際に動く掲示板システムを構築します。
🐪 Perlサーバサイドプログラミング講座(バックナンバー)
現在地:【第3回】掲示板を作ろう(前編)!制御構文とファイル操作(読み書き)の基礎
- 【第1回】Linuxの「最強の武器」を手にせよ!環境構築とCGIでのHello World
- 【第2回】入力フォームを攻略せよ!GET/POSTデータの受け取りと変数入門
- 【第3回】掲示板を作ろう(前編)!制御構文とファイル操作(読み書き)の基礎
- 【第4回】車輪の再発明を防ぐ!CPANモジュールの導入とCGI.pmの活用
- 【第5回】データはデータベースへ!MariaDB(MySQL)連携とDBI/DBDモジュール
- 【第6回】モダンPerlの世界へ!Webフレームワーク「Mojolicious」入門
- 【第7回】セキュリティと見た目の分離!テンプレートエンジンの利用とXSS対策
- 【第8回】実践アプリ開発!簡易ブログシステムの構築とPSGIデプロイ
第1章:ファイル操作の基本「open」と「close」
プログラムからファイルを扱うには、まず「ファイルを開く(open)」、そして用が済んだら「ファイルを閉じる(close)」という手順が必要です。
ファイルハンドルとは?
Perlでは、開いたファイルを操作するために「ファイルハンドル」という専用の変数を使います。
昔のPerlでは IN や OUT のような大文字の裸のワードを使っていましたが、現在は $fh(File Handleの略)のように、通常のスカラ変数を使うのがモダンな作法です(レキシカルファイルハンドル)。
open関数の基本構文
open(my $fh, "モード", "ファイル名") or die "エラーメッセージ: $!";
my $fh: ファイルハンドルを受け取る変数。- モード: 読み込むのか、書き込むのかを指定する記号。
or die ...: ファイルが開けなかった場合(ファイルがない、権限がないなど)に、プログラムを強制終了してエラーを表示する定型句。必ず書きましょう。
3つの重要なモード
| モード記号 | 動作 | 内容 |
|---|---|---|
< |
読み込み (Read) | ファイルの内容を読み取るだけ。内容は変更されない。ファイルがないとエラー。 |
> |
書き込み (Write) | 新しい内容でファイルを上書きする。元の内容は消える。ファイルがなければ作る。 |
>> |
追記 (Append) | ファイルの末尾に新しい内容を追加する。元の内容は消えない。掲示板はこれを使う。 |
第2章:【書き込み編】掲示板への投稿機能を作る
まずは、フォームから送られてきたデータを、サーバー上の bbs.txt に保存する機能を作ります。
掲示板では、新しい投稿を「追記(Append)」していく必要があります。
1. データ形式の設計
1つのファイルに複数の情報を保存するには、ルールを決める必要があります。
今回はシンプルに、1行を1つの投稿とし、各項目を「タブ文字(\t)」で区切ることにします(TSV形式)。
名前 <タブ> メッセージ <タブ> 日時 <改行>
2. 排他制御(flock)の重要性
Webアプリケーションでは、複数のユーザーが「同時に」書き込みボタンを押す可能性があります。
もし同時にファイルを開いて書き込もうとすると、データが混ざったり、ファイルが壊れて中身が全部消えたりします。
これを防ぐために、「今、俺が書き込んでるから、他の人は待ってて!」とロックを掛ける仕組みが flock です。
3. 書き込みプログラムの作成
/var/www/cgi-bin/bbs_write.cgi を作成します。
#!/usr/bin/perl
use strict;
use warnings;
use CGI;
use Encode;
use Fcntl qw(:flock); # ロック用の定数を使う宣言
# 文字化け対策
binmode STDOUT, ":utf8";
my $q = CGI->new;
my $file = "data.txt";
# パラメータ取得とデコード
my $name = decode('UTF-8', $q->param('name'));
my $message = decode('UTF-8', $q->param('message'));
# 日時の取得
my $time = localtime;
# 入力チェック
if ($name ne "" && $message ne "") {
# ファイルを「追記モード」で開く
open(my $fh, ">>", $file) or die "Cannot open file: $!";
# ファイルをロックする(排他ロック: LOCK_EX)
flock($fh, LOCK_EX);
# ファイルの末尾に移動(念のため)
seek($fh, 0, 2);
# データの結合(タブ区切り)と改行
# 書き込み時はエンコードする
my $line = "$name\t$message\t$time\n";
print $fh encode('UTF-8', $line);
# ファイルを閉じる(同時にロックも解除される)
close($fh);
}
# 完了画面の表示(リダイレクトしても良いが今回は表示)
print $q->header(-charset => 'UTF-8');
print "<html><body>";
print "<h2>書き込みました</h2>";
print "<a href='bbs_read.cgi'>掲示板を見る</a>";
print "</body></html>";
4. 権限設定(重要!)
CGIがファイルに書き込むためには、データファイルに対する「書き込み権限」が必要です。
# 空のデータファイルを作成 sudo touch /var/www/cgi-bin/data.txt # 権限を「誰でも読み書きOK(666)」にする # 本来はApacheユーザー(apache)に所有権を持たせるのが安全ですが、 # 学習用として手っ取り早い666を使います。 sudo chmod 666 /var/www/cgi-bin/data.txt # SELinux対策(読み書き可能なコンテキストを付与) sudo chcon -t httpd_sys_rw_content_t /var/www/cgi-bin/data.txt
⚠️ SELinuxの罠
第1回でCGIファイルには httpd_sys_script_exec_t を付けましたが、CGIが書き込むファイルには httpd_sys_rw_content_t (Read/Write Content) という別のラベルが必要です。
これを忘れると、Permission denied で書き込めません。
第3章:制御構文「ループ処理」をマスターする
書き込みができたら、次はそれを読み込んで表示します。
ファイルには何行データがあるか分かりません(1行かもしれないし、1000行かもしれない)。
そんな時は「データがある限り繰り返す」というループ処理を使います。
1. whileループ(ファイル読み込みの定番)
条件が真(True)である間、ひたすら繰り返します。
ファイル操作では「次の行が読み込めたら(真)、処理する。読み込めなかったら(偽)、終わる」という使い方が定石です。
open(my $fh, "<", "data.txt");
# 1行ずつ読み込んで $line に入れる
while (my $line = <$fh>) {
print $line;
}
close($fh);
2. foreachループ(配列の処理)
すでに配列に入っているデータを、最初から最後まで順番に処理します。
my @users = ("田中", "鈴木", "佐藤");
foreach my $u (@users) {
print "こんにちは、$u さん\n";
}
第4章:【読み込み編】掲示板の表示機能を作る
data.txt を読み込んで、HTMLの表(Table)として表示するプログラム bbs_read.cgi を作ります。
1. 文字列操作関数(chomp, split)
ファイルから読み込んだデータは、そのままでは使えません。
chomp: 行末の改行文字を削除する関数。split: 文字列を特定の区切り文字で分割して、配列にする関数。
例:"田中\tこんにちは\n" → chomp → "田中\tこんにちは" → split(\t) → ("田中", "こんにちは")
2. 読み込みプログラムの作成
sudo nano /var/www/cgi-bin/bbs_read.cgi
コード記述:
#!/usr/bin/perl
use strict;
use warnings;
use CGI;
use Encode;
binmode STDOUT, ":utf8";
my $q = CGI->new;
my $file = "data.txt";
print $q->header(-charset => 'UTF-8');
print "<html><body>";
print "<h1>Perl掲示板</h1>";
# 入力フォームも一緒に表示する
print <<HTML;
<form action="bbs_write.cgi" method="POST">
名前: <input type="text" name="name"><br>
内容: <textarea name="message"></textarea><br>
<input type="submit" value="書き込む">
</form>
<hr>
<table border="1">
<tr><th>名前</th><th>メッセージ</th><th>日時</th></tr>
HTML
# ファイルを開く(読み込みモード)
if (open(my $fh, "<", $file)) {
# 最新の投稿を上にしたい場合、一度配列に読み込んで逆順にするテクニック
my @lines = <$fh>; # 全部読み込む
close($fh);
@lines = reverse @lines; # 逆順にする
foreach my $line (@lines) {
# 文字コード変換(ファイルから読んだらデコード!)
$line = decode('UTF-8', $line);
chomp($line); # 改行削除
# タブで分割して、それぞれの変数に入れる
my ($name, $msg, $time) = split(/\t/, $line);
# HTMLエスケープ(XSS対策:第7回で詳しくやりますが、簡易的に)
# 本来はモジュールを使うべきですが、今回は基本動作優先で省略
print "<tr>";
print "<td>$name</td>";
print "<td>$msg</td>";
print "<td>$time</td>";
print "</tr>\n";
}
} else {
print "<p>まだ投稿はありません。</p>";
}
print "</table>";
print "</body></html>";
3. 権限設定
sudo chmod 755 /var/www/cgi-bin/bbs_read.cgi # 必要なら chcon も
第5章:動作確認とデータ管理の注意点
ブラウザで http://サーバーIP/cgi-bin/bbs_read.cgi にアクセスしてみましょう。
入力フォームと、投稿一覧が表示されましたか?
適当に入力して「書き込む」を押し、一覧に追加されれば成功です!
データファイルが見えてしまう問題
現在の設定では、ブラウザで http://サーバーIP/cgi-bin/data.txt に直接アクセスすると、生データが丸見えになってしまいます。
これを防ぐ方法はいくつかあります。
- Web公開領域の外に置く:
/var/www/data/など、DocumentRootやScriptAlias以外の場所にファイルを置き、CGIからはフルパスで指定する。これが一番安全です。 - ドットファイルにする:
ファイル名を.data.txtのようにドットから始めると、Apacheの設定によってはアクセス拒否されます(.htaccessと同様)。 - 拡張子を変える:
data.datなどにして、Apacheの設定で.datへのアクセスを拒否する。
実務では「1. 公開領域の外に置く」が鉄則です。
第6章:トラブルシューティング
うまくいかない場合のチェックリストです。
Q. 書き込みボタンを押しても何も増えない
- 権限エラー:
data.txtのパーミッションは666になっていますか? - SELinux:
data.txtのコンテキストはhttpd_sys_rw_content_tになっていますか? - パス間違い: プログラム内のファイル名は合っていますか?
Q. 文字化けする
- Perlスクリプトの保存形式: UTF-8になっていますか?
- binmode:
binmode STDOUT, ":utf8";を書いていますか? - 入出力のエンコード: 書き込み時は
encode、読み込み時はdecodeをしていますか?
まとめ:ファイル操作はすべての基本
お疲れ様でした!
これで、データを受け取り、保存し、表示するというWebアプリケーションの基本サイクルが完成しました。
今回の重要ポイント:
open関数でファイルを開き、モード(<,>,>>)を指定する。flockで排他制御をしないと、データが壊れるリスクがある。while (<$fh>)でファイルを1行ずつ読み込む。data.txtへの書き込み権限とSELinux設定を忘れない。
しかし、今回はすべての機能を「ゼロから手書き」しました。
「HTMLのエスケープ処理」や「メール送信」、「画像のアップロード」など、よくある機能を毎回手書きするのは大変ですし、バグの元です。
次回、第4回は「車輪の再発明を防ぐ!CPANモジュールの導入とCGI.pmの活用」です。
世界中のPerlハッカーたちが作った便利なライブラリ群「CPAN(シーパン)」を使って、もっと楽に、もっと高度なプログラミングをする方法を学びます。
お楽しみに!
▼ エンジニアとしてのキャリアを加速させる ▼
掲示板を公開するなら
「VPS」で自分専用環境
サーバー知識を年収に
「ITエンジニア転職」

コメント