ファイルに対する操作 | Programming Place Plus C言語編 第44章

トップページC言語編

このページの概要

以下は目次です。


ファイルの削除

標準ライブラリ関数には、ファイルを削除する remove関数が用意されています。remove関数は、<stdio.h> に以下のように宣言されています。

int remove(const char* filename);

引数にファイル名を指定すると、そのファイルを削除します。戻り値は、成功すれば 0 が、失敗すると 0以外が返されます。

【上級】remove関数は、厳密には、ファイルを指定した名前でアクセスできない状態にするとされています。アクセスできない状態というのは、ほとんどの環境では、ファイルを削除することと同義です。また、「指定した名前で」とあるように、ほかの名前で同一のファイルを開いているようなケースでは、削除が行われないかもしれません。

指定したファイルがどこかからアクセスされている場合、remove関数がどういう結果になるかは、処理系定義です。

たとえば、ファイルがオープンされていると、ファイルは削除はできるかもしれませんし、できないかもしれません。基本的には、ファイルがクローズされている状態で、削除を行うようにするのがトラブルが少ないはずです。

ファイルを削除する方法については、「逆引き ファイルを削除する」でも取り上げています。

ファイル名を変更する

rename関数を使うと、ファイルの名前を変更できます。rename関数は、<stdio.h> に以下のように宣言されています。

int rename(const char* old, const char* new);

第1引数に対象ファイルの名前(これが変更前の名前)を指定し、第2引数に新しい名前を指定します。

戻り値は、成功すれば 0 が、失敗すると 0以外が返されます。

新しい名前をもったファイルがすでに存在していた場合の動作は、処理系定義です。

ファイル名を変更する方法については、「逆引き ファイルの名前を変更する」でも取り上げています。

ファイルの移動

ファイルを、別のディレクトリへ移動させるような操作は、実は、前の項で紹介した rename関数で行えます。第2引数に、移動先のディレクトリ名を含めて、パスを指定します。

if (rename("test.txt", "other_dir/test.txt") != 0) {
    // 失敗
}

ただし、これが成功するかどうかは、環境に依存するかもしれません。

【上級】たとえば、移動元と移動先とで、使用しているファイルシステムが異なる場合、そのための変換が必要となるため、失敗することがあるようです。

別の手段としては、ファイルのコピーのような方法で、移動先にファイルの複製を作り、移動元のファイルを remove関数で削除する方法があります。

ファイルを移動する方法については、「逆引き ファイルを移動する」でも取り上げています。


ファイルのコピー

ファイルのコピーを簡単に行える標準ライブラリ関数はありません。そのため、C言語の標準機能だけで実現しようと思うと、ファイルを読み取り用にオープンし、書き込み用にオープンされた別のファイルへ1文字ずつ複写していくしかありません。

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

int main(void)
{
    const char* error_msg = "";


    FILE* fp_src = fopen("test.txt", "rb");
    if (fp_src == NULL) {
        error_msg = "コピー元のファイルオープンに失敗しました。";
        goto error;
    }
    FILE* fp_dest = fopen("test_copy.txt", "wb");
    if (fp_dest == NULL) {
        error_msg = "コピー先のファイルオープンに失敗しました。";
        goto error;
    }

    while (1) {
        // バイナリデータとして 1バイトずつ読み込み、1バイトずつ書き込む

        char c;

        if (fread(&c, sizeof(c), 1, fp_src) < 1) {
            if (feof(fp_src)) {
                break;
            }
            else {
                error_msg = "読み取り中にエラーが発生しました。";
                goto error;
            }
        }

        if (fwrite(&c, sizeof(c), 1, fp_dest) < 1) {
            error_msg = "書き込み中にエラーが発生しました。";
            goto error;
        }
    }

    if (fclose(fp_dest) == EOF) {
        error_msg = "コピー先のファイルクローズに失敗しました。";
        goto error;
    }
    if (fclose(fp_src) == EOF) {
        error_msg = "コピー元のファイルクローズに失敗しました。";
        goto error;
    }

    return 0;

error:
    fprintf(stderr, "%s\n", error_msg);
    exit(EXIT_FAILURE);
}

入力ファイル (test.txt)

test.txt

テストファイル

出力ファイル (test_copy.txt)

test.txt

テストファイル

実行結果

