トップC/C++ > ファイルの操作

ファイルの操作

1. ファイル情報

ファイルの存在をチェックしたり、ファイルサイズ更新日時などを知りたいことがしばしばある。 そんなとき有効なのが stat関数である。

次のようにして、ファイルの存在の有無をチェックできる。

    #include <sys/stat.h>

    struct stat  st;
    if (stat(fname, &st) == 0) {
        return;     //  既に存在する
    }

stat構造体の中身は次の通りである。

    struct stat {
        dev_t   st_dev;         /* ファイルがあるデバイスの ID */
        ino_t   st_ino;         /* inode 番号 */
        mode_t  st_mode;        /* アクセス保護 */
        nlink_t st_nlink;       /* ハードリンクの数 */
        uid_t   st_uid;         /* 所有者のユーザ ID */
        gid_t   st_gid;         /* 所有者のグループ ID */
        dev_t   st_rdev;        /* デバイス ID (特殊ファイルの場合) */
        off_t   st_size;        /* 全体のサイズ (バイト単位) */
        blksize_t st_blksize;   /* ファイルシステム I/O でのブロックサイズ */
        blkcnt_t  st_blocks;    /* 割り当てられたブロック数 */
        time_t  st_atime;       /* 最終アクセス時刻 */
        time_t  st_mtime;       /* 最終修正時刻 */
        time_t  st_ctime;       /* 最終状態変更時刻 */
    };

ここで、st_ctimeは作成時間ではない。cはchageの頭文字である。 すなわち、stat関数ではファイル作成時刻を知ることはできない。

stat構造体には三種類の時刻があるが、下のプログラムでは、 三つとも同じ時刻であった。

// filetime.c  2013-03-08  Hatada

#include <stdio.h>
#include <time.h>
#include <sys/stat.h>

int main(int argc, char* argv[]) {
    struct stat  st;

    stat(argv[1], &st);
    printf("%s\n%s\n%s\n", 
                asctime(localtime(&st.st_atime)), 
                asctime(localtime(&st.st_mtime)), 
                asctime(localtime(&st.st_ctime)));
}

下に実行時間を示す。 エクスプローラでは、作成時間とアクセス時間は共に 2013年3月8日 16:15:21で、更新時間は2013年3月8日 17:33:57 であった。

c:\mh\mcc>filetime ../www/c01/file/filetime.c
Fri Mar 08 16:15:21 2013

Fri Mar 08 16:15:21 2013

Fri Mar 08 16:15:21 2013

2. ディレクトリ再帰スキャン

指定ディレクトリを再帰的に探索し、 拡張子別にファイル数、行数、サイズを求める。

count-lines.c

3. テキストファイルの一括読み込み

char *loadText(char *file) {
    char *buf;
    FILE *fp;
    struct _stat st;

    if (_stat(file, &st) != 0) return NULL;
    buf = calloc(st.st_size + 1, sizeof(char));
    fp = fopen(file, "rb");
    fread(buf, sizeof(char), st.st_size, fp);
    fclose(fp);
    return buf;
}

4. カンマ区切りの数値列の読み込み

234,121,45, のような整数列を読み込む。末尾にも「 , 」があるものとする。

    long number;
    FILE *fp = fopen(fname, "r");
    if (fp == NULL) {
        printf("ファイル %s が開けません\n", fname);
        return -1;
    }
    while (fscanf(fp, "%ld,", &number) > 0) {
        printf("%d,", number);
    }
    fclose(fp);

数値が実数の場合には次のようにすればよい。 数値は 2104.61 のような表記だけでなく、2.10461e+03 のような指数表記でもよい。

    double  number;
    FILE *fp = fopen(fname, "r");
    if (fp == NULL) {
        printf("ファイル %s が開けません\n", fname);
        return -1;
    }
    while (fscanf(fp, "%lf,", &number) > 0) {
        printf("%f,", number);
    }
    fclose(fp);

5. HTMLファイルのTableにレコードを挿入する(ftell, fseek)

CSVファイルにレコードを追加する場合にはアペンドモードでオープンしてレコードを書き込むだけでよいから簡単である。

これに対して HTMLファイルでの表は次のような形をしている。

これは下に示すように表示される。

表現意味
\d任意の数値([0-9]と同じ)
\D\d以外の文字([^0-9]と同じ)
\w英数文字([A-Za-z0-9_]と同じ)
\W\w以外の文字

この表の末尾に1行追加して

表現意味
\d任意の数値([0-9]と同じ)
\D\d以外の文字([^0-9]と同じ)
\w英数文字([A-Za-z0-9_]と同じ)
\W\w以外の文字
\sスペース(\f\r\n\t\v)
のようにするには、下に示すように、赤字で示した1行を挿入しなければならない。
<html>
<body>
<table border="1">
<tr><th>表現</th><th>意味</th></tr>
<tr><th>\d</th><td>任意の数値([0-9]と同じ)</td></tr>
<tr><th>\D</th><td>\d以外の文字([^0-9]と同じ)</td></tr>
<tr><th>\w</th><td>英数文字([A-Za-z0-9_]と同じ)</td></tr>
<tr><th>\W</th><td>\w以外の文字</td></tr>
<tr><th>\s</th><td>スペース(\f\r\n\t\v)</td></tr>
</table>
</body>
</html>

これを実現するには、このファイルを読み書き可能なモードでオープンして、 1行ずつ読み込み、</table を探し、そこに新しいレコードを書き込み、その後に </table>、 </body>、 </html>を書き込むことになる。

プログラム例を下に示す。 一行ずつ読み込み、</table>が現れるまで繰り返す。 そこから書き込むと </table> の後ろに書き込むことになるため、それではいけない。 ftell関数を使って、</table>の先頭を覚えている。

	long filepos;
	char *result = "<tr><th>\s</th><td>スペース(\f\r\n\t\v)</td></tr>";

	FILE *fp = fopen("test.html", "rb+");
	while (fgets(buffer, sizeof(buffer), fp) != NULL) {
		if (strncmp(buffer,"</table>",7) == 0) break;
		filepos = ftell(fp);
	}
	fseek(fp, filepos, SEEK_SET);	// filepos は  の位置を指している
	fprintf(fp, "%s\n", result);	// 表の一行分を出力する
	fprintf(fp, "</table>\n</body>\n</html>\n");
	fclose(fp);

ftellを使わず、</table>行のサイズだけ、前に戻す方法もある。これを下に示す。

	char *result = "<tr><th>\s</th><td>スペース(\f\r\n\t\v)</td></tr>";

	FILE *fp = fopen("test.html", "rb+");
	while (fgets(buffer, sizeof(buffer), fp) != NULL) {
		if (strncmp(buffer,"</table>",7) == 0) break;
	}
	fseek(fp, -strlen(buffer), SEEK_CUR);
	fprintf(fp, "%s\n", result);	// 表の一行分を出力する
	fprintf(fp, "</table>\n</body>\n</html>\n");
	fclose(fp);