rename/renameat/renameat2系统调用及示例

rename/renameat/renameat2系统调用及示例

Linux 文件重命名系统调用详解,Linux 提供了三种文件重命名系统调用:rename、renameat 和 renameat2。其中 rename 是最基础的文件重命名函数,renameat 支持相对路径操作,renameat2 则是最强大的版本,支持额外标志位控制(如不替换已存在文件、交换文件等)。这些函数成功时返回0,失败返回-1并设置errno。文章详细介绍了各函数的原型、参数、标志位选项、常见错误码以及关联函数,并提供了基础用法的示例代码,展示了如何创建测试文件、重命名文件以及覆盖已存在文件的操作过程。

  1. 函数介绍

文件重命名是 Linux 系统中最常用的操作之一。可以把文件重命名想象成”文件的身份证号码变更”——文件的内容和属性都没有改变,只是改变了文件在文件系统中的”名字”或”位置”。

data-ad-format="fluid" data-ad-layout-key="-7k+ex-4a-9w+4a">

这三个函数都用于重命名文件或目录,但功能逐渐增强:

  • rename: 最基础的重命名函数

  • renameat: 支持相对路径的重命名函数

  • renameat2: 最强大的重命名函数,支持额外选项

  1. 函数原型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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);

  1. 功能

rename

将 oldpath 指定的文件或目录重命名为 newpath。

renameat

在指定目录描述符的上下文中重命名文件或目录,支持相对路径。

renameat2

增强版重命名,支持额外的标志控制选项。

  1. 参数

rename 参数

  • oldpath: 旧文件路径名

  • newpath: 新文件路径名

renameat/renameat2 参数

  • olddirfd: 旧文件路径的目录文件描述符

  • oldpath: 旧文件路径名

  • newdirfd: 新文件路径的目录文件描述符

  • newpath: 新文件路径名

  • flags: 控制重命名行为的标志位(仅 renameat2)

  1. 标志位(renameat2 专用)

标志值说明RENAME_NOREPLACE(1 << 0)不替换已存在的文件RENAME_EXCHANGE(1 << 1)交换两个文件RENAME_WHITEOUT(1 << 2)创建白名单条目(overlayfs 专用)

  1. 返回值
  • 成功: 返回 0

  • 失败: 返回 -1,并设置相应的 errno 错误码

  1. 常见错误码
  • EACCES: 权限不足

  • EBUSY: 文件正被使用

  • EDQUOT: 磁盘配额不足

  • EEXIST: 新文件已存在(当使用 RENAME_NOREPLACE 时)

  • EINVAL: 参数无效(如 flags 无效)

  • EISDIR: 试图用目录覆盖非目录

  • ELOOP: 符号链接循环

  • ENOENT: 文件或目录不存在

  • ENOTDIR: 路径组件不是目录

  • ENOTEMPTY: 目录非空

  • EPERM: 操作不被允许

  • EROFS: 文件系统只读

  • EXDEV: 跨文件系统移动(rename 不支持)

  1. 相似函数或关联函数
  • link/unlink: 创建/删除硬链接

  • symlink: 创建符号链接

  • mv: 命令行重命名工具

  • stat: 获取文件状态

  • access: 检查文件访问权限

  • chown/chmod: 修改文件所有者和权限

  1. 示例代码

示例1:基础用法 - rename 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#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&#91;256];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;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 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
#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&#91;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 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
#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&#91;100];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;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&#91;100];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;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&#91;100];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;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&#91;100];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer&#91;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&#91;] = {
"new_file_a.txt", "file_b.txt", "protected_file.txt",
"temp_swap_file.txt", NULL
};

for (int i = 0; files_to_delete&#91;i]; i++) {
if (access(files_to_delete&#91;i], F_OK) == 0) {
if (unlink(files_to_delete&#91;i]) == 0) {
printf("✓ 删除文件: %s\n", files_to_delete&#91;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:完整的文件管理工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
#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&#91;10];
if (fgets(response, sizeof(response), stdin)) {
if (response&#91;0] != 'y' && response&#91;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&#91;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&#91;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 &#91;选项] 源文件 目标文件\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&#91;]) {
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&#91;] = {
{"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&#91;0]);
return 0;
default:
fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv&#91;0]);
return 1;
}
}

// 检查参数数量
if (optind + 1 >= argc) {
fprintf(stderr, "错误: 需要指定源文件和目标文件\n");
fprintf(stderr, "使用 '%s --help' 查看帮助信息\n", argv&#91;0]);
return 1;
}

const char *oldpath = argv&#91;optind];
const char *newpath = argv&#91;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;
}

编译和运行说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 编译示例程序
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

系统要求检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 检查内核版本(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

符号链接: 不会跟随符号链接

目录操作: 可以重命名目录

实际应用场景

文件管理: 日常文件重命名操作

版本控制: 文件版本管理

备份系统: 自动备份和版本控制

日志轮转: 日志文件重命名和轮转

临时文件: 临时文件重命名为正式文件

原子更新: 原子性的文件更新操作

最佳实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// 安全的重命名函数
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&#91;i], new_paths&#91;i]) == 0) {
printf("✓ 重命名: %s -> %s\n", old_paths&#91;i], new_paths&#91;i]);
success_count++;
} else {
fprintf(stderr, "✗ 重命名失败: %s -> %s: %s\n",
old_paths&#91;i], new_paths&#91;i], strerror(errno));
failed_count++;
}
}

printf("批量重命名完成: 成功 %d, 失败 %d\n", success_count, failed_count);
return failed_count;
}

这些示例展示了 rename、renameat 和 renameat2 函数的各种使用方法,从基础的文件重命名到完整的文件管理工具,帮助你全面掌握 Linux 系统中的文件重命名机制。

data-ad-format="auto" data-full-width-responsive="true">