C言語基礎|ファイルのコピー

「読むだけから一歩前へ。読んだ内容を“別のファイルへ書く”と、コピーが作れる!」

ファイルの中身を表示できるようになると、次にやりたくなるのが「その内容を別の場所へ出したい」ということです。
画面ではなく別のファイルへ出力すれば、それはもう立派な“コピー”になります。

ファイルコピーは、ログのバックアップ、設定ファイルの複製、データ変換の前処理など、いろいろな場面で使う基本技術です。
この記事では、コピー元を読み取り、コピー先へ書き込むという2ストリーム構成を、やさしく整理しながら理解していきます。

まず全体像:コピーは「入力ストリーム → 出力ストリーム」

図:コピーの流れ(1文字ずつ)

コピー元ファイル
   ↓ fopen r
入力ストリーム sfp
   ↓ fgetc(1文字読む)
文字 ch(int)
   ↓ fputc(1文字書く)
出力ストリーム dfp
   ↓ fclose
コピー先ファイル

図の説明

  • 読む側(sfp)と書く側(dfp)の2つのストリームが登場します。
  • fgetc が読む、fputc が書く、という“左右対称”な構造です。
  • EOF が来たら読み取り終了=コピー完了です。

サンプルプログラム

コマンドライン引数でコピー元・コピー先を受け取るプログラム例です。

プロジェクト名:chap13-9-1 ソースファイル名:chap13-9-1.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

Windows の Visual Studio が実行環境の場合、以下のパスにコピー元ファイルを配置します。
C:\Users\<ユーザー名>\source\repos\chap13-9-1\chap13-9-1

コマンドライン引数を指定してプログラムを実行・デバッグするには、プロジェクトのプロパティ設定で引数を指定します。

1.プロパティ画面の左側のメニューから「構成プロパティ」→「デバッグ」を選択します。
2.右側の画面にある「コマンド引数」(またはCommand-line Arguments)の項目に、渡したい引数を入力します。

#include <stdio.h>

int main(int argc, char *argv[])
{
    /* 引数チェック:copy <src> <dst> */
    if (argc != 3) {
        printf("Usage: copy <source> <destination>\n");
        return 1;
    }

    /* コピー元を読み取りモードで開く */
    FILE *sfp = fopen(argv[1], "r");
    if (sfp == NULL) {
        printf("ERROR: Cannot open source file.\n");
        return 1;
    }

    /* コピー先を書き込みモードで開く(存在すれば中身は切り捨て) */
    FILE *dfp = fopen(argv[2], "w");
    if (dfp == NULL) {
        printf("ERROR: Cannot open destination file.\n");
        fclose(sfp);  /* 先に開いたコピー元を閉じる */
        return 1;
    }

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

    /* 1文字ずつ読み取り、1文字ずつ書き込む */
    int ch;
    while ((ch = fgetc(sfp)) != EOF) {
        if (fputc(ch, dfp) == EOF) {
            printf("ERROR: Write failed.\n");
            fclose(dfp);
            fclose(sfp);
            return 1;
        }
    }

    fclose(dfp);  /* コピー先をクローズ */
    fclose(sfp);  /* コピー元をクローズ */

    printf("Done.\n");
    return 0;
}

重要ポイント:2つのファイルを扱うので失敗パターンが増える

コピー処理はファイルが2つあるぶん、失敗の分岐も増えます。ここを整理しておくと、バグが減ります。

よくある失敗と対処

起こること原因の例どうする?
コピー元が開けないファイルが存在しない、権限がないsfp が NULL なら終了
コピー先が開けない書き込み権限がない、ディレクトリが不正dfp が NULL なら sfp を閉じて終了
書き込みに失敗するディスク容量不足、権限問題fputc が EOF ならエラー終了
読み取り終了ファイル終端fgetc が EOF でループ終了

表の説明

  • 特に大事なのは「途中で失敗したときに、先に開いた方を閉じる」ことです。
  • これを怠ると、ファイルが閉じられずに残ってしまうことがあります。

