ファイルへの文字列の読み書き

ここではファイルに対して文字列を読み書きする、基本的な例を記載します。

とても簡単な例なので、ことさら取り上げる必要もないかもしれませんが、話の流れでたまに参照したいときがあるので、 一応書いておきます。

文字列データをファイルに書き込む例は次のようになります。

#include <stdio.h>
#include <stdlib.h>

int main() {
  char *line = "Hello, world!\n";
  FILE *fp = NULL;

  if ((fp = fopen("foo.txt", "w")) == NULL) {
    perror(NULL);
    return 1;
  }

  fputs(line, fp);

  fclose(fp);
  return 0;
}

fopen 関数にて、ファイル名と書き込みモード w を指定してファイルを開いています。

蛇足ながら、ひとこと上のコードについて補足しておきます。上のコード例では fopen 関数の呼び出しは次のようにしています。

  if ((fp = fopen("foo.txt", "w")) == NULL) {

このようにファイルポインタに代入して、その結果を評価する、という一連の流れを一行に書くのは、 私としてはあまり良いことだと思っていないです。

しかし C 言語では一般的な書き方であって、 この程度で難しい、なんてギブアップしても話にならないのも事実です。そのためより普通の書き方として、当サイトでもそうしています。

ちなみに、自分はこのような場合は、次のように定数は前に書くことにしています。

  if (NULL == (fp = fopen("foo.txt", "w"))) {

定数に値は代入できないので、万が一 === と書き間違えていた場合、コンパイル時に間違いに気付くからです。しかし、こういう流儀の人は稀ではないと思いますが、なんとなく少数派の気がするので、より「普通の書き方」として上のように書いています。

そして、fputs 関数を使って、開いたファイルに文字列を書き込んでいます。

テキストファイルの読み込み

テキストファイルから全行を読み込み標準出力に出力する例は次の通りです。

#include <stdio.h>
#include <stdlib.h>

int main() {
  char line[256];
  FILE *fp = NULL;

  if ((fp = fopen("foo.txt", "r")) == NULL) {
    perror(NULL);
    return 1;
  }

  while (fgets(line, sizeof(line), fp) != NULL) {
    printf("%s", line);
  }

  fclose(fp);
  return 0;
}

fopen 関数にて、ファイル名と読み込みモード r を指定してファイルを開いています。 そして、fgets 関数を使って、開いたファイルから文字列を読み込んでいます。

fgets 関数は読み込めなかったときに、NULL を返すので、エラーがなければ NULL 以外のときに処理を繰り返すことで全行読み取ることができます。

feof 関数で、ファイルの末尾を表す EOF インジケータをチェックできます。すなわち次のようにすれば、 fgets 関数が NULL を返した理由が EOF に到達したことであることがわかります。

#include <stdio.h>
#include <stdlib.h>

int main() {
  char line[256];
  FILE *fp = NULL;

  if ((fp = fopen("foo.txt", "r")) == NULL) {
    perror(NULL);
    return 1;
  }

  while (fgets(line, sizeof(line), fp) != NULL) {
    printf("%s", line);
  }

  if (feof(fp)) {
    printf("[EOF]\n");
  }

  fclose(fp);
  return 0;
}

尚、ここでは、fgets 関数で読み込みを試みたときに、ファイルの末尾であるためにこれ以上データが読み取れない、という状況になって始めて、 EOF インジケータがセットされます。

そのため feof 関数で先に EOF をチェックしてから、fgets 関数を読むかどうか決めようとするのは間違いです。

バッファサイズ指定の注意点

念のため、fgets関数でバッファを渡すときの注意点を指摘しておきます。

fgets関数では、第一引数でバッファへのポインタを、第二引数でバッファサイズを渡します。 fgets 関数は文字列を読み込むことを想定しているので、末尾に 0 を書き込みます。 このため、読み込める文字数は (第二引数で渡したサイズ) より 1 文字少なくなります。

例えば次のようなテキストファイル foo.txt があるとします。

cat foo.txt
123456790123457890

cat foo.txt | xxd
00000000: 3132 3334 3536 3739 3031 3233 3435 3738  1234567901234578
00000010: 3930 0a                                  90.
$ cat foo.txt
123456790123457890

$ cat foo.txt | xxd #一応 HEX でも表示
00000000: 3132 3334 3536 3739 3031 3233 3435 3738  1234567901234578
00000010: 3930 0a                                  90.

これを次のように 5 バイトのバッファで読み込むと、4 文字ずつ読み込むことになります。

#include <stdio.h>
#include <stdlib.h>

int main() {
  int i = 0;
  char line[5];
  FILE *fp = NULL;

  if ((fp = fopen("foo.txt", "r")) == NULL) {
    perror(NULL);
    return 1;
  }

  while (fgets(line, sizeof(line), fp) != NULL) {
    printf("%03d: %s\n", ++i, line);
  }

  if (feof(fp)) {
    printf("[EOF]\n");
  }

  fclose(fp);
  return 0;
}

実行結果は次の通りです。

001: 1234
002: 5679
003: 0123
004: 4578
005: 90

[EOF]

以上、fputs 関数や fgets 関数で文字列をファイルに読み書きする基本的な方法と注意点を少し紹介しました。