閉じる

C言語のfscanf入門: ファイルから書式付きで読む基本と例

ファイルから数値や文字列を決まった形で読み取るとき、fscanfはとても便利です。

標準入力のscanfと同じ感覚で書けますが、ファイル特有の注意点もあります。

本記事では初心者の方にもわかりやすく、安全に正確に読み込む基本と実用的なサンプルを通して、fscanfの使い方を丁寧に解説します。

fscanfとは?ファイルから書式付きで読み込む基本

fscanfの概要

fscanfは、開いたファイルから書式(フォーマット)に従って値を読み込む関数です。

標準入力から読むscanfの「入力元がファイル版」で、C標準ライブラリstdio.hに含まれます。

書式と変数の型を正しく対応させること、および戻り値を必ず確認することが正しい使い方の基本です。

基本形と引数

プロトタイプ

int fscanf(FILE* stream, const char* format, ...); 第1引数streamfopenで開いたFILE*、第2引数formatは書式文字列、その後ろに&変数を並べます。

戻り値の概要

成功した代入の個数が返ります。

入力の終端に達して何も読めなかった場合はEOF(-1)が返ります。

0は「読み取りは試みたが、値に代入できるトークンが無かった」ことを意味します。

詳細は後半の「戻り値とエラー処理」で解説します。

使うヘッダ

#include <stdio.h>をインクルードします。

ファイルを開閉するためにfopenfcloseも併用します。

手順

1つのファイルから読む一般的な手順は次の通りです。 1) ファイルを開く → 2) fscanfで読む → 3) 戻り値を確認 → 4) 必要ならループで繰り返す → 5) ファイルを閉じる

途中でエラーや想定外のデータが来た場合は、ferrorや戻り値で状況を判断して対処します。

読み込み先の型と書式を一致させる

変数の型と書式指定子が一致しないと未定義動作になります

例えばintには%ddoubleには%lffloatには%fを使います。

必ずアドレス演算子&を付ける点も重要です。

ただし%schar配列の先頭(配列名)を渡します。

fscanfの書式指定子と基本ルール

主な書式指定子

よく使う変換と注意点

次の表は代表的な指定子と対応する変数型の例です。

表の「注意」欄は誤りやすいポイントです。

書式意味対応する変数型の例注意
%d符号付き10進整数int*符号あり
%u符号なし10進整数unsigned int*符号なし
%ldlongの整数long*32/64bit環境差に注意
%lldlong long整数long long*大きな整数
%i符号付き整数(基数自動)int*0xで16進、0で8進
%ffloatfloat*scanf系で%fはfloat、printfとは異なる
%lfdoubledouble*doubleは%lf
%c1文字char*先頭の空白をスキップしない
%s空白で区切られた文字列char[]幅指定必須例:%99s
%[…]スキャンセットchar[]例:%63[^,\n]でカンマまで読む
%*d読み捨て(該当なし)代入せず読み進める
%%リテラル%なし文字%と一致させる

幅指定(例:%99s)はバッファあふれ防止の要です。

%s%[…]で必ず使いましょう。

空白と改行の扱い

書式文字列内の空白文字は「入力中の任意長の空白(空白、タブ、改行)にマッチ」します

例えば"%d %d"は改行でも区切れます。

一方、%c%[…]は先頭の空白を自動で読み飛ばしません。

空白を飛ばしたいときは" %c"のように書式の先頭に空白を置くと、直前の空白をすべてスキップできます。

文字列を安全に読む

%sで幅指定を入れないのは危険です。

例えばchar name[100]; fscanf(fp, "%99s", name);のように、最大格納文字数を必ず指定します。

空白を含む文字列を1行まるごと読みたい場合は、%[…]を使って改行手前までを読み取れます。

例は後述します。

数値読み込みのコツ

整数なら%d%lld、浮動小数点なら%f(float)と%lf(double)を使います。

整数と実数の指定子を混同しないことが大切です。

区切り記号があるなら、書式にリテラル文字を含めて一致させます(例:"%d,%d")。

%iは基数を自動解釈するため、ログや設定ファイルで16進数が混じる可能性があるなら便利です。

部分一致の注意

fscanf部分的に一致したところまでで止まることがあります。

例えば"123,456""%d-%d"を適用すると、最初の%dで123は読めますが、次のリテラル'-'が合わずに失敗します。

このとき読み取り位置はコンマの直前や直後に残るため、次の読み取りでも同じ場所で失敗を繰り返すことがあります。

