みなさんのホームディレクトリ以下には、mule などのテキストエディタで作成した文書や受け取ったメール、 camera でキャプチャしたり netscape などを使ってダウンロードした画像など、 様々なデータがファイルとして保存されていると思います。 ここでは自分でファイルの操作を行うプログラムを作ってみましょう。
kaku.cc |
---|
#include <fstream.h> int main(void) { ofstream ofile("data"); // ファイル data を開く if (ofile != 0) { // ofile が非0 ofile << "はろ〜" << endl; // data に出力 } else { cerr << "ファイルが開けませんでした" << endl; } return 0; } |
このプログラムでは ofstream 型の変数 ofile を定義して、それを "data" という文字列で初期化しています。
ofstream ofile("data");
この初期化によって data という名前のファイルが書き込みモードで開かれ、 それが ofile に結び付けられます。 この処理は コンストラクタ関数 によって実行されます。
この時、開かれたファイルに書かれていた内容は消されます。 ファイルが無ければ新規に作成されます。
何らかの理由でファイルが開けなかったり、作成に失敗したりすると、 ofile の内容が 0 になります。
cerr は cout 同様出力(画面表示)を行うストリームを指すオブジェクトですが、 これはエラーメッセージなどを表示するために使う 標準エラー出力に結び付けられています。 標準エラー出力はコマンドに ">" を付けても リダイレクトされません。
この ofile には、出力ストリーム cout と同様に挿入演算子 << によってデータを出力することができます。 cout は標準出力(画面)にデータを出力するオブジェクトですが、 ofile は変数を生成したときの初期値 "data" という名前のファイルにデータを出力するオブジェクトです。
では、上のプログラムをコンパイルして実行してみてください。 実行ファイル名は kaku にしましょう。
% CC kaku.cc -o kaku[Enter] % kaku[Enter] |
このプログラムを実行しても何も起こらないように見えますが、 カレントディレクトリ上には data というファイルが出来ているはずです。 ls コマンドで確かめてみましょう。
% ls[Enter] ... data ... |
では、このファイルの中に何が入っているのか cat コマンドで見てみましょう。 オブジェクト ofile に出力した文字列が入っていると思います。
% cat data[Enter] はろ〜 |
それでは、今度はこのデータを読むプログラムを作成してみましょう。
yomu.cc |
---|
#include <fstream.h> int main(void) { ifstream ifile("data"); // data を開く if (ifile != 0) { // ifile が非0 char line[80]; ifile.getline(line, sizeof line); // data から入力して line に格納 cout << line << endl; // 画面に出力 } return 0; } |
先と同様に、このプログラムでは ifstream 型の変数 ifile を定義して "data" という文字列で初期化し、data という名前のファイルを読み出しモード で開いています。data というファイルは変数 ifile に結び付けられます。
ファイルが無ければエラーになり、 ifile の内容が 0 になります。
この変数 ifile からは、入力ストリーム cin と同様に抽出演算子 >> によってデータを入力することができます。 cin は標準入力(キーボード)からデータを受け取るオブジェクトですが、 ifile は変数を生成したときの初期値 "data" という名前のファイルからデータを読み込むオブジェクトです。
ところで、このプログラムは cout を使っているのに #include <iostream.h> が無くても正常にコンパイルできます。 実は ifstream, ofstream はそれぞれ istream(cin のデータ型), ostream(cout のデータ型)から派生した (これらを元に作成した) データ型(クラス)なのです。 このため、fstream.h の中には #include <iostream.h> が既に入っているのです。
このプログラムをコンパイルして実行してみてください。 実行ファイル名は、今度は yomu にしましょう。
% CC yomu.cc -o yomu[Enter] % yomu[Enter]はろ〜 |
実行するたびに、 そのプログラムが実行された回数を出力するプログラム mycounter を作成してください。
% mycounter[Enter] 1 % mycounter[Enter] 2 % mycounter[Enter] 3 % |
プログラムが確保したメモリは、 たとえ静的変数であってもプログラムが終了したときにはすべて解放されます。 このため、プログラム内で変数にデータを格納しても、 そのプログラムの実行が終了してしまえば、 もう一度そのプログラムを動かしても、 以前その変数に格納した内容を取り出すことはできません。 そこで、プログラムを次に実行したときに以前のデータが取り出せるよう、 プログラムが終了する前にデータをファイルに保存しておきます。 以下の手順を参考にしてください。
開かれたファイルは、 そのファイルを指している変数が無効になった時点で自動的に閉じられますが、 この課題では一度読み出しモードで開いた後、 書き込みモードで開く前に一旦ファイルを閉じる必要があります。 明示的にファイルを閉じるには close() を使います。
ifstream ifile("mycount"); // ファイル "mycount" を開く ... ifile.close(); // ファイル "mycount" を閉じる ... ofstream ofile("mycount"); // ファイル "mycount" を開く
ファイルに書かれている数字を数値として count に格納するには、 cin から読み込むのと同様抽出演算子 >> を使うことに気をつけてください。 作成したソースプログラムをメールに添付して、 tokoi まで送ってください。Subject:(件名)は kadai25 としてください。
Web サーバの SSI (Server Side Include) という機能を使えば、 mycounter を使って簡単なアクセスカウンタが実現できます (本当は複数の人から同時にアクセスされたときに 勘定し損なわないようファイルをロックすべきなんですが、 その辺は将来の課題ということで)。 mycounter を public_html の下にコピーしてください。
% cp mycounter ~/public_html[Enter] |
次に、public_html に下のような HTML ファイルを作成してください。 ファイル名は mycounter.shtml とでもしてください。 以前に作成した Web ページに組み込んでも構いません。 拡張子が .shtml になっていることに気をつけてください。
mycounter.shtml |
---|
<head> <title>counter test</title> </head> <body> あなたは <!--#exec cmd="./mycounter"--> 番目の訪問者です。 </body> |
こうすると、このページにアクセスするたびに mycounter が実行されて、 <!--...--> の部分がその出力に置き換わります。 このように、Web ページに他のファイルやプログラムの出力を埋め込む機能を、 SSI (Server Side Include) と呼びます。
ところが、このままでは何度アクセス (reload) してもカウンタは増えないと思います。 chmod コマンドを使って mycounter に setuid ビットを立ててください (setuid ビットについては注意事項がありますので、 あとの文章も必ず読んでください)。 reload してカウントが進むようになったら、 Netscape の「ファイル」メニューにある「リンクを送信」で、 そのページを私 (tokoi) に知らせてください。Subject:(件名)は kadai26 としてください。
% cd ~/public_html[Enter] % ls -l mycounter[Enter] -rwxr-xr-x 1 s035000 student 17816 1月 13日 10時50分 mycounter % chmod u+s mycounter[Enter] % ls -l mycounter[Enter] -rwsr-xr-x 1 s035000 student 17816 1月 13日 10時50分 mycounter |
ls コマンドにオプション -l を付けると、 ファイル名の他にファイルの様々な情報を表示します。 各欄は左から、アクセス権、リンク数、所有者 (user)、グループ (group)、 ファイルサイズ(バイト)、最終修正日時、ファイル名を示しています。
アクセス権 (permission) というのは、 そのファイルを「誰」が 「どのように」取り扱うことができるかという情報です。
ls -l で表示される各行の最初の - は、 そのファイルが(ディレクトリなどでない) 通常のファイルであることを示します。
次の rwx の3文字は、 所有者 (user) がそのファイルを読み (r-ead) 書き (w-rite) 実行 (e-x-eculte) できることを示します。
その右も3文字ずつに区切られており、 r-x はファイルのグループに属する所有者以外の利用者 (group) に対して、 そのファイルの読み出しと実行が許可されていることを示します。 w の位置が - になっているので、書き込みは許されていません。
残りの3文字は所有者およびグループ以外の他の全利用者 (others) に対するもので、 この場合はグループと同じになっています。
実行ファイルの所有者のアクセス権に setuid ビットを立てると、 その x が s に変わります。
リンクはファイル名とファイルの本体を結び付きのことを言います。 ファイルは、実は一つの本体に対して複数のリンク(ファイル名)を持つことができ、 リンク数はその数になります。ほとんどのファイルのリンク数は1です。 なお、シンボリックリンクはリンク数には反映されません。
みなさんのホームページには、和歌山大学以外からのアクセスもあります。 そういう人達はこのコンピュータには登録されていませんから、 本当はこのコンピュータを利用したり、 コンピュータのデータを見たりすることはできないきません。 そこで、Web ページへのアクセスについては、 nobody という利用者として扱うことにしています。 すなわち、Web ページの HTML ファイルには、 nobody という利用者が読み出せるようにアクセス権を設定する必要があります。
たいていのファイルには、others に対しても読み出し権と(実行ファイルの場合は)実行権が設定されていますから、 読み出すだけなら特別なことをしなくても何も問題はありませんでした。 しかし、mycounter は mycount というファイルにデータを書き込みますから、 これが許されていないためにカウントが増えないのです。
実行プログラムに setuid ビットを立てると、 そのプログラムは実行した人ではなく所有者によって実行されたことになります。
すなわち mycounter に setuid ビットを立てなければ、 nobody によって実行された mycounter は nobody として mycount データを書き込もうとして失敗しますが、 setuid ビットを立てておけば mycounter は mycounter の所有者(= mycount の所有者)によって実行されたのと同じになりますから、 mycount への書き込みは成功します。
ただ、こうするとあなた以外のいかなるユーザも、 このプログラムを通じて、 あなたの所有するすべてのファイルにアクセスできる可能性が出てきます (下手をするとあなたの名をかたって他で悪さを働くこともできます)。 したがって、setuid ビットを立てるプログラムは、 他のユーザがこのプログラムを使って他のことができないよう (myconter なら プログラム中でファイル名を /usr/people/ユーザ名/public_html/mycount というようにフルパス名で指定し、この以外のファイルが操作できないよう ) 注意深くプログラムを作成する必要があります。
もし、そういう自信がないなら、 プログラムに setuid ビットを立てる代りに、 データファイルの方のアクセス権を誰もが書き込めるように設定してください。 また Perl スクリプトや sh スクリプトに setuid ビットを立てても効果はないので、 その場合も同様です。
% ls -l mycount[Enter] -rw-r--r-- 1 s035000 student 3 1月 13日 10時50分 mycount % chmod go+w mycount[Enter] % ls -l mycount[Enter] -rw-rw-rw- 1 s035000 student 3 1月 13日 10時50分 mycount |
ただしこうすると、 そのファイルを他の利用者が好きなように修正できるようになります。
mycount.cc を修正して、 カウント数の各桁の数字に下のような画像 (自分で作った画像でも構いません) を使ったグラフィカルなアクセスカウンタを作成してください。
/icons/0.gif | /icons/1.gif | /icons/2.gif | /icons/3.gif | /icons/4.gif |
/icons/5.gif | /icons/6.gif | /icons/7.gif | /icons/8.gif | /icons/9.gif |
www.sys.wakayama-u.ac.jp の Web サーバソフトウェアは、 <img src="/icons/0.gif"> によって という数字の画像を貼り込めるように設定してあります。
これ(または自分で作成した画像)を使って、たとえばカウントが 345 なら、 と表示されるようにしてください。
これを実現するには、mycounter で 345 と数字を出力する代りに、
<img src="/icons/3.gif"><img src="/icons/4.gif"><img src="/icons/5.gif">と出力すればいいことになります。
このために、unsigned int 型の変数 counter に格納されている数値を、 文字列に直します。それには sprintf() という便利な関数や strstream クラスなどが使えますが、 ここでは次のような方法でやってみます。
// この文字列を格納する char 型の配列 digit[] を用意します。 // unsigned int 型の桁数は最大 10 桁なので // digit[] の要素数は 10 以上にします。 char digit[10]; // 一の位は count を 10 で割った余りですから、これを数字に直して // digit[9] に格納します。その後 count を 10 で割り、結果が 0 に // なるまでこれを繰り返します。 int i = 10; do { digit[--i] = count % 10 + '0'; // '0' を足して数字の文字コードを得る count /= 10; } while (count != 0);
これで、count が文字列に変換され、digit + i に格納されます。 これを頭から1文字ずつ取り出して、HTML タグに直して出力します。
// digit[i] を i < 10 の間1文字ずつ出力します。前後に“タグ”を付けます。 while (i < 10) { cout << "<img src=\"/icons/" << digit[i++] << ".gif\">"; }
なお、桁数を固定して上位の桁を0で埋める場合は、
} while (count != 0)を
} while (i > 0)に変更してください。
うまくできたら、Netscape の「ファイル」メニューにある「リンクを送信」で、 そのページを私 (tokoi) に知らせてください。Subject:(件名)は kadai27 としてください。
この方法は CGI (Common Gateway Interface) を使う方法に比べれば「ベタ」な方法ですが、 画像データ自体を加工せずに済むので簡単です。 しかし、排他制御(複数の人が同時にカウンタにアクセスしないようにすること) を行っていないので、 アクセスが集中するとカウンタの値が壊れてしまう恐れがあります。 ということで、このプログラムはあんまり「実用」にはなりません。