[C言語のはじめ方] Part22: 行入出力(fgets, fputs)

C言語

Step 5: ファイル入出力と標準ライブラリ 📂

行単位の入出力:fgets と fputs をマスターしよう!

こんにちは! C言語学習、順調に進んでいますか? 😊 Step 5 ではファイル操作について学んでいますね。前回は fscanffprintf を使って、書式を指定してファイルからデータを読み書きする方法を学びました。

今回は、ファイルから1行ずつ文字列を読み込んだり、ファイルに文字列(行)を書き込んだりするための重要な関数、fgetsfputs について詳しく見ていきましょう。これらはテキストファイルの処理、特に設定ファイルやログファイルの読み書きなどで非常によく使われます。📝


1. ファイルから1行読み込む: fgets 関数

ファイルからデータを読み込む際、特定の書式ではなく、単純に「1行分」のテキストをまとめて取得したい場面が多くあります。例えば、テキストファイルの内容をそのまま画面に表示したり、特定のキーワードを含む行を探したりする場合です。そんな時に活躍するのが fgets 関数です。

fgets 関数の書式

fgets 関数の基本的な形は次のようになります。

char *fgets(char *str, int n, FILE *stream);

引数を一つずつ見てみましょう。

  • char *str: 読み込んだ文字列を格納するための文字配列(バッファ)へのポインタを指定します。ここに読み込まれた文字列が格納されます。
  • int n: 読み込むことができる最大文字数を指定します。これは、str で指定した配列のサイズ(要素数)を指定するのが一般的です。重要なのは、実際に読み込まれるのは最大でも n-1 文字までということです。最後の1文字分は、文字列の終わりを示すヌル文字 (\0) を格納するために確保されます。
  • FILE *stream: どのファイルから読み込むかを指定するファイルポインタです。fopen 関数で開いたファイルのポインタを指定します。
fgets 関数の動作 🤔

fgets は、指定されたファイルストリーム (stream) から、以下のいずれかの条件が満たされるまで文字を読み込み、指定されたバッファ (str) に格納します。

  1. 改行文字 (\n) が読み込まれた場合: 改行文字も文字列の一部としてバッファに格納されます。
  2. n-1 文字が読み込まれた場合: 指定された最大文字数に達した場合です。
  3. ファイルの終端 (EOF) に達した場合: ファイルの最後まで読み込んだ場合です。

読み込みが完了すると、fgets は自動的にバッファの最後にヌル文字 (\0) を追加します。これにより、バッファに格納された内容がC言語の文字列として正しく扱えるようになります。

戻り値について

fgets 関数は、以下の値を返します。

  • 成功した場合: 文字列が格納されたバッファ (str) へのポインタを返します。これは、第1引数で渡したものと同じ値です。
  • 失敗した場合、またはファイルの終端に既に達していた場合: NULL ポインタを返します。

したがって、fgets をループで使う場合は、戻り値が NULL かどうかをチェックすることで、ファイルの終端まで読み込んだか、あるいはエラーが発生したかを判断できます。✅

💡 fgets の重要なポイント
  • バッファオーバーフローに強い: fgets は読み込む最大文字数 (n) を指定するため、用意したバッファのサイズを超えるデータが読み込まれてメモリを破壊する「バッファオーバーフロー」を防ぐことができます。これは非常に重要な特徴です。
  • 改行文字も読み込む: 読み込んだ行に改行文字 (\n) が含まれている場合、それも文字列の一部としてバッファに格納されます。
  • ヌル文字で終端: 読み込んだ文字列の末尾には必ずヌル文字 (\0) が追加されます。
  • gets 関数は使わないで!: 以前は gets という標準入力から1行読み込む関数がありましたが、これは読み込む文字数を指定できないため、バッファオーバーフローの脆弱性を引き起こす非常に危険な関数です。絶対に使用しないでくださいfgets を使いましょう。
fgets の使用例

それでは、実際に fgets を使ってファイルの内容を1行ずつ読み込み、画面に表示する簡単なプログラムを見てみましょう。

まず、以下のような内容のテキストファイル input.txt を用意してください。

Hello, C Language!
Let's learn fgets function.
This is the third line.

次に、このファイルを読み込む C のコードです。

#include <stdio.h>

#define BUFFER_SIZE 256 // バッファサイズを定義