必ず戻り値で成否を判定し、失敗時は同期を取り直す(不要な文字を読み捨てる、1行読み飛ばすなど)ことが必要です。

fscanfの使い方の例

以下では、あらかじめ示す内容のテキストファイルを用意してからプログラムをコンパイル・実行してください。

実行結果も併記します。

例1 スペース区切りの2整数

まずは最も基本的な例です。

ファイルnums.txtに次の内容を用意します。

nums.txt
10 20

次のプログラムは2つの整数を読み、合計を表示します。

C言語
#include <stdio.h>

int main(void) {
    // 読み込む2つの整数
    int a = 0, b = 0;

    // ファイルを開く (読み取り専用)
    FILE *fp = fopen("nums.txt", "r");
    if (!fp) {
        perror("nums.txt を開けませんでした");
        return 1;
    }

    // "%d %d" は空白・改行を区切りとして2つの整数を読む
    // 戻り値が 2 であることを必ず確認する
    int n = fscanf(fp, "%d %d", &a, &b);
    if (n == 2) {
        printf("読み取った値: a=%d, b=%d\n", a, b);
        printf("合計: %d\n", a + b);
    } else if (n == EOF) {
        fprintf(stderr, "ファイルの終端(EOF)に達しました\n");
    } else {
        fprintf(stderr, "想定した形式で読み取れませんでした (n=%d)\n", n);
    }

    fclose(fp);
    return 0;
}
実行結果
読み取った値: a=10, b=20
合計: 30

例2 カンマ区切りの数値と文字列

CSV風データを1行ずつ読みます。

文字列は空白を含んでも行末まで取りたいので%[…]を使います。

ファイルmix.csvは次の内容です。

mix.csv
42,apple
7,banana
100,dragon fruit
C言語
#include <stdio.h>