どんな種類のファイルであってもコピーできるようにするためには、バイナリモードで扱うことが必要です。そうでないと、偶然含まれていた改行文字と同じ値のバイトが、勝手に変換されてしまう可能性があります(第42章参照

なお、この実装は相当に非効率で遅い可能性があります。バッファリングされていれば、実際の読み書きが極端に遅くなることはないかもしれませんが、少なくとも、fread関数や fwrite関数を1回呼び出すごとにエラーチェックを挟んでいるため、この部分の遅さは否めません。

速度を求めるのなら、たとえば、ある程度のバイト数をまとめて扱うように、自前のバッファリングも行うと良いかもしれません。

ファイルのコピーを実装する方法については、「逆引き ファイルをコピーする」でも取り上げています。

ファイルの存在を調べる

ある名前を持ったファイルが、実際に存在しているかどうかを調べたいことがあります。ファイルの存在を確認するための標準ライブラリ関数はありませんが、fopen関数で代用する手法がよく使われます。

#include <stdio.h>

int main(void)
{
    FILE* fp = fopen("test.txt", "r");
    if (fp == NULL) {
        puts("存在しない。");
    }
    else {
        puts("存在する。");
        fclose(fp);  // 忘れずにクローズすること
    }
}

実行結果

存在する。

fopen関数は、“r” 系のオープンモードでオープンしようとして失敗すると、ヌルポインタを返すので、これを利用している訳です。ヌルポインタ以外が返された場合には、正常にオープンできたということなので、ファイルは間違いなく存在しています。

しかしこの方法では、ファイルが存在しないという理由以外でヌルポインタを返す可能性があるため、正確であるとは言えません。たとえば、指定したファイルの読み取りアクセスが許可されていないような状況下でもヌルポインタを返すかもしれません。

これが問題なのであれば、この方法は使えませんが、その場合、自分の環境に特化した汎用性の低い方法を探すしかありません。方法のいくつかを、「逆引き ファイルの存在を確認する」で取り上げているので、そちらを参照してください。

ただ、いずれにしても、ファイルの存在を調べる行為自体が、本当に意味があることなのか検討するべきです。存在を調べた後、その結果に応じて何らかの操作を行うのだと思いますが、存在を調べて、次の処理を行うまでのあいだに、そのファイルが削除されたり、作られたりする可能性はないのでしょうか? そういうことが起こり得るのなら、存在を調べても意味がありません。

ファイルのサイズを調べる

ファイルのサイズを直接的に調べられるような標準ライブラリ関数はありません。しかし、標準ライブラリ関数を組み合わせて実現できる可能性はあります。

ファイルをバイナリファイルとしてオープンし、ファイルの末尾までシークし、その位置のファイルポジションを読み取るという方法です。この方法はすでに、第42章で紹介しています。ただし、そのとき書いたとおり、この方法は環境依存な部分を含んでいます。

ファイルのサイズを取得する方法については、「逆引き ファイルサイズを取得する」でも取り上げています。


system関数の利用

C言語の標準ライブラリ関数だけでは不可能なことでも、実行環境の助けを借りればできることもあります。

標準ライブラリ関数の system関数を使うと、コマンドプロセッサへコマンド文字列を渡して、処理を行わせることができます。

コマンドプロセッサ (command processor) とは、コンソールアプリケーションを実行するプログラムのことです。コマンドプロセッサには、いくつかコマンドが用意されており、そのコマンドを使ってさまざまな処理が行えます。コマンドの種類や使い方は環境によって異なります。

system関数は、<stdlib.h> に次のように宣言されています。

int system(const char* s);

引数に、コマンドプロセッサに渡す文字列を指定します。戻り値の意味は、環境によって異なります。

Windows の場合

Windows環境では、system関数を使って、コマンドプロンプト (command prompt) へ文字列を渡して、実行させることができます。

コマンドプロンプトでは、DOSコマンド (DOS command) と呼ばれる一連のコマンドが使用できます。このコマンドの中には、ファイル操作に関わるものも数多くあります。たとえば、system関数を通して、DOSコマンドを使うことによって、ファイルのコピーを実現する例は、次のようになります。

#include <stdlib.h>

int main(void)
{
    system("copy /B test.txt test_copy.txt");
}

入力ファイル (test.txt)

test.txt

テストファイル

出力ファイル (test_copy.txt)

test.txt

テストファイル

実行結果

        1 個のファイルをコピーしました。

“copy /B test.txt test_copy.txt” というコマンド文字列が、コマンドプロンプトに渡されて実行されます。

これは copyコマンドを実行させており、test.txt を test_copy.txt へコピーします。/B はオプション指定で、バイナリファイルとして扱うことを意味しています。

macOS の場合

macOS では、system関数を使って、ターミナル (terminal) へ文字列を渡して、実行させることができます。

macOS では、UNIXコマンドを使えますから、system関数で UNIXコマンドを渡して実行してもらえば、ファイルコピーを実現できます。

#include <stdlib.h>

int main(void)
{
    system("cp test.txt test_copy.txt");
}

入力ファイル (test.txt)

test.txt

テストファイル

出力ファイル (test_copy.txt)

test.txt

テストファイル

実行結果

“cp test.txt test_copy.txt” というコマンド文字列が、ターミナルに渡されて実行されます。cp がコマンドの名前で、copy の略です。これで test.txt を test_copy.txt にコピーできます。

system関数を使う方法は、C言語の機能だけでは実現が難しいことができる可能性があるため、非常に楽ができるかもしれません。その一方で、この方法は環境に依存しており、移植性が低い方法です。system関数自体は標準ライブラリ関数なので確実に使えますが、コマンド文字列は環境に応じて変えないといけません。


練習問題

問題① remove関数を試してみてください。また、この関数は自分の使っている環境では、どんなときに失敗するでしょう?

問題② C言語の標準ライブラリ関数には、ディレクトリを操作するものはありません。自分の使っている環境にある非標準の機能にはあるでしょうか? たとえば、ディレクトリを作成する方法を調べてみてください。


解答ページはこちら

参考リンク


更新履歴

≪さらに古い更新履歴を展開する≫



前の章へ (第43章 バッファリング)

次の章へ (第45章 コマンドライン)

C言語編のトップページへ

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る