閉じる

C言語でファイル名を変更する(rename)の正しい使い方

ファイル名の変更は、小さなツールから本格的なアプリケーションまで幅広く必要になります。

本稿ではC言語のrename関数を使ってファイル名を変更する方法を、基本からエラー処理、WindowsとLinuxの違いまで丁寧に解説します。

初心者がつまずきやすい落とし穴や安全に使うコツもあわせて説明します。

renameでファイル名を変更する基本

renameの意味と用途

renameは、ファイルまたはディレクトリのパス名を変更する関数です。

既存のパスoldpathを新しいパスnewpathへ付け替えます。

単なる「名前変更」に加えて、同一ファイルシステム内であればディレクトリ間の移動も可能です。

  • 例1: "report.txt""report_2025.txt" (同じディレクトリ内の名前変更)
  • 例2: "work/report.txt""archive/2025/report.txt" (移動兼変更)
注意

WindowsとPOSIX(LinuxやmacOS)では「宛先が既に存在する場合」の挙動が異なります

詳細は後述します。

必要なヘッダ

renamestdio.hで宣言されています。

エラー原因を確認するerrnoperrorを使う場合はerrno.hも読み込みます。

C言語
// 関数プロトタイプ
// int rename(const char *old, const char *new);
#include <stdio.h>  // rename, perror
#include <errno.h>  // errno

返り値と成功・失敗判定

rename成功時に0、失敗時に非0を返します。

失敗時の原因はerrnoに格納されます。

下表は基本的な返り値の意味です。

返り値意味
0成功しました
非0失敗しました。errnoで原因を確認します

よくあるerrnoの例を挙げます。

環境によって異なる点があるため目安として捉えてください。

errno代表的な意味の例
ENOENT古いパスが存在しない、またはディレクトリが見つからない
EACCES/EPERMアクセス権がない、Windowsではファイルが開いたまま等
EEXISTWindowsでは宛先が既に存在する場合に失敗することがある
EXDEV異なるファイルシステム間での移動は不可(POSIX)
ENOTEMPTY/EBUSYディレクトリのリネームで空でない、または使用中等
重要

POSIXでは宛先が存在しても上書き置換を行えますが、Windowsでは失敗します

移植性を意識したコードではこの差を吸収する処理が必要です。

renameの使い方

最小サンプルコード

最も基本的な流れは、ファイルを用意して閉じ、renameを呼び、戻り値を確認することです。

標準出力と標準エラー出力を使って結果をわかりやすく表示します。

C言語
#include <stdio.h>
#include <errno.h>

// 最小限のデモプログラム: sample.txt を renamed.txt に変更します
int main(void) {
    const char *oldpath = "sample.txt";
    const char *newpath = "renamed.txt";

    // 宛先が既にあるとWindowsで失敗しやすいので、事前に削除を試みます(存在しない場合は無視)
    remove(newpath);

    // テスト用のファイルを作成します
    FILE *fp = fopen(oldpath, "w");
    if (!fp) {
        perror("fopen");
        return 1;
    }
    // 内容を書いて標準ファイルにする(中身は何でもOK)
    fprintf(fp, "Hello, rename!\n");
    // 変更前に必ずクローズ(Windows互換性のために特に重要)
    fclose(fp);

    // いよいよrenameを呼びます
    if (rename(oldpath, newpath) != 0) {
        perror("rename");               // 標準エラー出力に原因を表示
        return 1;
    }

    printf("Renamed %s -> %s\n", oldpath, newpath);  // 標準出力に成功メッセージ
    return 0;
}
実行結果
Renamed sample.txt -> renamed.txt
ポイント

fcloseを確実に呼ぶことで、Windowsでもエラーになりにくくなります。

同じディレクトリでの名前変更

同じディレクトリ内の単純なリネームは最も一般的です。

呼び出し自体は1行で済み、戻り値で判定します。

C言語
#include <stdio.h>

int main(void) {
    if (rename("log.txt", "log_old.txt") != 0) {
        perror("rename");
        return 1;
    }
    puts("OK: log.txt -> log_old.txt");
    return 0;
}

この例はlog.txtが存在して初めて成功します。

存在しない場合はENOENTが想定されます。

別ディレクトリへの移動兼変更

同じファイルシステム内であれば、移動と名前変更を同時に行えます。

事前に宛先ディレクトリが存在することを確認してください。

C言語
#include <stdio.h>
#include <errno.h>

