rename/renameat/renameat2系统调用及示例
Linux 文件重命名系统调用详解,Linux 提供了三种文件重命名系统调用:rename、renameat 和 renameat2。其中 rename 是最基础的文件重命名函数,renameat 支持相对路径操作,renameat2 则是最强大的版本,支持额外标志位控制(如不替换已存在文件、交换文件等)。这些函数成功时返回0,失败返回-1并设置errno。文章详细介绍了各函数的原型、参数、标志位选项、常见错误码以及关联函数,并提供了基础用法的示例代码,展示了如何创建测试文件、重命名文件以及覆盖已存在文件的操作过程。
1. 函数介绍
文件重命名是 Linux 系统中最常用的操作之一。可以把文件重命名想象成”文件的身份证号码变更”——文件的内容和属性都没有改变,只是改变了文件在文件系统中的”名字”或”位置”。
这三个函数都用于重命名文件或目录,但功能逐渐增强:
- rename: 最基础的重命名函数
- renameat: 支持相对路径的重命名函数
- renameat2: 最强大的重命名函数,支持额外选项
2. 函数原型
#include <stdio.h>
// 基础重命名
int rename(const char *oldpath, const char *newpath);
// 相对路径重命名
int renameat(int olddirfd, const char *oldpath,
int newdirfd, const char *newpath);
// 增强版重命名(需要定义 _GNU_SOURCE)
#define _GNU_SOURCE
#include <fcntl.h>
int renameat2(int olddirfd, const char *oldpath,
int newdirfd, const char *newpath, unsigned int flags);
3. 功能
rename
将 oldpath
指定的文件或目录重命名为 newpath
。
renameat
在指定目录描述符的上下文中重命名文件或目录,支持相对路径。
renameat2
增强版重命名,支持额外的标志控制选项。
4. 参数
rename 参数
- oldpath: 旧文件路径名
- newpath: 新文件路径名
renameat/renameat2 参数
- olddirfd: 旧文件路径的目录文件描述符
- oldpath: 旧文件路径名
- newdirfd: 新文件路径的目录文件描述符
- newpath: 新文件路径名
- flags: 控制重命名行为的标志位(仅 renameat2)
5. 标志位(renameat2 专用)
标志 | 值 | 说明 |
---|---|---|
RENAME_NOREPLACE | (1 << 0) | 不替换已存在的文件 |
RENAME_EXCHANGE | (1 << 1) | 交换两个文件 |
RENAME_WHITEOUT | (1 << 2) | 创建白名单条目(overlayfs 专用) |
6. 返回值
- 成功: 返回 0
- 失败: 返回 -1,并设置相应的 errno 错误码
7. 常见错误码
EACCES
: 权限不足EBUSY
: 文件正被使用EDQUOT
: 磁盘配额不足EEXIST
: 新文件已存在(当使用 RENAME_NOREPLACE 时)EINVAL
: 参数无效(如 flags 无效)EISDIR
: 试图用目录覆盖非目录ELOOP
: 符号链接循环ENOENT
: 文件或目录不存在ENOTDIR
: 路径组件不是目录ENOTEMPTY
: 目录非空EPERM
: 操作不被允许EROFS
: 文件系统只读EXDEV
: 跨文件系统移动(rename 不支持)
8. 相似函数或关联函数
- link/unlink: 创建/删除硬链接
- symlink: 创建符号链接
- mv: 命令行重命名工具
- stat: 获取文件状态
- access: 检查文件访问权限
- chown/chmod: 修改文件所有者和权限
9. 示例代码
示例1:基础用法 – rename 函数
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
// 创建测试文件
int create_test_file(const char *filename, const char *content) {
int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("创建文件失败");
return -1;
}
if (content) {
write(fd, content, strlen(content));
}
close(fd);
printf("创建文件: %s\n", filename);
return 0;
}
// 显示文件内容
void show_file_content(const char *filename) {
int fd = open(filename, O_RDONLY);
if (fd == -1) {
printf("无法打开文件: %s\n", filename);
return;
}
char buffer[256];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
printf("文件 %s 内容: %s", filename, buffer);
} else {
printf("文件 %s 为空\n", filename);
}
close(fd);
}
// 检查文件是否存在
int file_exists(const char *filename) {
return access(filename, F_OK) == 0;
}
int main() {
printf("=== rename 基础示例 ===\n\n");
// 1. 创建测试文件
printf("1. 创建测试文件:\n");
if (create_test_file("old_name.txt", "这是原始文件的内容\n") == -1) {
return 1;
}
if (create_test_file("another_file.txt", "另一个文件的内容\n") == -1) {
unlink("old_name.txt");
return 1;
}
// 2. 显示原始文件内容
printf("\n2. 原始文件内容:\n");
show_file_content("old_name.txt");
show_file_content("another_file.txt");
// 3. 使用 rename 重命名文件
printf("\n3. 重命名文件:\n");
if (rename("old_name.txt", "new_name.txt") == 0) {
printf("✓ 成功将 'old_name.txt' 重命名为 'new_name.txt'\n");
// 验证重命名结果
if (file_exists("new_name.txt")) {
printf("✓ 新文件 'new_name.txt' 存在\n");
}
if (!file_exists("old_name.txt")) {
printf("✓ 旧文件 'old_name.txt' 已不存在\n");
}
// 显示重命名后的文件内容
printf("\n重命名后文件内容:\n");
show_file_content("new_name.txt");
} else {
printf("✗ 重命名失败: %s\n", strerror(errno));
}
// 4. 使用 rename 覆盖已存在的文件
printf("\n4. 覆盖已存在的文件:\n");
if (file_exists("another_file.txt")) {
printf("覆盖前 'another_file.txt' 存在\n");
if (rename("new_name.txt", "another_file.txt") == 0) {
printf("✓ 成功用 'new_name.txt' 覆盖 'another_file.txt'\n");
// 验证覆盖结果
if (file_exists("another_file.txt")) {
printf("✓ 'another_file.txt' 现在包含原始文件的内容\n");
show_file_content("another_file.txt");
}
if (!file_exists("new_name.txt")) {
printf("✓ 原来的 'new_name.txt' 已不存在\n");
}
} else {
printf("✗ 覆盖失败: %s\n", strerror(errno));
}
}
// 5. 尝试重命名不存在的文件
printf("\n5. 重命名不存在的文件:\n");
if (rename("nonexistent.txt", "should_fail.txt") == -1) {
printf("✓ 正确处理不存在的文件: %s\n", strerror(errno));
}
// 6. 清理测试文件
printf("\n6. 清理测试文件:\n");
if (file_exists("another_file.txt")) {
unlink("another_file.txt");
printf("✓ 删除 another_file.txt\n");
}
printf("\n=== rename 函数特点 ===\n");
printf("1. 原子操作: 重命名是原子的\n");
printf("2. 同一文件系统: 只能在同一文件系统内移动\n");
printf("3. 覆盖行为: 默认会覆盖已存在的文件\n");
printf("4. 权限保持: 文件权限和所有者保持不变\n");
printf("5. 简单易用: 最基础的文件重命名函数\n");
printf("6. 限制: 不支持相对路径和高级选项\n");
return 0;
}
示例2:增强功能 – renameat 函数
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>
// 创建目录
int create_directory(const char *dirname) {
if (mkdir(dirname, 0755) == -1) {
if (errno != EEXIST) {
perror("创建目录失败");
return -1;
}
}
printf("创建目录: %s\n", dirname);
return 0;
}
// 在目录中创建文件
int create_file_in_directory(const char *dirname, const char *filename,
const char *content) {
char full_path[512];
snprintf(full_path, sizeof(full_path), "%s/%s", dirname, filename);
int fd = open(full_path, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("创建文件失败");
return -1;
}
if (content) {
write(fd, content, strlen(content));
}
close(fd);
printf("在目录 '%s' 中创建文件: %s\n", dirname, filename);
return 0;
}
// 列出目录内容
void list_directory_contents(const char *dirname) {
DIR *dir = opendir(dirname);
if (!dir) {
printf("无法打开目录: %s\n", dirname);
return;
}
printf("目录 '%s' 的内容:\n", dirname);
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) {
printf(" %s\n", entry->d_name);
}
}
closedir(dir);
printf("\n");
}
int main() {
printf("=== renameat 增强功能示例 ===\n\n");
// 1. 创建测试目录和文件
printf("1. 创建测试环境:\n");
if (create_directory("source_dir") == -1) {
return 1;
}
if (create_directory("target_dir") == -1) {
rmdir("source_dir");
return 1;
}
// 在源目录中创建文件
if (create_file_in_directory("source_dir", "file1.txt", "源目录中的文件1\n") == -1 ||
create_file_in_directory("source_dir", "file2.txt", "源目录中的文件2\n") == -1) {
rmdir("source_dir");
rmdir("target_dir");
return 1;
}
// 在目标目录中创建文件
if (create_file_in_directory("target_dir", "existing.txt", "目标目录中的已有文件\n") == -1) {
// 清理并退出
unlink("source_dir/file1.txt");
unlink("source_dir/file2.txt");
rmdir("source_dir");
rmdir("target_dir");
return 1;
}
// 2. 显示初始状态
printf("\n2. 初始状态:\n");
list_directory_contents("source_dir");
list_directory_contents("target_dir");
// 3. 使用 renameat 移动文件(需要打开目录)
printf("3. 使用 renameat 移动文件:\n");
int source_fd = open("source_dir", O_RDONLY);
int target_fd = open("target_dir", O_RDONLY);
if (source_fd == -1 || target_fd == -1) {
perror("打开目录失败");
if (source_fd != -1) close(source_fd);
if (target_fd != -1) close(target_fd);
goto cleanup;
}
// 移动 file1.txt 从 source_dir 到 target_dir
if (renameat(source_fd, "file1.txt", target_fd, "moved_file1.txt") == 0) {
printf("✓ 成功移动文件 'file1.txt' -> 'moved_file1.txt'\n");
} else {
printf("✗ 移动文件失败: %s\n", strerror(errno));
}
// 使用 AT_FDCWD 移动 file2.txt
printf("\n4. 使用 AT_FDCWD 移动文件:\n");
if (renameat(AT_FDCWD, "source_dir/file2.txt", AT_FDCWD, "target_dir/moved_file2.txt") == 0) {
printf("✓ 成功移动文件 'source_dir/file2.txt' -> 'target_dir/moved_file2.txt'\n");
} else {
printf("✗ 移动文件失败: %s\n", strerror(errno));
}
close(source_fd);
close(target_fd);
// 5. 显示移动后的状态
printf("\n5. 移动后状态:\n");
list_directory_contents("source_dir");
list_directory_contents("target_dir");
// 6. 覆盖已存在的文件
printf("\n6. 覆盖已存在的文件:\n");
printf("尝试用 'source_dir/file1.txt' 覆盖 'target_dir/existing.txt'\n");
source_fd = open("source_dir", O_RDONLY);
target_fd = open("target_dir", O_RDONLY);
if (source_fd != -1 && target_fd != -1) {
if (renameat(source_fd, "file1.txt", target_fd, "existing.txt") == 0) {
printf("✓ 成功覆盖文件\n");
} else {
printf("✗ 覆盖失败: %s\n", strerror(errno));
}
close(source_fd);
close(target_fd);
}
// 7. 显示最终状态
printf("\n7. 最终状态:\n");
list_directory_contents("source_dir");
list_directory_contents("target_dir");
cleanup:
// 8. 清理测试文件
printf("\n8. 清理测试环境:\n");
// 删除 target_dir 中的文件
unlink("target_dir/moved_file1.txt");
unlink("target_dir/moved_file2.txt");
unlink("target_dir/existing.txt");
// 删除 source_dir 中的文件
unlink("source_dir/file1.txt");
unlink("source_dir/file2.txt");
// 删除目录
rmdir("source_dir");
rmdir("target_dir");
printf("✓ 清理完成\n");
printf("\n=== renameat 函数特点 ===\n");
printf("1. 相对路径支持: 支持相对于目录描述符的路径\n");
printf("2. 目录描述符: 可以使用已打开的目录文件描述符\n");
printf("3. AT_FDCWD: 可以使用当前工作目录\n");
printf("4. 原子操作: 重命名仍然是原子操作\n");
printf("5. 跨目录移动: 可以在不同目录间移动文件\n");
printf("6. 权限保持: 文件权限和所有者保持不变\n");
printf("\n");
printf("优势:\n");
printf("1. 更灵活的路径处理\n");
printf("2. 支持相对路径操作\n");
printf("3. 可以避免重复打开目录\n");
printf("4. 更好的错误处理\n");
return 0;
}
示例3:高级功能 – renameat2 函数
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
// 检查系统是否支持 renameat2
int check_renameat2_support() {
// 尝试一个简单的 renameat2 调用
int result = renameat2(AT_FDCWD, "test", AT_FDCWD, "test", 0);
if (result == -1 && errno == ENOSYS) {
return 0; // 不支持
}
return 1; // 支持(或者其他错误)
}
// 创建测试文件
int create_test_file_advanced(const char *filename, const char *content) {
int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("创建文件失败");
return -1;
}
if (content) {
write(fd, content, strlen(content));
}
close(fd);
printf("创建文件: %s\n", filename);
return 0;
}
// 显示文件详细信息
void show_file_details(const char *filename) {
struct stat st;
if (stat(filename, &st) == 0) {
printf("文件: %s\n", filename);
printf(" Inode: %ld\n", (long)st.st_ino);
printf(" 大小: %ld 字节\n", (long)st.st_size);
printf(" 权限: %o\n", st.st_mode & 0777);
printf(" 链接数: %ld\n", (long)st.st_nlink);
printf(" 修改时间: %s", ctime(&st.st_mtime));
} else {
printf("文件不存在: %s\n", filename);
}
}
int main() {
printf("=== renameat2 高级功能示例 ===\n\n");
// 检查系统支持
printf("1. 检查系统支持:\n");
if (!check_renameat2_support()) {
printf("⚠ 系统不支持 renameat2,使用模拟功能\n");
printf(" Linux 内核 3.15+ 才支持 renameat2\n\n");
} else {
printf("✓ 系统支持 renameat2\n\n");
}
// 2. 创建测试文件
printf("2. 创建测试文件:\n");
if (create_test_file_advanced("file_a.txt", "文件 A 的内容\n") == -1 ||
create_test_file_advanced("file_b.txt", "文件 B 的内容\n") == -1 ||
create_test_file_advanced("protected_file.txt", "受保护的文件内容\n") == -1) {
return 1;
}
// 3. 显示初始文件信息
printf("\n3. 初始文件信息:\n");
show_file_details("file_a.txt");
show_file_details("file_b.txt");
show_file_details("protected_file.txt");
// 4. 演示 RENAME_NOREPLACE 标志(不覆盖已存在的文件)
printf("\n4. 演示 RENAME_NOREPLACE (不覆盖):\n");
printf("尝试将 'file_a.txt' 重命名为 'protected_file.txt' (不覆盖):\n");
// 在支持的系统上使用 renameat2
int result = renameat2(AT_FDCWD, "file_a.txt", AT_FDCWD, "protected_file.txt",
RENAME_NOREPLACE);
if (result == -1) {
if (errno == EEXIST) {
printf("✓ 正确阻止了覆盖操作: 文件已存在\n");
} else if (errno == ENOSYS) {
printf("⚠ 系统不支持 renameat2,使用模拟方式\n");
// 检查文件是否存在,如果不存在则重命名
if (access("protected_file.txt", F_OK) == 0) {
printf(" 文件已存在,阻止覆盖\n");
} else {
if (rename("file_a.txt", "protected_file.txt") == 0) {
printf(" 成功重命名\n");
} else {
printf(" 重命名失败: %s\n", strerror(errno));
}
}
} else {
printf("✗ 操作失败: %s\n", strerror(errno));
}
} else {
printf("✓ 成功重命名,没有覆盖文件\n");
}
// 5. 演示普通的重命名(会覆盖)
printf("\n5. 演示普通重命名 (会覆盖):\n");
printf("将 'file_a.txt' 重命名为 'new_file_a.txt':\n");
if (rename("file_a.txt", "new_file_a.txt") == 0) {
printf("✓ 成功重命名\n");
show_file_details("new_file_a.txt");
} else {
printf("✗ 重命名失败: %s\n", strerror(errno));
}
// 6. 演示文件交换功能(RENAME_EXCHANGE)
printf("\n6. 演示文件交换功能:\n");
printf("交换 'new_file_a.txt' 和 'file_b.txt' 的内容:\n");
// 显示交换前的内容
printf("交换前:\n");
printf(" new_file_a.txt 内容: ");
{
int fd = open("new_file_a.txt", O_RDONLY);
if (fd != -1) {
char buffer[100];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
printf("%s", buffer);
}
close(fd);
}
}
printf(" file_b.txt 内容: ");
{
int fd = open("file_b.txt", O_RDONLY);
if (fd != -1) {
char buffer[100];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
printf("%s", buffer);
}
close(fd);
}
}
// 尝试使用 renameat2 交换文件
result = renameat2(AT_FDCWD, "new_file_a.txt", AT_FDCWD, "file_b.txt",
RENAME_EXCHANGE);
if (result == -1) {
if (errno == ENOSYS) {
printf("⚠ 系统不支持 RENAME_EXCHANGE,使用传统方式模拟\n");
printf(" 传统方式: 需要临时文件进行三次重命名\n");
// 创建临时文件名
if (rename("new_file_a.txt", "temp_swap_file.txt") == 0) {
if (rename("file_b.txt", "new_file_a.txt") == 0) {
if (rename("temp_swap_file.txt", "file_b.txt") == 0) {
printf("✓ 成功使用传统方式交换文件\n");
} else {
printf("✗ 第三步交换失败\n");
// 恢复第一步
rename("new_file_a.txt", "file_b.txt");
rename("temp_swap_file.txt", "new_file_a.txt");
}
} else {
printf("✗ 第二步交换失败\n");
// 恢复第一步
rename("temp_swap_file.txt", "new_file_a.txt");
}
} else {
printf("✗ 第一步交换失败\n");
}
} else {
printf("✗ 交换失败: %s\n", strerror(errno));
}
} else {
printf("✓ 成功交换文件内容\n");
}
// 显示交换后的内容
printf("交换后:\n");
printf(" new_file_a.txt 内容: ");
{
int fd = open("new_file_a.txt", O_RDONLY);
if (fd != -1) {
char buffer[100];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
printf("%s", buffer);
}
close(fd);
}
}
printf(" file_b.txt 内容: ");
{
int fd = open("file_b.txt", O_RDONLY);
if (fd != -1) {
char buffer[100];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
printf("%s", buffer);
}
close(fd);
}
}
// 7. 跨文件系统移动演示
printf("\n7. 跨文件系统移动演示:\n");
printf("尝试跨文件系统移动文件 (通常会失败):\n");
// 这个操作通常会失败,因为 rename 不支持跨文件系统
result = rename("/etc/passwd", "./passwd_copy");
if (result == -1) {
if (errno == EXDEV) {
printf("✓ 正确检测到跨文件系统移动: %s\n", strerror(errno));
printf(" 说明: rename 不支持跨文件系统移动\n");
printf(" 解决方案: 使用 copy + delete 或 mv 命令\n");
} else {
printf("✗ 其他错误: %s\n", strerror(errno));
}
} else {
printf("✓ 跨文件系统移动成功 (罕见情况)\n");
// 如果成功,需要删除副本
unlink("./passwd_copy");
}
// 8. 清理测试文件
printf("\n8. 清理测试文件:\n");
const char *files_to_delete[] = {
"new_file_a.txt", "file_b.txt", "protected_file.txt",
"temp_swap_file.txt", NULL
};
for (int i = 0; files_to_delete[i]; i++) {
if (access(files_to_delete[i], F_OK) == 0) {
if (unlink(files_to_delete[i]) == 0) {
printf("✓ 删除文件: %s\n", files_to_delete[i]);
} else {
printf("✗ 删除文件失败: %s\n", strerror(errno));
}
}
}
printf("\n=== 三种重命名函数对比 ===\n");
printf("┌─────────────┬─────────────┬─────────────┬─────────────┐\n");
printf("│ 函数 │ 基础功能 │ 相对路径 │ 高级选项 │\n");
printf("├─────────────┼─────────────┼─────────────┼─────────────┤\n");
printf("│ rename │ ✓ │ ✗ │ ✗ │\n");
printf("│ renameat │ ✓ │ ✓ │ ✗ │\n");
printf("│ renameat2 │ ✓ │ ✓ │ ✓ │\n");
printf("└─────────────┴─────────────┴─────────────┴─────────────┘\n");
printf("\n");
printf("功能对比:\n");
printf("1. rename:\n");
printf(" - 最简单的重命名操作\n");
printf(" - 只支持绝对路径\n");
printf(" - 会自动覆盖已存在的文件\n");
printf(" - 最广泛的系统支持\n");
printf("\n");
printf("2. renameat:\n");
printf(" - 支持相对路径操作\n");
printf(" - 可以使用目录文件描述符\n");
printf(" - 支持 AT_FDCWD 常量\n");
printf(" - 需要较新的系统支持\n");
printf("\n");
printf("3. renameat2:\n");
printf(" - 支持所有 renameat 功能\n");
printf(" - 添加了高级控制选项\n");
printf(" - 支持原子文件交换\n");
printf(" - 支持不覆盖选项\n");
printf(" - 需要最新的内核支持\n");
printf("\n");
printf("=== 使用建议 ===\n");
printf("选择原则:\n");
printf("1. 简单重命名: 使用 rename\n");
printf("2. 相对路径操作: 使用 renameat\n");
printf("3. 高级控制需求: 使用 renameat2\n");
printf("4. 跨平台兼容: 使用 rename\n");
printf("5. 安全操作: 使用 renameat2 + RENAME_NOREPLACE\n");
printf("\n");
printf("安全最佳实践:\n");
printf("1. 始终检查返回值和 errno\n");
printf("2. 使用 RENAME_NOREPLACE 避免意外覆盖\n");
printf("3. 在关键操作前备份重要文件\n");
printf("4. 检查文件权限和磁盘空间\n");
printf("5. 使用事务性操作确保数据完整性\n");
return 0;
}
示例4:完整的文件管理工具
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#include <sys/stat.h>
#include <libgen.h>
// 配置结构体
struct rename_config {
int interactive; // 交互模式
int force; // 强制模式
int backup; // 备份模式
int verbose; // 详细输出
int no_replace; // 不替换模式
int exchange; // 交换模式
char *suffix; // 备份后缀
};
// 安全的文件重命名函数
int safe_rename(const char *oldpath, const char *newpath,
const struct rename_config *config) {
// 验证参数
if (!oldpath || !newpath) {
errno = EINVAL;
return -1;
}
// 检查源文件是否存在
if (access(oldpath, F_OK) != 0) {
if (errno == ENOENT) {
fprintf(stderr, "错误: 源文件不存在 '%s'\n", oldpath);
} else {
perror("检查源文件失败");
}
return -1;
}
// 检查目标文件是否存在
if (access(newpath, F_OK) == 0) {
if (config->interactive) {
printf("目标文件 '%s' 已存在,是否覆盖? (y/N) ", newpath);
char response[10];
if (fgets(response, sizeof(response), stdin)) {
if (response[0] != 'y' && response[0] != 'Y') {
printf("取消操作\n");
return 0;
}
}
} else if (config->no_replace) {
fprintf(stderr, "错误: 目标文件已存在 '%s'\n", newpath);
errno = EEXIST;
return -1;
}
// 创建备份
if (config->backup) {
char backup_name[1024];
if (config->suffix) {
snprintf(backup_name, sizeof(backup_name), "%s%s", newpath, config->suffix);
} else {
snprintf(backup_name, sizeof(backup_name), "%s~", newpath);
}
if (config->verbose) {
printf("创建备份: %s -> %s\n", newpath, backup_name);
}
if (rename(newpath, backup_name) != 0) {
perror("创建备份失败");
return -1;
}
}
}
// 执行重命名
int result;
if (config->no_replace) {
// 使用 renameat2 避免覆盖
#ifdef RENAME_NOREPLACE
result = renameat2(AT_FDCWD, oldpath, AT_FDCWD, newpath, RENAME_NOREPLACE);
#else
// 如果不支持,手动检查
if (access(newpath, F_OK) == 0) {
errno = EEXIST;
result = -1;
} else {
result = rename(oldpath, newpath);
}
#endif
} else if (config->exchange) {
// 使用 renameat2 交换文件
#ifdef RENAME_EXCHANGE
result = renameat2(AT_FDCWD, oldpath, AT_FDCWD, newpath, RENAME_EXCHANGE);
#else
// 如果不支持,使用传统方式
char temp_name[1024];
snprintf(temp_name, sizeof(temp_name), "%s.temp_swap_%d", oldpath, getpid());
result = rename(oldpath, temp_name);
if (result == 0) {
result = rename(newpath, oldpath);
if (result == 0) {
result = rename(temp_name, newpath);
if (result != 0) {
// 如果第三步失败,恢复第二步
rename(oldpath, newpath);
rename(temp_name, oldpath);
}
} else {
// 如果第二步失败,恢复第一步
rename(temp_name, oldpath);
}
}
#endif
} else {
result = rename(oldpath, newpath);
}
if (result == 0) {
if (config->verbose) {
if (config->exchange) {
printf("✓ 成功交换文件 '%s' 和 '%s'\n", oldpath, newpath);
} else {
printf("✓ 成功重命名 '%s' -> '%s'\n", oldpath, newpath);
}
}
} else {
switch (errno) {
case EACCES:
fprintf(stderr, "错误: 权限不足\n");
break;
case EEXIST:
fprintf(stderr, "错误: 目标文件已存在\n");
break;
case ENOENT:
fprintf(stderr, "错误: 文件不存在\n");
break;
case EXDEV:
fprintf(stderr, "错误: 不支持跨文件系统移动\n");
break;
case EISDIR:
fprintf(stderr, "错误: 目录操作冲突\n");
break;
case ENOTEMPTY:
fprintf(stderr, "错误: 目录非空\n");
break;
default:
fprintf(stderr, "错误: %s\n", strerror(errno));
break;
}
}
return result;
}
// 显示帮助信息
void show_help(const char *program_name) {
printf("用法: %s [选项] 源文件 目标文件\n", program_name);
printf("\n选项:\n");
printf(" -i, --interactive 交互模式(覆盖前询问)\n");
printf(" -f, --force 强制模式(忽略错误)\n");
printf(" -b, --backup 创建备份\n");
printf(" -S, --suffix=SUFFIX 备份文件后缀\n");
printf(" -v, --verbose 详细输出\n");
printf(" -n, --no-replace 不替换已存在的文件\n");
printf(" -x, --exchange 交换两个文件\n");
printf(" -h, --help 显示此帮助信息\n");
printf("\n示例:\n");
printf(" %s old.txt new.txt # 重命名文件\n", program_name);
printf(" %s -i old.txt existing.txt # 交互式重命名\n", program_name);
printf(" %s -b old.txt new.txt # 重命名并备份\n", program_name);
printf(" %s -n old.txt existing.txt # 不覆盖已存在文件\n", program_name);
printf(" %s -x file1.txt file2.txt # 交换两个文件\n", program_name);
printf(" %s -v old.txt new.txt # 详细输出\n", program_name);
}
int main(int argc, char *argv[]) {
struct rename_config config = {
.interactive = 0,
.force = 0,
.backup = 0,
.verbose = 0,
.no_replace = 0,
.exchange = 0,
.suffix = NULL
};
printf("=== 文件重命名管理工具 ===\n\n");
// 解析命令行参数
static struct option long_options[] = {
{"interactive", no_argument, 0, 'i'},
{"force", no_argument, 0, 'f'},
{"backup", no_argument, 0, 'b'},
{"suffix", required_argument, 0, 'S'},
{"verbose", no_argument, 0, 'v'},
{"no-replace", no_argument, 0, 'n'},
{"exchange", no_argument, 0, 'x'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
int opt;
while ((opt = getopt_long(argc, argv, "ifbS:vnxh", long_options, NULL)) != -1) {
switch (opt) {
case 'i':
config.interactive = 1;
break;
case 'f':
config.force = 1;
break;
case 'b':
config.backup = 1;
break;
case 'S':
config.suffix = optarg;
break;
case 'v':
config.verbose = 1;
break;
case 'n':
config.no_replace = 1;
break;
case 'x':
config.exchange = 1;
break;
case 'h':
show_help(argv[0]);
return 0;
default:
fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv[0]);
return 1;
}
}
// 检查参数数量
if (optind + 1 >= argc) {
fprintf(stderr, "错误: 需要指定源文件和目标文件\n");
fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv[0]);
return 1;
}
const char *oldpath = argv[optind];
const char *newpath = argv[optind + 1];
// 显示配置信息
if (config.verbose) {
printf("操作配置:\n");
printf(" 源文件: %s\n", oldpath);
printf(" 目标文件: %s\n", newpath);
printf(" 交互模式: %s\n", config.interactive ? "是" : "否");
printf(" 强制模式: %s\n", config.force ? "是" : "否");
printf(" 备份模式: %s\n", config.backup ? "是" : "否");
printf(" 不替换: %s\n", config.no_replace ? "是" : "否");
printf(" 文件交换: %s\n", config.exchange ? "是" : "否");
printf(" 详细输出: %s\n", config.verbose ? "是" : "否");
if (config.backup && config.suffix) {
printf(" 备份后缀: %s\n", config.suffix);
}
printf("\n");
}
// 执行重命名操作
int result = safe_rename(oldpath, newpath, &config);
// 显示结果
if (result == 0) {
printf("操作完成\n");
} else if (!config.force) {
return 1;
}
// 显示使用建议
printf("\n=== 重命名最佳实践 ===\n");
printf("安全建议:\n");
printf("1. 重要文件操作前创建备份\n");
printf("2. 使用 -i 选项避免意外覆盖\n");
printf("3. 使用 -n 选项保护已存在的文件\n");
printf("4. 检查磁盘空间和权限\n");
printf("5. 对于关键操作使用事务性处理\n");
printf("\n");
printf("性能优化:\n");
printf("1. 批量操作时合并系统调用\n");
printf("2. 避免跨文件系统移动\n");
printf("3. 使用相对路径减少路径解析\n");
printf("4. 合理使用目录文件描述符\n");
printf("5. 避免频繁的小文件操作\n");
printf("\n");
printf("错误处理:\n");
printf("1. 始终检查返回值和 errno\n");
printf("2. 区分不同类型的错误\n");
printf("3. 实现适当的重试机制\n");
printf("4. 提供清晰的错误信息\n");
printf("5. 保持操作的原子性\n");
return (result == 0 || config.force) ? 0 : 1;
}
编译和运行说明
# 编译示例程序
gcc -o rename_example1 example1.c
gcc -o rename_example2 example2.c
gcc -o rename_example3 example3.c
gcc -o rename_example4 example4.c
# 运行示例
./rename_example1
./rename_example2
./rename_example3
./rename_example4 --help
./rename_example4 -v old.txt new.txt
./rename_example4 -i -b old.txt existing.txt
系统要求检查
# 检查内核版本(renameat2 需要 3.15+)
uname -r
# 检查 glibc 版本
ldd --version
# 检查系统调用支持
grep -w rename /usr/include/asm/unistd_64.h
# 检查 renameat2 支持
grep -w renameat2 /usr/include/asm/unistd_64.h
# 查看文件系统类型
df -T .
重要注意事项
- 原子性: 重命名操作是原子的
- 权限要求: 需要对目录有写权限
- 跨文件系统: 不支持跨文件系统移动
- 错误处理: 始终检查返回值和 errno
- 符号链接: 不会跟随符号链接
- 目录操作: 可以重命名目录
实际应用场景
- 文件管理: 日常文件重命名操作
- 版本控制: 文件版本管理
- 备份系统: 自动备份和版本控制
- 日志轮转: 日志文件重命名和轮转
- 临时文件: 临时文件重命名为正式文件
- 原子更新: 原子性的文件更新操作
最佳实践
// 安全的重命名函数
int secure_rename(const char *oldpath, const char *newpath) {
// 验证参数
if (!oldpath || !newpath) {
errno = EINVAL;
return -1;
}
// 检查源文件
if (access(oldpath, F_OK) != 0) {
return -1; // errno 已设置
}
// 检查目标文件
if (access(newpath, F_OK) == 0) {
printf("警告: 目标文件已存在 '%s'\n", newpath);
}
// 执行重命名
int result = rename(oldpath, newpath);
if (result == 0) {
printf("成功重命名 '%s' -> '%s'\n", oldpath, newpath);
} else {
fprintf(stderr, "重命名失败 '%s' -> '%s': %s\n",
oldpath, newpath, strerror(errno));
}
return result;
}
// 原子文件更新
int atomic_file_update(const char *temp_file, const char *final_file) {
// 先创建临时文件并写入数据
// ...
// 原子性地重命名为最终文件
if (rename(temp_file, final_file) == 0) {
return 0; // 成功
} else {
// 清理临时文件
unlink(temp_file);
return -1;
}
}
// 批量重命名
int batch_rename(const char **old_paths, const char **new_paths, int count) {
int success_count = 0;
int failed_count = 0;
for (int i = 0; i < count; i++) {
if (rename(old_paths[i], new_paths[i]) == 0) {
printf("✓ 重命名: %s -> %s\n", old_paths[i], new_paths[i]);
success_count++;
} else {
fprintf(stderr, "✗ 重命名失败: %s -> %s: %s\n",
old_paths[i], new_paths[i], strerror(errno));
failed_count++;
}
}
printf("批量重命名完成: 成功 %d, 失败 %d\n", success_count, failed_count);
return failed_count;
}
这些示例展示了 rename
、renameat
和 renameat2
函数的各种使用方法,从基础的文件重命名到完整的文件管理工具,帮助你全面掌握 Linux 系统中的文件重命名机制。