C言語編 逆引き ファイルをコピーする

先頭へ戻る

目的

ファイルをコピーしたいとします。

方法①(1バイトずつコピーする処理を自作する)[C89~]

残念ながら、C言語の標準ライブラリには、ファイルをコピーする関数はありません。 そこで、コピー元ファイルとコピー先ファイルをそれぞれ開き、 1バイトずつ読み込み、書き込むという動作を繰り返すように実装します。

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

/*
    ファイルをコピーする。

    srcPath:  コピー元ファイルのパス
    destPath: コピー先ファイルのパス
    戻り値:   成功したら 0以外、失敗したら 0
*/
static int copyFile(const char* srcPath, const char* destPath)
{
    FILE* fpSrc;
    FILE* fpDest;
    int result = 1;

    /* コピー元とコピー先が同じ */
    if (strcmp(srcPath, destPath) == 0) {
        return 0;
    }

    /* バイナリモードで開く */
    fpSrc = fopen(srcPath, "rb");
    fpDest = fopen(destPath, "wb");
    if (fpSrc == NULL || fpDest == NULL) {
        result = 0;
    }

    if (result != 0) {
        for (;;) {
            char c;

            /* 1バイト読み込む */
            if (fread(&c, sizeof(c), 1, fpSrc) < 1) {
                if (feof(fpSrc)) {
                    break;
                }
                else {
                    result = 0;
                    break;
                }
            }

            /* 1バイト書き込む */
            if (fwrite(&c, sizeof(c), 1, fpDest) < 1) {
                result = 0;
                break;
            }
        }
    }

    if (fpDest != NULL) {
        if (fclose(fpDest) == EOF) {
            result = 0;
        }
    }
    if (fpSrc != NULL) {
        if (fclose(fpSrc) == EOF) {
            result = 0;
        }
    }

    return result;
}

int main(void)
{
    if (copyFile("test.txt", "test_copy.txt")) {
        puts("コピーしました。");
    }
    else {
        puts("コピーに失敗しました。");
    }

    return 0;
}

実行結果:

コピーしました。

1行ずつ読み込むような実装だと、十分な大きさのバッファが必要になりますが、 どんなファイルか分からない場合、「十分な大きさ」を決定することができません。 そのため、1バイトずつ読み込む必要があります。

また、Windows では、fopen関数の第2引数に "b" を加えてバイナリモードにしないと、 バイナリファイルのコピーの際に、改行文字と一致する値が含まれていたときに、 改行文字としての扱いを受けて、変換されてしまいます。

なお、この実装では、コピー先のファイルが既に存在していると、上書きされることに注意して下さい。
ファイルが既に存在していた場合にコピーを行わないようにすることは可能ですが、 その場合、同じプログラムを繰り返し実行すると、2度目以降、コピーが行えなくなります。 ファイルの自動バックアップのような用途では、これは好ましい動作では無いでしょう。
一方で、偶然、コピー後のファイルと同じ名前の無関係なファイルが無いとも限りません。 この場合は、上書きされてしまう方が好ましくない動作かも知れません。

この実装は、C言語の標準機能だけで実装できますが、 各環境が用意している機能を使った方が、 その環境特有の事情を考慮した実装がなされているであろうことから、 より確実かも知れません。

方法②(CopyFile関数を使う)[Windows]

Windows API には、ファイルのコピーを行う CopyFile関数(⇒MSDN)が用意されています。 また、コピー途中でキャンセルする等、より高度な機能を備えて CopyFileEx関数(⇒MSDN)があります。 ここでは CopyFile関数を取り上げます。

#include <stdio.h>
#include <Windows.h>

/*
    ファイルをコピーする。

    srcPath:  コピー元ファイルのパス
    destPath: コピー先ファイルのパス
    戻り値:   成功したら 0以外、失敗したら 0
*/
static int copyFile(const char* srcPath, const char* destPath)
{
    return CopyFileA(srcPath, destPath, FALSE);
}

int main(void)
{
    if (copyFile("test.txt", "test_copy.txt")) {
        puts("コピーしました。");
    }
    else {
        puts("コピーに失敗しました。");
    }

    return 0;
}

実行結果:

コピーしました。

CopyFile関数を使用するには、Windows.h のインクルードが必要です。 また、CopyFile は実際にはマクロになっていて、UNICODEマクロの定義の有無によって、 ANSI版(char型)の CopyFileA と、Unicode版(wchar_t型)の CopyFileW のいずれかに置換されます。 上のサンプルプログラムでは、文字列を const char* で扱っているため、 CopyFileA の方を直接呼び出すようにしています。

CopyFile関数の第3引数は、コピー先のファイルが既に存在していた場合に、 エラーとみなすか、上書きするかを選択するものです。 TRUE を指定するとエラーになり、FALSE を指定すると上書きされます。


参考リンク

更新履歴

'2018/3/19 「方法①」の実装で、fread関数や fwrite関数のエラーチェックの方法を修正 (ferror関数ではエラーを補足できない。戻り値を調べるべき)

'2018/2/22 「サイズ」という表記について表現を統一。 型のサイズ(バイト数)を表しているところは「大きさ」、要素数を表しているところは「要素数」。

'2018/2/21 文章中の表記を統一(bit、Byte -> ビット、バイト)

'2017/6/17 新規作成。


このエントリーをはてなブックマークに追加
rss1.0 取得ボタン RSS

逆引きのトップページへ

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

Programming Place Plus のトップページへ