// work/data.txt を archive/2025/data_2025.txt に移動兼リネームします
// 事前に "work" と "archive/2025" ディレクトリを作成しておいてください
int main(void) {
    const char *src = "work/data.txt";
    const char *dst = "archive/2025/data_2025.txt";

    if (rename(src, dst) != 0) {
        perror("rename");  // ENOENT(パスのどこかが無い), EXDEV(別FS) 等を確認
        // 異なるファイルシステム(EXDEV)の場合、コピー+削除のフォールバックが必要です
        if (errno == EXDEV) {
            fprintf(stderr, "異なるファイルシステム間ではrenameできません。コピーしてから古い方を削除してください。\n");
        }
        return 1;
    }
    printf("Moved and renamed: %s -> %s\n", src, dst);
    return 0;
}
実行結果
Moved and renamed: work/data.txt -> archive/2025/data_2025.txt
補足

POSIXではrenameは同一ファイルシステム内で原子的に完了します。

異なるファイルシステム間ではEXDEVで失敗するため、コピーしてからremoveする手順が必要です。

相対パス/絶対パスの指定

相対パスは「現在の作業ディレクトリ」を起点に解釈され、絶対パスはルートからの完全な指定です。

例えばLinuxでは"/home/user/file.txt"、Windowsでは"C:/Users/user/file.txt"のように表します。

C言語
#include <stdio.h>

// 相対パスでも絶対パスでも使い方は同じです
int main(void) {
    // 相対パスの例
    if (rename("data/today.txt", "data/today_done.txt") != 0) {
        perror("rename (relative)");
    }

    // 絶対パスの例(環境に合わせて書き換えてください)
    // Linux/macOS例: "/home/you/in.txt" -> "/home/you/archive/in.txt"
    // Windows例: "C:/Users/you/in.txt" -> "C:/Users/you/archive/in.txt"
    if (rename("/path/to/in.txt", "/path/to/archive/in.txt") != 0) {
        perror("rename (absolute)");
    }
    return 0;
}
重要

作業ディレクトリがどこか分からない場合は、シェルのpwdcdコマンドで確認するか、プログラム内で現在ディレクトリを表示する仕組みを用意してください。

renameのエラー対処

戻り値チェックの書き方

戻り値が0でない場合は失敗なので、すぐにerrnoを参照します。

エラーが起きた直後に確認することが大切です。

C言語
#include <stdio.h>
#include <errno.h>

int safe_rename(const char *oldpath, const char *newpath) {
    if (rename(oldpath, newpath) != 0) {
        // ここですぐにerrnoを参照する
        int e = errno;
        fprintf(stderr, "rename failed: old=%s new=%s errno=%d\n", oldpath, newpath, e);
        perror("perror says"); // 人間に読みやすいメッセージを追記
        return -1;
    }
    return 0;
}

perrorで原因を表示

perrorerrnoに対応した簡易メッセージをstderrに出力します。

初心者には非常に便利です。

C言語
#include <stdio.h>

int main(void) {
    if (rename("no_such_file.txt", "x.txt") != 0) {
        perror("rename"); // 例: "rename: No such file or directory"
    }
    return 0;
}
実行結果
rename: No such file or directory
補足

メッセージ内容はOSやロケールにより異なります。

よくある失敗

初心者が遭遇しやすい失敗は、存在しないパスを指定している、宛先ディレクトリがない、アクセス権が不足している、そしてファイルを開いたままの状態でリネームしていることです。

特にWindowsでは、開いているファイルはrenameにより失敗しやすくEACCESEPERMが発生します。

C言語
#include <stdio.h>
#include <errno.h>

// Windowsでは "opened.txt" を開いたまま rename すると失敗する例
int main(void) {
    FILE *fp = fopen("opened.txt", "w");
    if (!fp) { perror("fopen"); return 1; }

    // ここでfcloseしないままrenameに挑戦
    if (rename("opened.txt", "opened_renamed.txt") != 0) {
        perror("rename while open");  // Windowsなら "Permission denied" 等
    }

    fclose(fp); // 後片付け
    return 0;
}

Linuxでは上記が成功することもありますが、移植性を考えると必ず閉じてから実行しましょう。

宛先が既にある場合の扱い

POSIXでは、宛先が既に存在する場合でも原子的な置換が可能です。

一方、Windowsでは失敗EEXISTEACCESが返ることがあります。

移植性を高めるためには、失敗時に限定して宛先を削除して再試行するフォールバックが有効です。

ただし、他プロセスとの競合がある環境では競合条件に注意してください。

C言語
#include <stdio.h>
#include <errno.h>

