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

トップページC言語編逆引き

このページの概要 🔗

以下は目次です。

目的 🔗

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

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

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

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

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

    src_path:  コピー元ファイルのパス
    dest_path: コピー先ファイルのパス
    戻り値:   成功したら 0以外、失敗したら 0
*/
int copy_file(const char* src_path, const char* dest_path)
{
    // コピー元とコピー先が同じ
    if (strcmp(src_path, dest_path) == 0) {
        return 0;
    }

    int result = 1;

    // バイナリモードで開く
    FILE* fp_src = fopen(src_path, "rb");
    FILE* fp_dest = fopen(dest_path, "wb");
    if (fp_src == NULL || fp_dest == NULL) {
        result = 0;
    }

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

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

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

    if (fp_dest != NULL) {
        if (fclose(fp_dest) == EOF) {
            result = 0;
        }
    }
    if (fp_src != NULL) {
        if (fclose(fp_src) == EOF) {
            result = 0;
        }
    }

    return result;
}

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

実行結果:

コピーしました。

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

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

なお、この実装では、コピー先のファイルがすでに存在していると、上書きされることに注意してください。ファイルがすでに存在していた場合にコピーを行わないようにすることは可能ですが、その場合、同じプログラムを繰り返し実行すると、2度目以降、コピーが行えなくなります。ファイルの自動バックアップのような用途では、これは好ましい動作ではないでしょう。

一方で、偶然、コピー後のファイルと同じ名前の無関係なファイルがないとも限りません。この場合は、上書きされてしまう方が好ましくない動作かもしれません。

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

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

Windows API には、ファイルのコピーを行う CopyFile関数(⇒[Microsoft Docs](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-copyfile)が用意されています。また、コピー途中でキャンセルする等、より高度な機能を備えて CopyFileEx関数(⇒Microsoft Docs)があります。ここでは CopyFile関数を取り上げます。

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

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

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

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

実行結果:

コピーしました。

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

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


参考リンク 🔗


更新履歴 🔗

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

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

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

 新規作成。



逆引きのトップページへ

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

Programming Place Plus のトップページへ



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