fgetc と fputc の関係を押さえよう

ファイル表示では fgetc と putchar の組み合わせでした。
コピーでは、出力先が画面ではなくファイルなので fputc が登場します。

表示とコピーの差し替え

やりたいこと読む関数書く関数書き先
画面に表示fgetc(sfp)putchar(ch)stdout
ファイルにコピーfgetc(sfp)fputc(ch, dfp)dfp

表の説明

  • “読む部分”は同じで、“書く先”だけが変わっています。
  • ここが理解できると、コピーに加工処理(大文字変換など)を挟むのも簡単になります。

fputc の書式と戻り値

fputc の書式

項目内容
ヘッダ#include <stdio.h>
形式int fputc(int c, FILE *stream);
役割stream へ1文字書き込む
成功時書き込んだ文字(int)
失敗時EOF

表の説明

  • fputc の返り値チェックを入れると「書き込み失敗」を確実に検出できます。
  • コピーは「読み込み成功=書き込み成功」とは限らないので、ここは実用上かなり大事です。

どうして ch は int なの?

ファイル表示でも同じでしたが、コピーでも ch を int にします。

int を使う理由

文字:0〜255(unsigned char相当)
EOF :-1 など(特別な値)
→ 両方を表すために int が必要

図の説明

  • char に入れてしまうと EOF と区別できなくなる可能性があります。
  • そのため、fgetc の返り値を受ける変数は int が基本です。

コピー先を w で開くとどうなる?

w で開くと、コピー先ファイルが存在する場合は中身が消えて新しく書き直しになります。

モード w の性質(テキスト)

モード動き便利な点注意点
w新規作成または中身を切り捨て常に新しいコピーを作れる既存データは消える

表の説明

  • 「上書きコピー」の動作としては自然です。
  • 追加したいなら a 系モードですが、コピー用途では w が基本です。

演習問題

ここからは、元の演習 13-8〜13-10 に対応する“類似”を3つ作ります。
どれもコピーの骨格は同じで、「出力先が増える」「途中で変換する」がテーマです。

演習13-8:画面にも表示しながらコピーする

コピー元を読み取り、コピー先へ書き込みつつ、同時に画面にも表示せよ。

解答例

プロジェクト名:chap13-9-2 ソースファイル名:chap13-9-2.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

Windows の Visual Studio が実行環境の場合、以下のパスにコピー元のファイルを配置します。
C:\Users\<ユーザー名>\source\repos\chap13-9-2\chap13-9-2

コマンドライン引数を指定してプログラムを実行・デバッグするには、プロジェクトのプロパティ設定で引数を指定します。

1.プロパティ画面の左側のメニューから「構成プロパティ」→「デバッグ」を選択します。
2.右側の画面にある「コマンド引数」(またはCommand-line Arguments)の項目に、渡したい引数を入力します。

#include <stdio.h>

int main(int argc, char *argv[])
{
    if (argc != 3) {
        printf("Usage: copyview <source> <destination>\n");
        return 1;
    }

    FILE *sfp = fopen(argv[1], "r");
    if (sfp == NULL) {
        printf("ERROR: Cannot open source file.\n");
        return 1;
    }

    FILE *dfp = fopen(argv[2], "w");
    if (dfp == NULL) {
        printf("ERROR: Cannot open destination file.\n");
        fclose(sfp);
        return 1;
    }

    printf("Copying and displaying...\n");

    int ch;
    while ((ch = fgetc(sfp)) != EOF) {
        putchar(ch);                 /* 画面にも出す */
        if (fputc(ch, dfp) == EOF) { /* ファイルにも書く */
            printf("\nERROR: Write failed.\n");
            fclose(dfp);
            fclose(sfp);
            return 1;
        }
    }

    fclose(dfp);
    fclose(sfp);

    printf("\nDone.\n");
    return 0;
}