// 宛先が存在して失敗したら remove して再試行するフォールバック
// 危険性: 不意に宛先を消すことになるため、用途に応じて注意が必要
int rename_with_overwrite(const char *oldpath, const char *newpath) {
    if (rename(oldpath, newpath) == 0) return 0;

    if (errno == EEXIST || errno == EACCES) {
        // 宛先を消して再試行(Windows向けフォールバック)
        if (remove(newpath) == 0) {
            if (rename(oldpath, newpath) == 0) return 0;
        }
    }
    // ここに来るのは最終的に失敗
    perror("rename_with_overwrite");
    return -1;
}
重要

重要ファイルの上書きは取り返しがつかない場合があります。

本当に上書きしてよいかをアプリ側で確認し、必要ならバックアップを取るなど安全策を講じてください。

初心者向けの注意点

変更前に必ずクローズ

Windowsでは開いているファイルのrenameが失敗しやすいです。

書き込み後はfflushだけでなくfcloseまで行い、ハンドルを解放してから名前変更してください。

これは標準出力や標準エラー出力のような他のストリームでも同じで、整然としたリソース管理が品質に直結します。

WindowsとLinuxの違い

挙動の差を理解するとデバッグが楽になります。

代表的なものをまとめます。

観点POSIX(Linux/macOS等)Windows
宛先が既に存在上書き置換が可能失敗することが多い
開いたままのファイル多くの場合リネーム可能多くの場合失敗(EACCES/EPERM)
異なるFS間失敗(EXDEV)失敗の可能性(ドライブ間で不可)
原子性あり(同一FS内)あり(条件やAPIに依存)
Unicodeパスマルチバイト設定などで注意_wrename等のワイド版推奨
補足

Windowsで日本語パスを安全に扱うには、wchar_t_wrenameの利用が有効です。

C言語
// Windows専用: Unicodeパスのリネーム
// コンパイル例: cl /utf-8 sample.c
#ifdef _WIN32
#include <wchar.h>
#include <stdio.h>

int main(void) {
    const wchar_t *oldp = L"レポート.txt";
    const wchar_t *newp = L"レポート_保存.txt";
    if (_wrename(oldp, newp) != 0) {
        perror("_wrename");
        return 1;
    }
    puts("OK: ワイド文字パスでのリネーム");
    return 0;
}
#endif
実行結果
OK: ワイド文字パスでのリネーム

安全に使う小さなコツ

安全性とユーザー体験を高めるための実践的な工夫をいくつか紹介します。

まず「一時ファイルに書いてからrenameで最終名にする」パターンは、途中状態を見せずに更新を原子的に切り替えられるため非常に有効です。

これにより、プロセスが途中で落ちても壊れた中間ファイルを露出しにくくなります。

C言語
#include <stdio.h>
#include <errno.h>

// tmp -> final を rename で切り替える安全更新パターン(同一ディレクトリ内を推奨)
int write_safely(const char *final) {
    char tmp[512];
    snprintf(tmp, sizeof(tmp), "%s.tmp", final);

    FILE *fp = fopen(tmp, "w");
    if (!fp) { perror("fopen tmp"); return -1; }

    // 新しい内容を書き込む
    fprintf(fp, "new content\n");

    // データを確実にディスクへ(必要に応じて明示的なfsync等を検討: 環境依存)
    if (fclose(fp) != 0) { perror("fclose tmp"); return -1; }

    // 最後に原子的に切り替え
    if (rename(tmp, final) != 0) {
        perror("rename tmp->final");
        // 失敗したtmpは後でクリーンアップ
        remove(tmp);
        return -1;
    }
    return 0;
}

また、相対パスは実行場所によって意味が変わるため、バッチやサービスで動かす場合は絶対パスを使うと安全です。

さらに、エラー時のメッセージをstderrに十分な文脈付きで出すと、将来の自分が助かります。

まとめ

C言語のrenameは、ファイル名の変更や同一ファイルシステム内の移動を簡潔かつ高速に実現する基本APIです。

使い方自体はシンプルですが、WindowsとPOSIXでの挙動差開いたままのファイルを扱わないこと、戻り値とerrnoで確実にエラー判定することが実用上の要点です。

上書きの扱いは特に差が大きいため、必要に応じて宛先削除のフォールバックや一時ファイル方式を取り入れて移植性と安全性を確保してください。

標準出力と標準エラー出力を活用しながら、堅牢で読みやすいコードを目指していきましょう。

この記事を書いた人
エーテリア編集部
エーテリア編集部

プログラミングの基礎をしっかり学びたい方向けに、C言語の基本文法から解説しています。ポインタやメモリ管理も少しずつ理解できるよう工夫しています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!