int main() {
    FILE *fp;
    char buffer[BUFFER_SIZE]; // 文字列を読み込むためのバッファ

    // ファイルを読み込みモードで開く
    fp = fopen("input.txt", "r");
    if (fp == NULL) {
        perror("Error opening file"); // エラーメッセージを表示
        return 1; // エラー終了
    }

    printf("Reading from file:\n");
    printf("--------------------\n");

    // fgetsで1行ずつ読み込み、NULLが返されるまで(ファイルの終わりまで)繰り返す
    while (fgets(buffer, BUFFER_SIZE, fp) != NULL) {
        // 読み込んだ行を表示
        printf("%s", buffer); // bufferには改行が含まれている可能性があるので、printfでは\nをつけない
    }

    printf("--------------------\n");
    printf("Finished reading file.\n");

    // ファイルを閉じる
    if (fclose(fp) != 0) {
        perror("Error closing file");
        return 1; // エラー終了
    }

    return 0; // 正常終了
}

このコードを実行すると、input.txt の内容が1行ずつ読み込まれ、画面に表示されます。BUFFER_SIZE は、1行の最大文字数を想定して決めます。ここでは256文字としていますが、必要に応じて調整してください。fgets のループ条件で != NULL をチェックしている点にも注目してください。これにより、ファイルの終端まで安全に読み込めます。


2. ファイルに文字列(行)を書き込む: fputs 関数

次に、ファイルに文字列(通常は1行分のテキスト)を書き込むための fputs 関数について学びましょう。fprintf のように書式を指定する必要がなく、単純に文字列をそのままファイルに書き込みたい場合に便利です。

fputs 関数の書式

fputs 関数の基本的な形は以下の通りです。

int fputs(const char *str, FILE *stream);

引数を見てみましょう。

  • const char *str: ファイルに書き込みたい文字列へのポインタを指定します。この文字列はヌル文字 (\0) で終わっている必要がありますが、ヌル文字自体はファイルには書き込まれません。
  • FILE *stream: どのファイルに書き込むかを指定するファイルポインタです。fopen 関数で書き込みモード(”w”, “a” など)で開いたファイルのポインタを指定します。
fputs 関数の動作 🤔

fputs は、指定された文字列 (str) を、ヌル文字 (\0) に到達するまで、指定されたファイルストリーム (stream) に書き込みます。重要な点として、fputs自動的に改行文字 (\n) を追加しません。もし行末に改行を入れたい場合は、書き込む文字列 str の中にあらかじめ含めておく必要があります。

戻り値について

fputs 関数は、以下の値を返します。

  • 成功した場合: 負でない値(通常は 0)を返します。
  • 失敗した場合: EOF (End Of File、通常は -1 として定義される定数) を返します。

書き込みエラーが発生したかどうかを確認するには、戻り値が EOF かどうかをチェックします。✅

⚠️ fputs の注意点
  • 改行は自動で追加されない: これが fputs の最も重要な特徴の一つです。改行が必要な場合は、文字列の末尾に \n を付けておく必要があります。
  • ヌル文字は書き込まない: 文字列の終端を示す \0 はファイルには書き込まれません。
  • fprintf との違い: fprintf は書式指定文字列を使って様々なデータ型を整形して書き込めますが、fputs は単純に文字列をそのまま書き込むための関数です。
fputs の使用例

では、fputs を使ってファイルに複数の行を書き込む簡単なプログラムを見てみましょう。

#include <stdio.h>

int main() {
    FILE *fp;

    // ファイルを書き込みモードで開く(ファイルが存在しない場合は新規作成、存在する場合は上書き)
    fp = fopen("output.txt", "w");
    if (fp == NULL) {
        perror("Error opening file");
        return 1;
    }

    printf("Writing to file...\n");

    // 1行目(改行を含む)を書き込む
    if (fputs("This is the first line.\n", fp) == EOF) {
        perror("Error writing to file");
        fclose(fp); // エラーが発生してもファイルを閉じる試みはする
        return 1;
    }

    // 2行目(改行を含む)を書き込む
    if (fputs("fputs example.\n", fp) == EOF) {
        perror("Error writing to file");
        fclose(fp);
        return 1;
    }

    // 3行目(改行を含まない)を書き込む
    if (fputs("This line has no newline character.", fp) == EOF) {
        perror("Error writing to file");
        fclose(fp);
        return 1;
    }
    // 必要なら、改行だけを書き込むこともできる
    if (fputs("\n", fp) == EOF) {
        perror("Error writing to file");
        fclose(fp);
        return 1;
    }


    printf("Finished writing to file.\n");

    // ファイルを閉じる
    if (fclose(fp) != 0) {
        perror("Error closing file");
        return 1;
    }

    return 0;
}