int main(void) {
    FILE *fp = fopen("mix.csv", "r");
    if (!fp) {
        perror("mix.csv を開けませんでした");
        return 1;
    }

    int num = 0;
    char item[64]; // 63文字 + 終端'
#include <stdio.h>
int main(void) {
FILE *fp = fopen("mix.csv", "r");
if (!fp) {
perror("mix.csv を開けませんでした");
return 1;
}
int num = 0;
char item[64]; // 63文字 + 終端'\0' を想定
// 先頭の空白を飛ばすため書式の先頭に空白を置く
// %d の後にカンマをリテラルとして一致させ、
// %63[^\r\n] で行末まで(改行を含まない)最大63文字を読む
// 戻り値が 2 のときのみ成功として処理する
while (1) {
int n = fscanf(fp, " %d , %63[^\r\n]", &num, item);
if (n == 2) {
printf("num=%d, item=%s\n", num, item);
} else if (n == EOF) {
// 入力終端
break;
} else {
// 想定外の形式。残りを行末まで読み捨てて同期を回復
int ch;
while ((ch = fgetc(fp)) != '\n' && ch != EOF) {
/* 何もしない(読み捨て) */
}
fprintf(stderr, "行の形式エラーを検出しスキップしました\n");
}
}
fclose(fp);
return 0;
}
' を想定 // 先頭の空白を飛ばすため書式の先頭に空白を置く // %d の後にカンマをリテラルとして一致させ、 // %63[^\r\n] で行末まで(改行を含まない)最大63文字を読む // 戻り値が 2 のときのみ成功として処理する while (1) { int n = fscanf(fp, " %d , %63[^\r\n]", &num, item); if (n == 2) { printf("num=%d, item=%s\n", num, item); } else if (n == EOF) { // 入力終端 break; } else { // 想定外の形式。残りを行末まで読み捨てて同期を回復 int ch; while ((ch = fgetc(fp)) != '\n' && ch != EOF) { /* 何もしない(読み捨て) */ } fprintf(stderr, "行の形式エラーを検出しスキップしました\n"); } } fclose(fp); return 0; }
実行結果
num=42, item=apple
num=7, item=banana
num=100, item=dragon fruit

例3 日付(YYYY MM DD)を読む

スペース区切りの年・月・日を読み、0埋めで表示します。

ファイルdate.txtは次の内容です。

date.txt
2025 10 11
C言語
#include <stdio.h>

int main(void) {
    FILE *fp = fopen("date.txt", "r");
    if (!fp) {
        perror("date.txt を開けませんでした");
        return 1;
    }

    int y = 0, m = 0, d = 0;

    // 年・月・日を順に読む。空白や改行は自動で区切り扱い
    int n = fscanf(fp, "%d %d %d", &y, &m, &d);
    if (n == 3) {
        // ゼロ埋め表示(%02d)は出力側(printf)の書式
        printf("読み取った日付: %04d/%02d/%02d\n", y, m, d);
    } else {
        fprintf(stderr, "日付を3要素すべて読み込めませんでした (n=%d)\n", n);
    }

    fclose(fp);
    return 0;
}
実行結果
読み取った日付: 2025/10/11

例4 名前と年齢を読む

%sは空白で区切られます。

安全のため幅指定を使います。

ファイルpeople.txtは次の内容です。

people.txt
Taro 19
Hanako 21
Suzuki 35
C言語
#include <stdio.h>

int main(void) {
    FILE *fp = fopen("people.txt", "r");
    if (!fp) {
        perror("people.txt を開けませんでした");
        return 1;
    }

    char name[100];
    int age = 0;

    // 1行ずつ「名前(空白なし) 年齢」を読む
    while (1) {
        // 先頭に空白を入れて改行などの空白をスキップ
        int n = fscanf(fp, " %99s %d", name, &age);
        if (n == 2) {
            printf("name=%s, age=%d\n", name, age);
        } else if (n == EOF) {
            break;
        } else {
            // 整形ミスがあれば残りを読み捨て
            int ch;
            while ((ch = fgetc(fp)) != '\n' && ch != EOF) { /* skip */ }
            fprintf(stderr, "不正な行をスキップしました\n");
        }
    }

    fclose(fp);
    return 0;
}
実行結果
name=Taro, age=19
name=Hanako, age=21
name=Suzuki, age=35
補足

氏名に空白が含まれる場合は" %99[^0-9\r\n] %d"のように、%[…]で「年齢の手前まで」を読む手もあります。

例5 先頭の空白を飛ばして読む

%cは空白を自動でスキップしません。

先頭に空白を置いた" %c"で、最初の非空白文字だけを取り出せます。

ファイルchars.txtは次の内容です。

chars.txt
   X

Y
C言語
#include <stdio.h>

int main(void) {
    FILE *fp = fopen("chars.txt", "r");
    if (!fp) {
        perror("chars.txt を開けませんでした");
        return 1;
    }

    char c1 = '
#include <stdio.h>
int main(void) {
FILE *fp = fopen("chars.txt", "r");
if (!fp) {
perror("chars.txt を開けませんでした");
return 1;
}
char c1 = '\0', c2 = '\0';
// " %c" は直前の空白(空白/タブ/改行)をすべて読み飛ばして1文字読む
if (fscanf(fp, " %c", &c1) == 1) {
printf("最初の非空白文字: '%c'\n", c1); // 期待: 'X'
} else {
fprintf(stderr, "1文字目を読み取れませんでした\n");
}
// 次の呼び出しでも同様に非空白を読み取る
if (fscanf(fp, " %c", &c2) == 1) {
printf("次の非空白文字: '%c'\n", c2); // 期待: 'Y'
} else {
fprintf(stderr, "2文字目を読み取れませんでした\n");
}
fclose(fp);
return 0;
}
', c2 = '
#include <stdio.h>
int main(void) {
FILE *fp = fopen("chars.txt", "r");
if (!fp) {
perror("chars.txt を開けませんでした");
return 1;
}
char c1 = '\0', c2 = '\0';
// " %c" は直前の空白(空白/タブ/改行)をすべて読み飛ばして1文字読む
if (fscanf(fp, " %c", &c1) == 1) {
printf("最初の非空白文字: '%c'\n", c1); // 期待: 'X'
} else {
fprintf(stderr, "1文字目を読み取れませんでした\n");
}
// 次の呼び出しでも同様に非空白を読み取る
if (fscanf(fp, " %c", &c2) == 1) {
printf("次の非空白文字: '%c'\n", c2); // 期待: 'Y'
} else {
fprintf(stderr, "2文字目を読み取れませんでした\n");
}
fclose(fp);
return 0;
}
'; // " %c" は直前の空白(空白/タブ/改行)をすべて読み飛ばして1文字読む if (fscanf(fp, " %c", &c1) == 1) { printf("最初の非空白文字: '%c'\n", c1); // 期待: 'X' } else { fprintf(stderr, "1文字目を読み取れませんでした\n"); } // 次の呼び出しでも同様に非空白を読み取る if (fscanf(fp, " %c", &c2) == 1) { printf("次の非空白文字: '%c'\n", c2); // 期待: 'Y' } else { fprintf(stderr, "2文字目を読み取れませんでした\n"); } fclose(fp); return 0; }
実行結果
最初の非空白文字: 'X'
次の非空白文字: 'Y'

fscanfの戻り値とエラー処理

戻り値の意味

fscanfの戻り値は「成功した代入の個数」です。

想定した個数と一致しているか必ず確認します。

  • 期待個数と一致: その読み取りは成功
  • 0: 書式は合致しないか、数値に変換できないなどで代入が1つも成功していない
  • EOF(-1): 入力の終端に達して、何も読み取れなかった

0とEOFは意味が違います

0は「形式不一致」、EOFは「データが尽きた」です。

EOFの判定と読み取りループ

feofでループを制御するのは避けfscanfの戻り値でループを回すのが正しいパターンです。

次の例は「2つの整数の列」を最後まで読む典型です。

C言語
#include <stdio.h>

int main(void) {
    FILE *fp = fopen("pairs.txt", "r");
    if (!fp) {
        perror("pairs.txt を開けませんでした");
        return 1;
    }

    int a, b;
    for (;;) {
        int r = fscanf(fp, " %d %d", &a, &b);
        if (r == 2) {
            printf("pair: %d %d\n", a, b);
        } else if (r == EOF) {
            // 正常に終端へ
            break;
        } else {
            // 部分的にしか読めなかった or 書式不一致
            // ここで無限ループに陥らないよう、読み捨てを行って同期回復
            int ch;
            while ((ch = fgetc(fp)) != '\n' && ch != EOF) { /* skip */ }
            fprintf(stderr, "形式エラーの行をスキップしました\n");
            // 状況に応じて継続 or 中断
            // break; // 中断したい場合
        }
    }

    fclose(fp);
    return 0;
}

失敗時の対処

失敗には「入力終端」と「形式不一致」があります。

終端なら終了すればよいですが、形式不一致なら次のいずれかで対処します。

  • その行の残りを読み捨てて次行からやり直す(上の例のようにfgetcで改行まで捨てる)。
  • エラーフラグを確認するためferrorを用いてI/Oエラーかどうかを判断し、必要ならclearerrでクリアします。
  • 読み取り位置を移動する必要があればfseekftellで巻き戻す。ただしテキストファイルでは安易なランダムアクセスは推奨されません。

よくあるつまずき

初心者がつまずきやすい点を挙げ、短く対策を添えます。

  • &を付け忘れる: %d%lfでは&変数が必須です。
  • %fと%lfの混同: scanf系では%ffloat%lfdoubleです。
  • %sの幅指定なし: バッファあふれの原因。必ず最大長を指定します。
  • %cで空白を読んでしまう: " %c"と先頭に空白を入れます。
  • 形式不一致時の無限ループ: 戻り値を見て読み捨てや中断で同期を取ります。
  • リテラルとの一致漏れ: CSVなら"%d,%d"のようにコンマを明示します。

安全に使うポイント

  • 戻り値を必ず確認する(期待個数と一致しているか、EOFか)。
  • 文字列には幅指定(例:%63s%63[^\n])を付ける。
  • 型と書式を一致(int⇔%d、double⇔%lf、float⇔%f)。
  • %cや%[…]で空白を飛ばす場合は" "を書式に含める。
  • 形式不一致時は同期回復(行末まで読み捨てるなど)を設ける。

まとめ

fscanfは「ファイルからフォーマットに従って値を読む」ための強力な関数です。

正しく使うには、型と書式の対応を守る文字列には幅指定を付ける戻り値を厳密に検査する、そして失敗時に無限ループに落ちない設計が欠かせません。

例で示したように、スペースやカンマなどの区切り、改行を含む空白の扱い、%[…]による柔軟な文字列抽出を組み合わせることで、さまざまなファイル形式に対応できます。

これらのポイントを押さえれば、fscanfは初心者にとっても扱いやすく、実務でも役立つ堅牢な読み込み処理を実装できます。

この記事を書いた人
エーテリア編集部
エーテリア編集部

プログラミングの基礎をしっかり学びたい方向けに、C言語の基本文法から解説しています。ポインタやメモリ管理も少しずつ理解できるよう工夫しています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!