解説

  • 出力先が増えるだけで、コピーの骨格は同じです。
  • putchar と fputc を並べるだけで実現できます。

演習13-9:英小文字を英大文字に変換してコピーする

コピーしながら、a〜z を A〜Z に変換して保存せよ。

解答例

プロジェクト名:chap13-9-3 ソースファイル名:chap13-9-3.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

Windows の Visual Studio が実行環境の場合、以下のパスにコピー元のファイルを配置します。
C:\Users\<ユーザー名>\source\repos\chap13-9-3\chap13-9-3

コマンドライン引数を指定してプログラムを実行・デバッグするには、プロジェクトのプロパティ設定で引数を指定します。

1.プロパティ画面の左側のメニューから「構成プロパティ」→「デバッグ」を選択します。
2.右側の画面にある「コマンド引数」(またはCommand-line Arguments)の項目に、渡したい引数を入力します。

#include <stdio.h>

int main(int argc, char *argv[])
{
    if (argc != 3) {
        printf("Usage: uppercopy <source> <destination>\n");
        return 1;
    }

    FILE *sfp = fopen(argv[1], "r");
    if (sfp == NULL) {
        printf("ERROR: Cannot open source file.\n");
        return 1;
    }

    FILE *dfp = fopen(argv[2], "w");
    if (dfp == NULL) {
        printf("ERROR: Cannot open destination file.\n");
        fclose(sfp);
        return 1;
    }

    int ch;
    while ((ch = fgetc(sfp)) != EOF) {
        /* a〜z を A〜Z に変換する */
        if (ch >= 'a' && ch <= 'z') {
            ch = ch - ('a' - 'A');
        }

        if (fputc(ch, dfp) == EOF) {
            printf("ERROR: Write failed.\n");
            fclose(dfp);
            fclose(sfp);
            return 1;
        }
    }

    fclose(dfp);
    fclose(sfp);

    printf("Done.\n");
    return 0;
}

解説

  • コピー処理の途中に「変換」を挟むだけです。
  • 文字の範囲チェック(a〜z)を入れるのがポイントです。

演習13-10:英大文字を英小文字に変換してコピーする

コピーしながら、A〜Z を a〜z に変換して保存せよ。

解答例

プロジェクト名:chap13-9-4 ソースファイル名:chap13-9-4.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

Windows の Visual Studio が実行環境の場合、以下のパスに「memo.txt」が作成されます。
C:\Users\<ユーザー名>\source\repos\chap13-9-4\chap13-9-4

コマンドライン引数を指定してプログラムを実行・デバッグするには、プロジェクトのプロパティ設定で引数を指定します。

1.プロパティ画面の左側のメニューから「構成プロパティ」→「デバッグ」を選択します。
2.右側の画面にある「コマンド引数」(またはCommand-line Arguments)の項目に、渡したい引数を入力します。

#include <stdio.h>

int main(int argc, char *argv[])
{
    if (argc != 3) {
        printf("Usage: lowercopy <source> <destination>\n");
        return 1;
    }

    FILE *sfp = fopen(argv[1], "r");
    if (sfp == NULL) {
        printf("ERROR: Cannot open source file.\n");
        return 1;
    }

    FILE *dfp = fopen(argv[2], "w");
    if (dfp == NULL) {
        printf("ERROR: Cannot open destination file.\n");
        fclose(sfp);
        return 1;
    }

    int ch;
    while ((ch = fgetc(sfp)) != EOF) {
        /* A〜Z を a〜z に変換する */
        if (ch >= 'A' && ch <= 'Z') {
            ch = ch + ('a' - 'A');
        }

        if (fputc(ch, dfp) == EOF) {
            printf("ERROR: Write failed.\n");
            fclose(dfp);
            fclose(sfp);
            return 1;
        }
    }

    fclose(dfp);
    fclose(sfp);

    printf("Done.\n");
    return 0;
}

解説

  • 大文字の範囲(A〜Z)だけを変換します。
  • 変換式は uppercopy と対称で覚えやすいです。