ファイルから数値や文字列を決まった形で読み取るとき、fscanfはとても便利です。
標準入力のscanfと同じ感覚で書けますが、ファイル特有の注意点もあります。
本記事では初心者の方にもわかりやすく、安全に正確に読み込む基本と実用的なサンプルを通して、fscanfの使い方を丁寧に解説します。
fscanfとは?ファイルから書式付きで読み込む基本
fscanfの概要
fscanf
は、開いたファイルから書式(フォーマット)に従って値を読み込む関数です。
標準入力から読むscanf
の「入力元がファイル版」で、C標準ライブラリstdio.h
に含まれます。
書式と変数の型を正しく対応させること、および戻り値を必ず確認することが正しい使い方の基本です。
基本形と引数
プロトタイプ
int fscanf(FILE* stream, const char* format, ...);
第1引数stream
はfopen
で開いたFILE*
、第2引数format
は書式文字列、その後ろに&変数
を並べます。
戻り値の概要
成功した代入の個数が返ります。
入力の終端に達して何も読めなかった場合はEOF
(-1)が返ります。
0は「読み取りは試みたが、値に代入できるトークンが無かった」ことを意味します。
詳細は後半の「戻り値とエラー処理」で解説します。
使うヘッダ
#include <stdio.h>
をインクルードします。
ファイルを開閉するためにfopen
とfclose
も併用します。
手順
1つのファイルから読む一般的な手順は次の通りです。 1) ファイルを開く → 2) fscanfで読む → 3) 戻り値を確認 → 4) 必要ならループで繰り返す → 5) ファイルを閉じる。
途中でエラーや想定外のデータが来た場合は、ferror
や戻り値で状況を判断して対処します。
読み込み先の型と書式を一致させる
変数の型と書式指定子が一致しないと未定義動作になります。
例えばint
には%d
、double
には%lf
、float
には%f
を使います。
必ずアドレス演算子&
を付ける点も重要です。
ただし%s
はchar配列
の先頭(配列名)を渡します。
fscanfの書式指定子と基本ルール
主な書式指定子
よく使う変換と注意点
次の表は代表的な指定子と対応する変数型の例です。
表の「注意」欄は誤りやすいポイントです。
書式 | 意味 | 対応する変数型の例 | 注意 |
---|---|---|---|
%d | 符号付き10進整数 | int* | 符号あり |
%u | 符号なし10進整数 | unsigned int* | 符号なし |
%ld | longの整数 | long* | 32/64bit環境差に注意 |
%lld | long long整数 | long long* | 大きな整数 |
%i | 符号付き整数(基数自動) | int* | 0xで16進、0で8進 |
%f | float | float* | scanf系で%fはfloat、printfとは異なる |
%lf | double | double* | doubleは%lf |
%c | 1文字 | 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
に次の内容を用意します。
10 20
次のプログラムは2つの整数を読み、合計を表示します。
#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
は次の内容です。
42,apple
7,banana
100,dragon fruit
#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
は次の内容です。
2025 10 11
#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
は次の内容です。
Taro 19
Hanako 21
Suzuki 35
#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
は次の内容です。
X
Y
#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つの整数の列」を最後まで読む典型です。
#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
でクリアします。 - 読み取り位置を移動する必要があれば
fseek
やftell
で巻き戻す。ただしテキストファイルでは安易なランダムアクセスは推奨されません。
よくあるつまずき
初心者がつまずきやすい点を挙げ、短く対策を添えます。
- &を付け忘れる:
%d
や%lf
では&変数
が必須です。 - %fと%lfの混同: scanf系では
%f
はfloat
、%lf
はdouble
です。 - %sの幅指定なし: バッファあふれの原因。必ず最大長を指定します。
- %cで空白を読んでしまう:
" %c"
と先頭に空白を入れます。 - 形式不一致時の無限ループ: 戻り値を見て読み捨てや中断で同期を取ります。
- リテラルとの一致漏れ: CSVなら
"%d,%d"
のようにコンマを明示します。
安全に使うポイント
- 戻り値を必ず確認する(期待個数と一致しているか、EOFか)。
- 文字列には幅指定(例:
%63s
、%63[^\n]
)を付ける。 - 型と書式を一致(int⇔%d、double⇔%lf、float⇔%f)。
- %cや%[…]で空白を飛ばす場合は
" "
を書式に含める。 - 形式不一致時は同期回復(行末まで読み捨てるなど)を設ける。
まとめ
fscanfは「ファイルからフォーマットに従って値を読む」ための強力な関数です。
正しく使うには、型と書式の対応を守る、文字列には幅指定を付ける、戻り値を厳密に検査する、そして失敗時に無限ループに落ちない設計が欠かせません。
例で示したように、スペースやカンマなどの区切り、改行を含む空白の扱い、%[…]
による柔軟な文字列抽出を組み合わせることで、さまざまなファイル形式に対応できます。
これらのポイントを押さえれば、fscanfは初心者にとっても扱いやすく、実務でも役立つ堅牢な読み込み処理を実装できます。