ファイル名の変更は、小さなツールから本格的なアプリケーションまで幅広く必要になります。
本稿では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)では「宛先が既に存在する場合」の挙動が異なります。
詳細は後述します。
必要なヘッダ
rename
はstdio.h
で宣言されています。
エラー原因を確認するerrno
やperror
を使う場合はerrno.h
も読み込みます。
// 関数プロトタイプ
// 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ではファイルが開いたまま等 |
EEXIST | Windowsでは宛先が既に存在する場合に失敗することがある |
EXDEV | 異なるファイルシステム間での移動は不可(POSIX) |
ENOTEMPTY/EBUSY | ディレクトリのリネームで空でない、または使用中等 |
POSIXでは宛先が存在しても上書き置換を行えますが、Windowsでは失敗します。
移植性を意識したコードではこの差を吸収する処理が必要です。
renameの使い方
最小サンプルコード
最も基本的な流れは、ファイルを用意して閉じ、rename
を呼び、戻り値を確認することです。
標準出力と標準エラー出力を使って結果をわかりやすく表示します。
#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行で済み、戻り値で判定します。
#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
が想定されます。
別ディレクトリへの移動兼変更
同じファイルシステム内であれば、移動と名前変更を同時に行えます。
事前に宛先ディレクトリが存在することを確認してください。
#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"
のように表します。
#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;
}
作業ディレクトリがどこか分からない場合は、シェルのpwd
やcd
コマンドで確認するか、プログラム内で現在ディレクトリを表示する仕組みを用意してください。
renameのエラー対処
戻り値チェックの書き方
戻り値が0でない場合は失敗なので、すぐにerrno
を参照します。
エラーが起きた直後に確認することが大切です。
#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で原因を表示
perror
はerrno
に対応した簡易メッセージをstderr
に出力します。
初心者には非常に便利です。
#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
により失敗しやすくEACCES
やEPERM
が発生します。
#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では失敗しEEXIST
やEACCES
が返ることがあります。
移植性を高めるためには、失敗時に限定して宛先を削除して再試行するフォールバックが有効です。
ただし、他プロセスとの競合がある環境では競合条件に注意してください。
#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
の利用が有効です。
// 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
で最終名にする」パターンは、途中状態を見せずに更新を原子的に切り替えられるため非常に有効です。
これにより、プロセスが途中で落ちても壊れた中間ファイルを露出しにくくなります。
#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
で確実にエラー判定することが実用上の要点です。
上書きの扱いは特に差が大きいため、必要に応じて宛先削除のフォールバックや一時ファイル方式を取り入れて移植性と安全性を確保してください。
標準出力と標準エラー出力を活用しながら、堅牢で読みやすいコードを目指していきましょう。