このコードを実行すると、output.txt というファイルが作成(または上書き)され、3行のテキストが書き込まれます。fputs の呼び出しごとに、書き込みが成功したか(戻り値が EOF でないか)をチェックすることが推奨されます。文字列の末尾に \n を含めることで、ファイル内で改行されることに注意してください。


3. 実践! fgets と fputs でファイルコピー

fgetsfputs の使い方を理解したところで、これらを組み合わせて、あるファイルの内容を別のファイルに1行ずつコピーするプログラムを作成してみましょう。これは非常に実用的な例です。

#include <stdio.h>

#define BUFFER_SIZE 256

int main() {
    FILE *source_fp, *dest_fp;
    char buffer[BUFFER_SIZE];

    // コピー元ファイルを開く(読み込みモード)
    source_fp = fopen("input.txt", "r");
    if (source_fp == NULL) {
        perror("Error opening source file");
        return 1;
    }

    // コピー先ファイルを開く(書き込みモード)
    dest_fp = fopen("output_copy.txt", "w");
    if (dest_fp == NULL) {
        perror("Error opening destination file");
        fclose(source_fp); // 開いたファイルを閉じる
        return 1;
    }

    printf("Copying file contents...\n");

    // source_fp から1行読み込み、bufferに格納
    // fgetsがNULLを返すまで(ファイルの終わりまで)繰り返す
    while (fgets(buffer, BUFFER_SIZE, source_fp) != NULL) {
        // 読み込んだ行 (buffer) を dest_fp に書き込む
        if (fputs(buffer, dest_fp) == EOF) {
            perror("Error writing to destination file");
            fclose(source_fp);
            fclose(dest_fp);
            return 1;
        }
    }

    // ファイルの終端に達したか、fgetsでエラーが発生したかを確認
    if (ferror(source_fp)) {
        fprintf(stderr, "Error reading from source file.\n");
         fclose(source_fp);
         fclose(dest_fp);
        return 1;
    }


    printf("File copied successfully!\n");

    // 両方のファイルを閉じる
    if (fclose(source_fp) != 0) {
        perror("Error closing source file");
        // dest_fp も閉じるべきだが、エラー処理は簡略化
    }
    if (fclose(dest_fp) != 0) {
        perror("Error closing destination file");
    }

    return 0;
}

このプログラムは、まず input.txt を読み込みモード ("r") で、output_copy.txt を書き込みモード ("w") で開きます。そして、while ループの中で fgets を使って input.txt から1行ずつ読み込み、その内容を fputs を使って output_copy.txt に書き込んでいます。fgetsNULL を返したら(ファイルの終端)、ループが終了します。最後に両方のファイルを閉じて完了です。

fgets は改行文字 \n も読み込んでくれるので、fputs でそのまま書き込めば、元のファイルの改行構造が維持されるのがポイントです。

また、ループ終了後に `ferror` 関数を使って、fgetsがNULLを返した理由がファイル終端(EOF)なのか、それとも読み込みエラーなのかを確認しています。これはより堅牢なエラー処理のために役立ちます。


まとめ ✨

今回は、ファイルから行単位で文字列を読み込む fgets と、ファイルに文字列(行)を書き込む fputs について学びました。

  • fgets:
    • ファイルから1行(または指定最大文字数-1まで)を読み込む。
    • 改行文字(\n)も読み込む。
    • 読み込んだ文字列の末尾にヌル文字(\0)を付加する。
    • バッファサイズを指定するため、バッファオーバーフローに対して安全。
    • ファイルの終端またはエラーで NULL を返す。
  • fputs:
    • ファイルに文字列を書き込む。
    • 改行文字(\n)を自動で追加しない
    • 文字列終端のヌル文字(\0)は書き込まない。
    • 書き込みエラーで EOF を返す。

これらの関数は、テキストファイルを扱う上で非常に基本的かつ重要なものです。特に fgets は、安全な入力処理の基本となる関数ですので、しっかり使い方をマスターしておきましょう。

次回は、文字列を操作するための便利な標準ライブラリ関数 (strlen, strcpy, strcat など) について学びます。お楽しみに!🚀

コメント

タイトルとURLをコピーしました