ユーニックス総合研究所

  • home
  • archives
  • c-recipe-readfile

自作関数read_fileでファイルを読み込み【ファイル入出力, コマンド】

  • 作成日: 2022-09-08
  • 更新日: 2023-12-25
  • カテゴリ: C言語

C言語レシピ!ファイル内容を読み込むread_file関数

どうもC言語シェフです。
本日はとっても簡単、そしてすごく便利な関数とコマンドを作ってみたいと思います。

関数はその名も「read_file」関数!
この関数を使えば指定したファイルの内容を簡単に読み込めちゃうんですね。
後始末はfree()を呼び出すだけです。

コマンドは「readfile」コマンド!
これはread_file関数を使ったコマンドです。
引数のファイル名のファイル内容を出力します。

では具体的にレシピを見ていきましょう。

関連記事

自作関数read_fileでファイルを読み込み【ファイル入出力, コマンド】
目が覚めるC言語のdo-while文の使い方【ループ処理、初心者向け】
明快!C言語のcontinue文の使い方
君はまだC言語のdefineのすべてを知らない【マクロ、プリプロセス】
プログラミングのポインタをわかりやすく解説【C言語】

readfileコマンドの仕様

まずreadfileコマンドの仕様についてです。
このコマンドのインターフェース(使い方)は↓になります。

$ readfile ファイル名  

コマンドライン引数から引数を1つ取り、それをファイル名として扱います。
そしてそのファイル名の内容をread_file関数で読み込んで画面に出力します。

ファイルが存在しなかった場合は画面にエラーを出力するようにします。

read_file関数の仕様

read_file関数のインターフェースは↓になります。

/**  
 * fnameのファイル内容を読み込み、動的メモリ確保した文字列で返す。  
 * ファイルが存在しない場合はNULLを返す。  
 *   
 * @param fname ファイル名(パス)  
 * @return 動的メモリ確保をした文字列  
 */  
char *read_file(const char *fname);  

引数のfnameのファイル内容を読み込み、それを動的メモリ確保で返します。
ファイルが存在しない場合はNULLを返します。

コード全文

では本日のレシピのコード全文を見てみましょう。
↓になります。

/*  
* 本日のレシピはファイル内容を読み込む「read_file」関数。  
* ファイル名を指定してファイルを動的メモリ確保で読み込みます。  
* 使った後はcontentをfree()してね!  
*  
* 2022/07/21  
* MIT  
*/  
#include <stdio.h>  
#include <stdlib.h>  

// 以下テスト用  
#include <assert.h>  
#include <string.h>  

/**  
 * fnameのファイル内容を読み込み、動的メモリ確保した文字列で返す。  
 * ファイルが存在しない場合はNULLを返す。  
 *   
 * @param fname ファイル名(パス)  
 * @return 動的メモリ確保をした文字列  
 */  
char *read_file(const char *fname) {  
    if (!fname) {  
        return NULL;  
    }  

    FILE *fin = fopen(fname, "r+b");  // ファイルを開く  
    if (!fin) {  
        return NULL;  
    }  

    // ファイルサイズの取得  
    fseek(fin, 0, SEEK_END);  // ファイル末尾にシーク  
    long size = ftell(fin) + 1;  // ナル文字も含めたファイルサイズを取得  
    fseek(fin, 0, SEEK_SET);  // ファイル先頭にシーク  

    char *content = malloc(size);  // 動的メモリ確保  
    if (!content) {  
        fclose(fin);  
        return NULL;  
    }  

    fread(content, 1, size, fin);  // contentにファイル内容を読み込み  
    content[size - 1] = '\0';  // ナル文字を入れる  
    fclose(fin);  // ファイル閉じる  

    return content;  
}  

void test(void) {  
    char *content = read_file("readfile.c");  
    assert(content);  
    free(content);  

    content = read_file("sample.txt");  
    assert(content);  
    assert(!strcmp(content, "abc\ndef\nghi"));  
    free(content);  

    assert(read_file(NULL) == NULL);  
    assert(read_file("notfound") == NULL);  
}  

int main(int argc, char *argv[]) {  
    test();  // テスト(この行は削除していいです)  

    if (argc < 2) {  
        printf("Usage: readfile [file-name]\n");  
        return 0;  
    }  

    const char *fname = argv[1];  // コマンドライン引数からファイル名を取得  

    char *content = read_file(fname);  // ファイル内容の読み込み  
    if (!content) {  
        perror("read_file");  
        return 1;  
    }  

    printf("%s", content);  // ファイル内容の出力  
    free(content);  // メモリ解放  
    return 0;  
}  

ソースコードの解説

ではソースコードを解説します。
まずはインクルードからですね。

インクルード

必要なヘッダーをインクルードします。

#include <stdio.h>  
#include <stdlib.h>  

// 以下テスト用  
#include <assert.h>  
#include <string.h>  

stdio.hprintf()などで使います。
stdlib.hmalloc()free()などで使います。

