Unix C プログラミング入門資料(高崎金久)   8.ファイル入出力の処理 目次 1.C言語におけるファイルの処理 2.テキストファイル処理のプログラム例 3.テキストファイル処理に用いられる基本的な関数 4.バイナリファイル処理 -------------------------------------------------------------------------- 1.C言語におけるファイルの処理 これまでは端末(標準入出力)との入出力だけを扱って来ました.このようなプロ グラムでもリダイレクションにより入出力先をファイルに切り替えることができま す.しかしこれでは2つ以上の入力先(あるいは出力先)をもつプログラムはでき ませんし,ファイルのみを処理対象とする問題に対しては直接ファイルにアクセス できる方が便利です. この章ではファイルに対する入出力の方法を説明します.ファイルとは本来は外部 記憶装置に蓄えられたものを指しますが,Unixでは端末や周辺装置(ディスク 装置,CDーROM装置,通信装置,など)もファイルの一種として扱います.C 言語ではこのようにデータの出入口一般を広い意味でのファイルとしてとらえ,フ ァイルストリームと呼びます. ファイルストリームは FILE *(FILE 型ポインタ)という特別なデータ型として扱 われます.ポインタについてはあとの章で説明します.今はおまじないだと思って 下さい.) C言語に限らず,ファイルの処理はどのようなプログラミング言語でも次のような 3つの基本的ステップからなります.   1.ファイルを開く.(これは新規作成も含む.)   2.ファイルに対する読み出し・書き込みを行う.   3.ファイルを閉じる. 実際には,ファイルを閉じるステップをプログラム中で省略しても,コンパイラは 自動的に閉じる処置をしてくれます.しかしファイルを閉じる前にプログラムの実 行が何らかの理由で中断された場合,書き込み中のファイルは失われてしまいます ので,注意が必要です. 例外は標準入出力と標準エラー出力です.これらは常に開かれているファイルスト リームです.これらはプログラムの中では次のキーワードでアクセスできます. stdin --- 標準入力 stdout --- 標準出力 stderr --- 標準エラー出力 標準エラー出力は標準出力同様に端末画面への表示を行うものですが,リダイレク トされない,という違いがあります.このため,エラーメッセージなどリダイレク ションを避けたい場合に用いられます. C言語では一般のファイル(バイナリファイル)を処理する関数も用意されていま す.これについては最後に簡単に述べるにとどめます. また,以下ではもっぱらシーケンシャルファイルの処理について説明します.ラン ダムアクセスについては省略します. 2.テキストファイル処理のプログラム例 前回の文字列照合のプログラムを書き換えて,照合対象のテキストをファイルから 読み込むようにします. 1:/* ファイルから1行ずつ読み込み, コマンド引数として渡した文字列 2: がそこに何個含まれているかを表示する.ファイル名を引数に指定 3: しないときには標準入力から読み込む.^D で入力を打ち切ると, 4: プログラムを終了する.*/ 5:#include 6:#include 7:#define patternMax 100 8:#define textMax 1000 9:int match(char [], char []); /* 関数プロトタイプ宣言 */ 10: 11:int main(int argc, char *argv[]) 12:{ 13: FILE *infile; 14: char text[textMax]; 15: int line, num; 16: /* コマンド引数に照合文字列の指定がないか,あるいは空の時, 17: 標準エラー出力に使い方を表示して終了する */ 18: if ((1 == argc) || (0 == strlen(argv[1]))){ 19: fprintf(stderr,"Usage: %s pattern filename\n\n", argv[0]); 20: exit(1); 21: } 22: /* コマンドで指定されたファイルを開く(開けられない場合は 23: エラーメッセージを表示して終了) 24: ファイル名の指定がなければ標準入力から入力する */ 25: if (argc > 2){ 26: infile = fopen(argv[2],"r"); 27: if (NULL == infile){ 28: fprintf(stderr, "File %s cannot be open\n\n", argv[2]); 29: exit(2); 30: } 31: }else{ 32: infile = stdin; 33: } 34: /* 行数カウンターを初期化 */ 35: line = 1; 36: /* 入力行がある限り繰り返し照合を行なう */ 37: while (NULL != fgets(text, textMax, infile)){ 38: /* 入力行が改行のみのときは line++ のみ行なう */ 39: if (strcmp(text,"\n") == 0) { line++; continue; } 40: /* 照合関数を呼び出して結果を表示する */ 41: num = match(argv[1],text); 42: if (num > 0) 43: printf("line %d: pattern matched at %d places.\n", line, num); 44: line++; 45: } 46: return 0; 47:} 48: 49:int match(char pattern[], char text[]) 50:{ 51: 省略(前回と同じ) 52:} プログラムの解説 match() は前回と同じです.main() の中が違っています.処理の内容はコメント 部分を読めばわかるはずです.要点を解説します. 1)fprintf による出力(18行,28行) fprintf はファイルストリームを出力先にすること以外は printf とまったく同じ 機能をもちます.ファイルストリームは第1引数で指定します.今の場合は標準エラ ー出力(stderr)を出力先に指定しています. 2)fopen によりファイルを開く(26行) ファイルを開くには fopen という関数を使います.fopen は2つの引数をもちます. 最初の引数はファイル名(文字列で指定),次の引数はファイルを開くモード(文 字列で指定)です. * fopen(name,mode) name --- ファイル名(文字列 char[] 型) mode --- モード文字列 ファイル名は文字列として渡します.上のプログラムではコマンド引数 argv[1]を ファイル名としてファイルを開いています. モード文字列はファイルを開くモード(基本的には読み込み,書き込み,追加の3 種類がある)をしていします.上のプログラムでは読み込みモード "r" でファイ ルを開いています. 返戻値はファイルポインタ(FILE *)です.以後のファイルアクセスにはこの返戻 値が必要なので,あらかじめ FILE * 変数を用意しておいて(13行目参照),そ こに格納します.上のプログラムでは,ファイル名がコマンド引数として指定され なかった場合には代わりに標準入力 stdin をセットしています.(stdin, stdout, stderr はこのように FILE * 型データとして機能します.) 指定したファイル名のファイルを開くことができかった(たとえばファイルが存在 しなかったり読めない状態になっていた)場合,fopenはNULLという特別な値(マ クロ定義されている)を返します.この時の処理が27行ー30行です.ファイル 処理の場合は安然を期してこのようなエラー処理を行うのが普通です. 3)ファイルから1行ずつ読み込み,照合をおこなう(34行ー45行) 行数を表示するための整数変数 line を初期化して,1行ずつファイルから読み込 みながら照合を行います.この部分は前回と実質的には同じですが,ファイルから 大量に読み込む場合を想定して,照合文字列が見つかった行についてのみ表示を行 うように変更しています. 3.テキストファイル処理に用いられる基本的な関数 上のプログラムで用いたものも含めて,テキストファイルの処理に用いられる基本 的な関数についてまとめておきます. 1)ファイルを開閉する関数 fopen, fclose ファイルを開閉する関数 fopen, fclose は次のような仕様になっています. ★fopen(name,mode) name --- ファイル名(文字列 char[] 型) mode --- "r"(読み込み)"w"(書き込み)"a"(追加)(この他にも 読み書きを同時に行うモードがあるが,省略する) *ファイルを開くことにに成功すると,開いたファイルに対応する FILE * 型 の値を返す.この値は FILE * 型変数に格納し,以後はそれを使って入出力 処理を行う.ファイルを開くことに失敗すると,NULL ポインタを返す. *書き込みモード "w" で開くともとのファイルの内容は完全に失われる. *追加モード "a" で開くと,もとのファイルの内容はそのまま残してその後 に追加する. ★ fclose(file) file --- ファイルストリーム(FILE * 型) file のところに閉じたいファイルのファイルストリームポインタを置く. エラーが生じると EOF という値を返す. 2)文字列入出力関数 fgets, fputs ★ fputs(s, file) s -- 文字列(char [] 型) file -- ファイルポインタ(FILE * 型) ファイルストリームに文字列を出力する.puts と違って自動的に改行コード をつけることはしない.出力先を stdout, stderr にして使えば改行させずに 画面出力できる. ★ fgets(s, n, file) s -- 文字配列変数(char [] 型) n -- 整数(int) file -- ファイルポインタ(FILE * 型) ファイルストリームから文字配列変数に最大 n-1 文字(バイト)を読み込む. 文字列は n-1 文字になるか,改行文字がでてくるか,あるいはファイルの終 わりまで読み込まれる. 3)書式付き入出力関数 fprintf, fscanf printf, scanf(標準入出力に対する書式付き入出力函数)に対応するものとして 一般のファイルストリームを入出力先とする次の函数が用意されています.第1 引数にファイルストリームを指定する以外は printf, scanf とまったく同じです. ★fprintf(file, format, 変数の並び) file -- ファイルポインタ(FILE * 型) format -- 書式文字列(char [] 型) ★fscanf(file, format, 変数アドレスの並び) file -- ファイルポインタ(FILE * 型) format -- 書式文字列(char [] 型) この他に1文字入出力用の関数 putc, getc(およびその標準入出力専用版である putchar, getchar)もありますが,以上のような文字列単位の処理関数と併用する と問題を起こすことがあるので,ここでは説明を省きます. 4.バイナリファイルの処理 1)バイナリファイルの処理とは? ここまではもっぱらテキストファイルの処理を考えてきました.つまり,基本的に は文字データからなるファイルを開閉して,文字や文字列単位で入出力処理を行っ てきました. 計算機で扱うデータはテキストだけではありません.たとえば,整数や浮動小数点 数は文字列とは異なるデータ形式です.(その間を変換するものとして atoi,atol, atof などの関数があったことを思い出すこと.)これをそのままの形でファイル に保存すればバイナリファイルとなります.また,画像・音声などのデータも多く の場合一定形式のバイナリファイルとして保存されます. このようなバイナリファイルを扱うための関数もC言語では標準的にいくつか用意 されています.以下ではそれについて簡単に説明します.実際には(一般にはシス テムの種類に依存して)それよりもさらに機械寄りの入出力関数も用意されていた りするのですが,ここではそれには立ち入りません. 2)ファイルの開閉 バイナリファイルを開くにも fopen, fclose を用います.ただし,システムによ ってはモードの指定を次のようにする必要のある場合もあります.Unix の場合は "r", "w", "a" で通用します. "rb" ---- バイナリファイルを読み込み専用で開く "wb" ---- バイナリファイルを書き込み専用で開く "ab" ---- バイナリファイルを追加用に開く 返戻値を FILE * 型変数に格納して使うことはいままでと同じです. 3)バイナリファイルに対する入出力関数 バイナリファイルの読み書きには以下のような関数が用いられます.基本的にはバ イト単位で読み書きを行います. ★ fread(p, s, n, f) p --- 読み込みデータを格納する変数のアドレス s,n --- 整数 (int) f --- ファイルストリームポインタ (FILE *) 返戻値 --- 整数 (int) ファイルストリーム f から長さ s バイトのデータを最大 n 個読み込み, 第1引数で指定したアドレス(配列変数ならばその配列名)に書き込む. ★ fwrite(p, s, n, f) p --- データを格納する変数のアドレス(ポインタ) s,n --- 整数 (int) f --- ファイルストリームポインタ (FILE *) 返戻値 --- 整数 (int) 第1引数で指定した変数のアドレス(配列変数ならば配列名)からファイ ルストリーム f へ,長さ s バイトのデータを n 個書き出す.返戻値は 書き出したデータの個数. 第1引数にはデータを格納する変数 x のアドレス &x を渡します.ただし,配列 の場合は配列名自体を渡します.また次の章で説明するようなポインタ変数を渡 すこともできます. 第2引数 s には読み書きするデータがメモリー上に占める大きさ(バイト数)を 指定します.これは言語処理系やシステムによって異なりますが,sizeof 演算子 を使えば fread(p, sizeof(TYPE), n, f), fwrite(p, sizeof(TYPE), n, f) とい う形で自動的に設定できます. * sizeof(データ型)または sizeof(変数) --- 指定したデータ型あるいは 変数がメモリー上に占める大きさをバイト数で与える.