ユーニックス総合研究所

  • home
  • archives
  • c-file-yomikomi-itigyozutsu

C言語でファイルを一行ずつ読み込む方法【fgets, getline】

  • 作成日: 2024-03-19
  • 更新日: 2024-03-19
  • カテゴリ: C言語

C言語で一行ずつファイルを読み込む方法を解説します。

C言語や他の言語を扱うYoutubeも公開しています。
興味がある方は以下のリンクからご覧ください。

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のことでファイルの終端を表すキーワードです。

引数のsizesのサイズ(バイト数)を指定できるので、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引数にはストリームです。

lineptrNULLnが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.txtdata.crlf.txtの中身は以下。
data.txtは改行がLFdata.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()LFCRLFCRの改行に対応しています。

my_getline()の使用は無保証です。
ライセンスはMITになります。

関連動画
C言語でファイルから一行読み取るmy_getline()関数を作る - YouTube