assert.hstring.hはテスト用にインクルードしています。
テスト用というのは単体テストです。
これはtest()関数で実行します。

read_file関数

char *read_file(const char *fname) {  
    if (!fname) {  
        return NULL;  
    }  

    ...  
}  

↑read_file関数は引数fnameを取ります。
fnamefile name(ファイル名)の略です。
そしてfnameがNULLだったらNULLポインタを返すようにします。

    FILE *fin = fopen(fname, "r+b");  // ファイルを開く  
    if (!fin) {  
        return NULL;  
    }  

fnameを使ってファイルを開きます。
fopen()はファイルを開く関数です。
第1引数にファイル名、第2引数にモードを指定します。
モードはr+bになっています。
これはバイナリな読み書きモードです。

今回はfseek()でランダムアクセスするので読み書きモードで開いています。
finがNULLだったらNULLを返します。

関連記事
C言語のfopen関数でファイルを開く方法【fcloseも解説】

    // ファイルサイズの取得  
    fseek(fin, 0, SEEK_END);  // ファイル末尾にシーク  
    long size = ftell(fin) + 1;  // ナル文字も含めたファイルサイズを取得  
    fseek(fin, 0, SEEK_SET);  // ファイル先頭にシーク  

↑動的メモリ確保でファイルサイズ分のメモリを確保しないといけません。
ですので↑のようにランダムアクセスでファイルサイズを取得しています。
まずfseek()でファイル末尾にファイルをシークします。
そしてftell()で現在のファイルの位置、つまりファイルは末尾になっているのでファイル全体のバイト数を取得します。
それからfseek()でファイル先頭にシークしてファイルの状態を元に戻しておきます。

ファイルサイズについては+ 1にしてナル文字もふくめたサイズにして取得します。
これはC言語の文字列がナル文字を必要とするためです。

    char *content = malloc(size);  // 動的メモリ確保  
    if (!content) {  
        fclose(fin);  
        return NULL;  
    }  

malloc()size分のメモリを確保します。
malloc()はメモリの確保に失敗するとNULLを返しますので、NULLだったらファイルを閉じてNULLを返します。

関連記事
C言語の動的配列のリサイズ方法
C言語の静的なメモリと動的なメモリ

    fread(content, 1, size, fin);  // contentにファイル内容を読み込み  
    content[size - 1] = '\0';  // ナル文字を入れる  
    fclose(fin);  // ファイル閉じる  

fread()contentfinのファイル内容を保存します。

fread(保存先バッファ, 要素のバイト数, 要素数, ファイル);  

content[size - 1]の位置にナル文字を入れておきます。
これを入れておかないとcontentが文字列として機能しないことがあるので注意です!

    return content;  

↑関数の最後でcontentを返して終わりです。

main関数

次にmain関数を見てみましょう。

int main(int argc, char *argv[]) {  
    test();  // テスト(この行は削除していいです)  

    if (argc < 2) {  
        printf("Usage: readfile [file-name]\n");  
        return 0;  
    }  

    const char *fname = argv[1];  // コマンドライン引数からファイル名を取得  

    ...  
}  

main関数はコマンドライン引数を扱うのでint argcchar *argv[]を書いておきます。
関数の先頭でtest()を呼び出していますが、これはテスト用です。
本当はテストは別のバイナリを作って実行しないといけないんですが今回は手抜きです!
このtest()を付けたままにしてると余計な処理が走ってしまうので、不要な場合は消してください。

argc < 2が真になる場合、コマンドライン引数が空です。
このargcargvの長さを表しています。
argvにはデフォルトで最初の要素にプログラムのパスが入ります。
ですので長さは常に1以上です。
argc2以上になるということはコマンドライン引数が指定されているということです。

関連記事
C言語のargc, argv(コマンドライン引数)の使い方

argv[1]にコマンドで指定したファイル名が入ってます(と思われます)。
のでこれをfnameに抜き出しています。

    char *content = read_file(fname);  // ファイル内容の読み込み  
    if (!content) {  
        perror("read_file");  
        return 1;  
    }  

    printf("%s", content);  // ファイル内容の出力  
    free(content);  // メモリ解放  
    return 0;  

↑ファイル名を指定してread_file()関数を呼び出してcontentを取得します。
このcontentはファイル内容を表す文字列です。
contentがNULLの場合はperror()でエラー出力します。
perror()は内部でerrnoを参照して文字列にして出力してくれます。

printf()contentを出力して画面にファイル内容を表示します。
contentは動的メモリ確保された文字列なので使い終わったらfree()でメモリを開放します。

あとはreturn 0;して終わりです。

おわりに

今回はC言語レシピの1つ、read_file関数とreadfileコマンドを紹介しました。
ファイル内容を読み込みたい場合はこのようなユーティリティーを作っておくと便利ですよね。

🦝 < ファイル内容をリード!

🐭 < 動的メモリ確保!