C言語でファイルを一行ずつ読み込む方法【fgets, getline】
- 作成日: 2024-03-19
- 更新日: 2024-03-19
- カテゴリ: C言語
C言語で一行ずつファイルを読み込む方法を解説します。
C言語や他の言語を扱うYoutubeも公開しています。
興味がある方は以下のリンクからご覧ください。
fgetsを使う方法
fgets()
は指定されたストリームから一行読み込む関数です。
#include <stdio.h>
// 返り値はsまたはNULL
char *fgets(
char *s, // 文字列を格納するバッファのポインタ
int size, // sのサイズ(バイト数)
FILE *stream // 読み込むストリーム
);
fgets()
は引数のstream
から一行読み込み、読み込んだデータを引数のs
に格納します。
fgets()
は読み込みに成功した場合はs
へのポインタを返し、読み込みに失敗したりEOFに達した場合はNULL
を返します。
EOFとはEnd Of File
のことでファイルの終端を表すキーワードです。
引数のsize
にs
のサイズ(バイト数)を指定できるので、gets()
などに比べるとセキュアな関数と言えます。
fgets()
は改行も一緒に読み込みます。
ですのでバッファs
のお尻には改行が付くことがあります。
関連記事
C言語のfgetsを使う方法
fgetsのサンプルコード
fgets()
でファイルから一行ずつ読み込むサンプルコードです。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
// ファイルを開く
FILE *fin = fopen("sample.txt", "r");
if (fin == NULL) {
perror("fopen");
exit(EXIT_FAILURE);
}
char buf[128]; // データ格納先のバッファ
// fgetsで一行ずつ読み込み
while (fgets(buf, sizeof buf, fin) != NULL) {
printf("buf[%s]\n", buf);
}
// ファイルを閉じる
fclose(fin);
return 0;
}
fgets()
はEOFに達するとNULL
を返してくる性質を利用して、while
文でfgets()
がNULL
に達しない間、一行ずつ読み込んでいます。
sizeof buf
で配列のバッファのサイズ(バイト数)が求まるので、これをfgets()
の第2引数に渡しています。
while (fgets(buf, sizeof buf, fin) != NULL) {
...
}
このコードは非常に手軽なコードで似たようなものはよく使われますが、一つ問題があります。
それはバッファが固定長だということです。
ファイル内の一行のサイズが決まっている場合はこの固定長の読み方でも大丈夫ですが、サイズが未定の場合はこの固定長の読み込みだと一行ずつ読み込めない場合があります。
こういう場合は後述するgetline()
などを使います。
getline()を使う方法
glibc
が使える処理系(GCC
など)の場合はgetline()
が使えます。
#include <stdio.h>
// 返り値は読み込んだ文字数か-1
ssize_t getline(
char **lineptr, // 行バッファを格納するポインタのアドレス
size_t *n, // lineptrのバッファのサイズ
FILE *stream // 読み込むストリーム
);
getline()
は動的メモリ確保を内部で使用している関数です。
バッファのサイズが自動で伸縮するため、行サイズが決まっていない行も読み込むことができます。
ただし読み込むサイズはメモリが許容する範囲になります。
getline()
は第1引数にポインタ変数のアドレスを渡します。
第2引数にはsize_t
型のバッファのサイズを表す変数を渡します。
第3引数にはストリームです。
lineptr
がNULL
でn
が0の場合はgetline()
は内部でlineptr
にメモリを確保します。
このメモリはユーザー側(われわれ側)で開放しないといけません。
getline()
は読み込みに成功した場合は読み込んだ文字数を返します。
エラーが発生したりEOFに達した場合は-1
を返します。
getline()
もfgets()
と同様で改行も一緒に読み込みます。
ですので読み込んだバッファのお尻には改行が付加されることがあります。
getline()のサンプルコード
getline()
を使ってファイルから一行ずつ読み込むサンプルコードは以下になります。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
// 読み込むファイルを開く
FILE *fin = fopen("sample.txt", "r");
if (fin == NULL) {
perror("fopen");
exit(EXIT_FAILURE);
}
// getline()で一行ずつ読み込む
char *line = NULL;
size_t n = 0;
while (getline(&line, &n, fin) != -1) {
printf("line[%s]\n", line);
}
free(line);
// ファイルを閉じる
fclose(fin);
return 0;
}
getline()
がEOFに達すると-1
を返すことを利用し、while
文でgetline()
が-1
を返さない間、一行ずつ読み込むようにしています。
line
は使い終わったらfree()
関数でメモリを解放します。
これを忘れるとメモリリークと言うバグになります。
自作のmy_getline()を使う方法
getline()
関数が使えない場合はgetline()
に相当する関数を自作する必要があります。
ここでは簡単な作例を1つご紹介します。
// my_getline.c
// License: MIT
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
char *my_getline(FILE *fin) {
if (fin == NULL) {
return NULL;
}
if (feof(fin)) {
return NULL;
}
size_t capa = 2;
size_t byte = sizeof(char);
char *line = calloc(capa + 1, byte);
if (line == NULL) {
goto error;
}
size_t nread = 0;
#define resize() \
if (nread >= capa) { \
capa *= 2; \
size_t byte = sizeof(char); \
size_t size = capa * byte + byte; \
char *tmp = realloc(line, size); \
if (tmp == NULL) { \
goto error; \
} \
line = tmp; \
} \
for (;;) {
int c1 = fgetc(fin);
if (c1 == EOF) {
break;
}
resize();
if (c1 == '\r') {
// CR
line[nread++] = c1;
line[nread] = '\0';
int c2 = fgetc(fin);
if (c2 == '\n') {
// CR + LF
resize();
line[nread++] = c2;
line[nread] = '\0';
} else if (c2 == EOF) {
break;
} else {
ungetc(c2, fin);
}
break;
} else if (c1 == '\n') {
// LF
line[nread++] = c1;
line[nread] = '\0';
break;
} else {
line[nread++] = c1;
line[nread] = '\0';
}
}
return line;
error:
free(line);
return NULL;
}
void test1(void) {
FILE *fin = fopen("data.txt", "r");
if (fin == NULL) {
return;
}
char *line = NULL;
line = my_getline(fin);
assert(line);
assert(strlen(line) == 1);
assert(strcmp(line, "\n") == 0);
free(line);
line = my_getline(fin);
assert(line);
assert(strlen(line) == 4);
assert(strcmp(line, "123\n") == 0);
free(line);
line = my_getline(fin);
assert(line);
assert(strlen(line) == 5);
assert(strcmp(line, "1234\n") == 0);
free(line);
line = my_getline(fin);
assert(line);
assert(strlen(line) == 5);
assert(strcmp(line, "12345") == 0);
free(line);
fclose(fin);
}
void test2(void) {
FILE *fin = fopen("data.crlf.txt", "r");
if (fin == NULL) {
return;
}
char *line = NULL;
line = my_getline(fin);
assert(line);
assert(strlen(line) == 2);
assert(strcmp(line, "\r\n") == 0);
free(line);
line = my_getline(fin);
assert(line);
assert(strlen(line) == 5);
assert(strcmp(line, "123\r\n") == 0);
free(line);
line = my_getline(fin);
assert(line);
assert(strlen(line) == 6);
assert(strcmp(line, "1234\r\n") == 0);
free(line);
line = my_getline(fin);
assert(line);
assert(strlen(line) == 5);
assert(strcmp(line, "12345") == 0);
free(line);
fclose(fin);
}
void test3(void) {
char *line = NULL;
while ((line = my_getline(stdin)) != NULL) {
printf("line[%s]\n", line);
free(line);
}
}
int main(void) {
test1();
test2();
test3();
return 0;
}
data.txt
とdata.crlf.txt
の中身は以下。
data.txt
は改行がLF
、data.crlf.txt
は改行がCRLFになってます。
CR
のテストはしてません。
123
1234
12345
my_getline()
は動的メモリ確保を利用し、ストリームから一行読み込む自作関数です。
// 読み込みに成功したらポインタ、失敗したらNULL
char *my_getline(
FILE *fin // 読み込むストリーム
);
my_getline()
は読み込みに成功した場合は動的に確保されたメモリを返します。
エラーになったりEOFに達した場合はNULL
を返します。
使い方を示すサンプルコードは以下になります。
char *line = NULL;
while ((line = my_getline(stdin)) != NULL) {
printf("line[%s]\n", line);
free(line);
}
getline()
と違い、一行読み込むごとに新しくバッファのメモリを確保しています。
そのため行を使い終わったらその都度開放する必要があります。
my_getline()
はLF
とCRLF
とCR
の改行に対応しています。
my_getline()
の使用は無保証です。
ライセンスはMITになります。