unlink 和 unlinkat 函数详解 Link to heading
1. 函数介绍 Link to heading
unlink函数和unlinkat函数是Linux系统中用于删除文件的系统调用函数。可以把它们想象成"文件删除器",专门负责从文件系统中移除文件的目录项链接。
unlink函数是最基本的文件删除函数,它删除指定路径名的文件链接。而unlinkat函数是unlink的增强版本,提供了更灵活的相对路径操作能力,可以相对于指定的目录文件描述符进行操作。
这两个函数的工作原理是减少文件的链接计数,当链接计数变为0且没有进程打开该文件时,文件的数据块才会被真正释放。这就像一个"引用计数"机制,只有当所有引用都被删除时,文件才会真正消失。
使用场景:
- 临时文件的清理
- 程序运行时的文件管理
- 系统维护和清理脚本
- 安全删除敏感文件
- 实现文件的原子操作
2. 函数原型 Link to heading
#include <unistd.h>
// 基本文件删除函数
int unlink(const char *pathname);
// 增强版文件删除函数(支持相对路径)
int unlinkat(int dirfd, const char *pathname, int flags);
3. 功能 Link to heading
- unlink: 删除指定路径名的文件链接,减少文件的链接计数
- unlinkat: 相对于指定目录文件描述符删除文件,支持更多选项
4. 参数 Link to heading
unlink参数: Link to heading
- pathname: 文件路径名
- 类型:const char*
- 含义:要删除的文件的路径名
unlinkat参数: Link to heading
-
dirfd: 目录文件描述符
- 类型:int
- 含义:基准目录的文件描述符,或特殊值AT_FDCWD
-
pathname: 文件路径名
- 类型:const char*
- 含义:相对于dirfd的文件路径名
-
flags: 操作标志
- 类型:int
- 含义:控制删除行为的标志位
- 常用值:
- 0:删除普通文件
- AT_REMOVEDIR:删除目录(相当于rmdir)
5. 返回值 Link to heading
- 成功: 返回0
- 失败: 返回-1,并设置errno错误码
- EACCES:权限不足
- EBUSY:文件正在被使用
- EFAULT:路径名指向无效内存
- EIO:I/O错误
- EISDIR:pathname是目录(unlink)或不是目录(AT_REMOVEDIR)
- ELOOP:符号链接循环
- ENAMETOOLONG:路径名过长
- ENOENT:文件或路径不存在
- ENOTDIR:路径前缀不是目录
- EPERM:操作不被允许
- EROFS:文件系统只读
6. 相似函数或关联函数 Link to heading
- remove(): 删除文件或目录的通用函数
- rmdir(): 删除空目录
- open(): 打开文件获取文件描述符
- close(): 关闭文件描述符
- stat(): 获取文件状态信息
- access(): 检查文件访问权限
- rename(): 重命名文件
7. 示例代码 Link to heading
示例1:基础unlink使用 - 简单文件删除 Link to heading
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.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 (write(fd, content, strlen(content)) == -1) {
perror("写入文件失败");
close(fd);
return -1;
}
close(fd);
printf("创建文件: %s\n", filename);
return 0;
}
// 检查文件是否存在
int file_exists(const char* filename) {
return access(filename, F_OK) == 0;
}
int main() {
printf("=== 基础unlink使用示例 ===\n");
// 创建测试文件
const char* test_files[] = {
"test_file1.txt",
"test_file2.txt",
"test_file3.txt"
};
const char* test_contents[] = {
"这是测试文件1的内容\n",
"这是测试文件2的内容\n",
"这是测试文件3的内容\n"
};
int num_files = sizeof(test_files) / sizeof(test_files[0]);
printf("1. 创建测试文件:\n");
for (int i = 0; i < num_files; i++) {
if (create_test_file(test_files[i], test_contents[i]) == -1) {
exit(EXIT_FAILURE);
}
}
// 验证文件创建成功
printf("\n2. 验证文件存在:\n");
for (int i = 0; i < num_files; i++) {
if (file_exists(test_files[i])) {
printf("✓ 文件 %s 存在\n", test_files[i]);
} else {
printf("✗ 文件 %s 不存在\n", test_files[i]);
}
}
// 使用unlink删除文件
printf("\n3. 使用unlink删除文件:\n");
for (int i = 0; i < num_files; i++) {
printf("删除文件: %s ... ", test_files[i]);
if (unlink(test_files[i]) == 0) {
printf("成功\n");
// 验证文件已被删除
if (!file_exists(test_files[i])) {
printf(" ✓ 文件 %s 已被成功删除\n", test_files[i]);
} else {
printf(" ✗ 文件 %s 仍然存在\n", test_files[i]);
}
} else {
printf("失败: %s\n", strerror(errno));
}
}
// 演示错误处理
printf("\n4. 错误处理演示:\n");
// 尝试删除不存在的文件
const char* nonexistent_file = "nonexistent.txt";
printf("删除不存在的文件 %s: ", nonexistent_file);
if (unlink(nonexistent_file) == -1) {
printf("失败(预期行为): %s\n", strerror(errno));
}
// 尝试删除目录
const char* test_dir = "test_directory";
if (mkdir(test_dir, 0755) == 0) {
printf("删除目录 %s: ", test_dir);
if (unlink(test_dir) == -1) {
printf("失败(预期行为): %s\n", strerror(errno));
}
// 清理目录
rmdir(test_dir);
}
// 尝试删除只读文件
const char* readonly_file = "readonly_file.txt";
if (create_test_file(readonly_file, "只读文件") == 0) {
// 设置只读权限
if (chmod(readonly_file, 0444) == 0) {
printf("删除只读文件 %s: ", readonly_file);
if (unlink(readonly_file) == -1) {
printf("失败: %s\n", strerror(errno));
} else {
printf("成功(权限允许)\n");
}
}
}
printf("\n=== 基础unlink演示完成 ===\n");
return 0;
}
示例2:unlinkat使用 - 相对路径文件删除 Link to heading
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <dirent.h>
// 创建目录和文件结构
int create_test_structure() {
// 创建测试目录
if (mkdir("unlinkat_test", 0755) == -1 && errno != EEXIST) {
perror("创建测试目录失败");
return -1;
}
// 在测试目录中创建子目录
if (mkdir("unlinkat_test/subdir1", 0755) == -1 && errno != EEXIST) {
perror("创建子目录失败");
return -1;
}
if (mkdir("unlinkat_test/subdir2", 0755) == -1 && errno != EEXIST) {
perror("创建子目录失败");
return -1;
}
// 创建测试文件
int fd;
// 在根目录创建文件
fd = open("unlinkat_test/file1.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd != -1) {
write(fd, "文件1内容", 9);
close(fd);
}
// 在subdir1中创建文件
fd = open("unlinkat_test/subdir1/file2.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd != -1) {
write(fd, "文件2内容", 9);
close(fd);
}
// 在subdir2中创建文件
fd = open("unlinkat_test/subdir2/file3.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd != -1) {
write(fd, "文件3内容", 9);
close(fd);
}
printf("测试目录结构创建完成\n");
return 0;
}
// 显示目录内容
void show_directory_contents(const char* path) {
DIR* dir = opendir(path);
if (dir == NULL) {
printf("无法打开目录 %s: %s\n", path, strerror(errno));
return;
}
printf("目录 %s 的内容:\n", path);
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("=== unlinkat使用示例 ===\n");
// 创建测试结构
if (create_test_structure() == -1) {
exit(EXIT_FAILURE);
}
// 显示初始目录结构
printf("1. 初始目录结构:\n");
show_directory_contents("unlinkat_test");
show_directory_contents("unlinkat_test/subdir1");
show_directory_contents("unlinkat_test/subdir2");
// 使用unlinkat删除文件
printf("2. 使用unlinkat删除文件:\n");
// 打开基准目录
int base_dirfd = open("unlinkat_test", O_RDONLY);
if (base_dirfd == -1) {
perror("打开基准目录失败");
exit(EXIT_FAILURE);
}
// 相对于基准目录删除文件
printf("相对于基准目录删除 file1.txt: ");
if (unlinkat(base_dirfd, "file1.txt", 0) == 0) {
printf("成功\n");
} else {
printf("失败: %s\n", strerror(errno));
}
// 使用AT_FDCWD(当前工作目录)删除文件
printf("使用AT_FDCWD删除 subdir1/file2.txt: ");
if (unlinkat(AT_FDCWD, "unlinkat_test/subdir1/file2.txt", 0) == 0) {
printf("成功\n");
} else {
printf("失败: %s\n", strerror(errno));
}
// 打开subdir2并删除其中的文件
int subdir2_fd = open("unlinkat_test/subdir2", O_RDONLY);
if (subdir2_fd != -1) {
printf("相对于subdir2删除 file3.txt: ");
if (unlinkat(subdir2_fd, "file3.txt", 0) == 0) {
printf("成功\n");
} else {
printf("失败: %s\n", strerror(errno));
}
close(subdir2_fd);
}
// 显示删除后的目录结构
printf("\n3. 删除后的目录结构:\n");
show_directory_contents("unlinkat_test");
show_directory_contents("unlinkat_test/subdir1");
show_directory_contents("unlinkat_test/subdir2");
// 演示AT_REMOVEDIR标志(删除目录)
printf("4. 使用AT_REMOVEDIR删除空目录:\n");
// 创建一个空目录用于测试
if (mkdir("unlinkat_test/empty_dir", 0755) == 0) {
printf("创建空目录 empty_dir\n");
show_directory_contents("unlinkat_test");
printf("使用AT_REMOVEDIR删除 empty_dir: ");
if (unlinkat(base_dirfd, "empty_dir", AT_REMOVEDIR) == 0) {
printf("成功\n");
} else {
printf("失败: %s\n", strerror(errno));
}
show_directory_contents("unlinkat_test");
}
// 错误处理演示
printf("\n5. 错误处理演示:\n");
// 尝试删除不存在的文件
printf("删除不存在的文件 nonexistent.txt: ");
if (unlinkat(base_dirfd, "nonexistent.txt", 0) == -1) {
printf("失败(预期行为): %s\n", strerror(errno));
}
// 尝试删除非空目录(不使用AT_REMOVEDIR)
printf("删除非空目录 subdir1(不使用AT_REMOVEDIR): ");
if (unlinkat(base_dirfd, "subdir1", 0) == -1) {
printf("失败(预期行为): %s\n", strerror(errno));
}
// 尝试删除文件但使用了AT_REMOVEDIR
printf("删除文件但使用AT_REMOVEDIR: ");
if (unlinkat(AT_FDCWD, "unlinkat_test/subdir1/file2.txt", AT_REMOVEDIR) == -1) {
printf("失败(预期行为): %s\n", strerror(errno));
}
// 清理资源
close(base_dirfd);
// 最终清理
rmdir("unlinkat_test/subdir1");
rmdir("unlinkat_test");
printf("\n=== unlinkat演示完成 ===\n");
return 0;
}
示例3:高级应用 - 安全临时文件管理 Link to heading
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <time.h>
#include <sys/types.h>
// 临时文件信息结构
typedef struct {
char filepath[256];
int fd;
time_t create_time;
size_t size;
} temp_file_info_t;
// 创建安全的临时文件
temp_file_info_t* create_secure_temp_file(const char* prefix) {
temp_file_info_t* temp_info = malloc(sizeof(temp_file_info_t));
if (!temp_info) {
perror("内存分配失败");
return NULL;
}
// 生成唯一的临时文件名
snprintf(temp_info->filepath, sizeof(temp_info->filepath),
"%s_%ld_%d.tmp", prefix, time(NULL), getpid());
// 创建临时文件(安全模式:创建后立即删除目录项)
temp_info->fd = open(temp_info->filepath,
O_CREAT | O_EXCL | O_RDWR | O_TRUNC,
0600);
if (temp_info->fd == -1) {
perror("创建临时文件失败");
free(temp_info);
return NULL;
}
temp_info->create_time = time(NULL);
temp_info->size = 0;
// 立即删除目录项,这样即使程序异常退出,文件也会自动清理
// 但文件描述符仍然有效,可以继续读写
if (unlink(temp_info->filepath) == -1) {
perror("删除临时文件目录项失败");
close(temp_info->fd);
free(temp_info);
return NULL;
}
printf("创建安全临时文件: fd=%d (路径已被删除)\n", temp_info->fd);
return temp_info;
}
// 向临时文件写入数据
int write_to_temp_file(temp_file_info_t* temp_info, const char* data) {
ssize_t bytes_written = write(temp_info->fd, data, strlen(data));
if (bytes_written == -1) {
perror("写入临时文件失败");
return -1;
}
temp_info->size += bytes_written;
printf("向临时文件写入 %zd 字节\n", bytes_written);
return 0;
}
// 从临时文件读取数据
int read_from_temp_file(temp_file_info_t* temp_info, char* buffer, size_t buffer_size) {
// 重置文件指针到开始
if (lseek(temp_info->fd, 0, SEEK_SET) == -1) {
perror("重置文件指针失败");
return -1;
}
ssize_t bytes_read = read(temp_info->fd, buffer, buffer_size - 1);
if (bytes_read == -1) {
perror("读取临时文件失败");
return -1;
}
buffer[bytes_read] = '\0';
printf("从临时文件读取 %zd 字节\n", bytes_read);
return bytes_read;
}
// 关闭并清理临时文件
void close_temp_file(temp_file_info_t* temp_info) {
if (temp_info) {
if (temp_info->fd != -1) {
close(temp_info->fd);
}
printf("临时文件已关闭和清理\n");
free(temp_info);
}
}
// 演示unlink的安全删除特性
void demonstrate_secure_delete() {
printf("\n=== 安全删除特性演示 ===\n");
// 创建普通文件
const char* normal_file = "normal_temp.txt";
int fd = open(normal_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd != -1) {
write(fd, "普通临时文件内容", 15);
printf("创建普通临时文件: %s\n", normal_file);
close(fd);
}
// 创建安全临时文件
temp_file_info_t* secure_temp = create_secure_temp_file("secure");
if (secure_temp) {
write_to_temp_file(secure_temp, "安全临时文件内容");
// 验证文件是否真的被删除了(但仍然可以访问)
if (access(secure_temp->filepath, F_OK) == -1) {
printf("✓ 安全临时文件的路径已被删除\n");
} else {
printf("✗ 安全临时文件路径仍然存在\n");
}
if (access(normal_file, F_OK) == 0) {
printf("✓ 普通临时文件仍然存在\n");
}
// 读取安全临时文件内容
char buffer[256];
if (read_from_temp_file(secure_temp, buffer, sizeof(buffer)) > 0) {
printf("安全临时文件内容: %s\n", buffer);
}
// 关闭安全临时文件
close_temp_file(secure_temp);
}
// 清理普通文件
unlink(normal_file);
printf("普通临时文件已手动清理\n");
}
// 演示unlinkat的相对路径优势
void demonstrate_relative_path_advantage() {
printf("\n=== 相对路径优势演示 ===\n");
// 创建测试目录结构
mkdir("demo_root", 0755);
mkdir("demo_root/dir1", 0755);
mkdir("demo_root/dir2", 0755);
// 在各个目录创建文件
int fd1 = open("demo_root/file_root.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
int fd2 = open("demo_root/dir1/file_dir1.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
int fd3 = open("demo_root/dir2/file_dir2.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd1 != -1) {
write(fd1, "根目录文件", 10);
close(fd1);
}
if (fd2 != -1) {
write(fd2, "目录1文件", 9);
close(fd2);
}
if (fd3 != -1) {
write(fd3, "目录2文件", 9);
close(fd3);
}
printf("创建测试文件完成\n");
// 使用传统方式删除(需要完整路径)
printf("传统方式删除:\n");
if (unlink("demo_root/file_root.txt") == 0) {
printf(" 删除 demo_root/file_root.txt 成功\n");
}
// 使用unlinkat相对路径删除
printf("unlinkat相对路径删除:\n");
int root_fd = open("demo_root", O_RDONLY);
if (root_fd != -1) {
if (unlinkat(root_fd, "dir1/file_dir1.txt", 0) == 0) {
printf(" 相对于root_fd删除 dir1/file_dir1.txt 成功\n");
}
if (unlinkat(root_fd, "dir2/file_dir2.txt", 0) == 0) {
printf(" 相对于root_fd删除 dir2/file_dir2.txt 成功\n");
}
close(root_fd);
}
// 清理目录
rmdir("demo_root/dir1");
rmdir("demo_root/dir2");
rmdir("demo_root");
printf("相对路径删除演示完成\n");
}
int main() {
printf("=== unlink和unlinkat高级应用示例 ===\n");
// 演示安全临时文件管理
printf("1. 安全临时文件管理:\n");
temp_file_info_t* temp1 = create_secure_temp_file("temp1");
temp_file_info_t* temp2 = create_secure_temp_file("temp2");
if (temp1) {
write_to_temp_file(temp1, "第一个安全临时文件的内容\n");
write_to_temp_file(temp1, "追加的内容\n");
char buffer[256];
read_from_temp_file(temp1, buffer, sizeof(buffer));
printf("读取内容: %s", buffer);
close_temp_file(temp1);
}
if (temp2) {
write_to_temp_file(temp2, "第二个安全临时文件的内容\n");
close_temp_file(temp2);
}
// 演示安全删除特性
demonstrate_secure_delete();
// 演示相对路径优势
demonstrate_relative_path_advantage();
// 性能对比测试
printf("\n=== 性能对比测试 ===\n");
// 创建大量测试文件
const int file_count = 1000;
printf("创建 %d 个测试文件...\n", file_count);
for (int i = 0; i < file_count; i++) {
char filename[64];
snprintf(filename, sizeof(filename), "perf_test_%d.txt", i);
int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd != -1) {
char content[32];
snprintf(content, sizeof(content), "文件%d", i);
write(fd, content, strlen(content));
close(fd);
}
}
// 测试unlink性能
clock_t start = clock();
for (int i = 0; i < file_count; i++) {
char filename[64];
snprintf(filename, sizeof(filename), "perf_test_%d.txt", i);
unlink(filename);
}
clock_t end = clock();
double unlink_time = ((double)(end - start)) / CLOCKS_PER_SEC;
printf("删除 %d 个文件耗时: %.4f 秒\n", file_count, unlink_time);
printf("\n=== 高级应用演示完成 ===\n");
return 0;
}
示例4:错误处理和最佳实践 Link to heading
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <limits.h>
// 安全的unlink包装函数
int safe_unlink(const char* pathname) {
if (pathname == NULL) {
errno = EINVAL;
return -1;
}
// 检查路径长度
if (strlen(pathname) >= PATH_MAX) {
errno = ENAMETOOLONG;
return -1;
}
// 检查文件是否存在
if (access(pathname, F_OK) != 0) {
if (errno == ENOENT) {
printf("文件 %s 不存在,无需删除\n", pathname);
return 0; // 不算错误
} else {
return -1; // 其他访问错误
}
}
// 执行删除操作
int result = unlink(pathname);
if (result == 0) {
printf("成功删除文件: %s\n", pathname);
} else {
switch (errno) {
case EACCES:
fprintf(stderr, "权限不足,无法删除文件: %s\n", pathname);
break;
case EBUSY:
fprintf(stderr, "文件正在被使用: %s\n", pathname);
break;
case EISDIR:
fprintf(stderr, "不能使用unlink删除目录: %s\n", pathname);
break;
case EPERM:
fprintf(stderr, "操作不被允许: %s\n", pathname);
break;
case EROFS:
fprintf(stderr, "文件系统只读: %s\n", pathname);
break;
default:
fprintf(stderr, "删除文件失败 %s: %s\n", pathname, strerror(errno));
break;
}
}
return result;
}
// 安全的unlinkat包装函数
int safe_unlinkat(int dirfd, const char* pathname, int flags) {
if (pathname == NULL) {
errno = EINVAL;
return -1;
}
// 检查路径长度
if (strlen(pathname) >= PATH_MAX) {
errno = ENAMETOOLONG;
return -1;
}
// 执行删除操作
int result = unlinkat(dirfd, pathname, flags);
if (result == 0) {
if (flags & AT_REMOVEDIR) {
printf("成功删除目录: %s (相对于fd %d)\n", pathname, dirfd);
} else {
printf("成功删除文件: %s (相对于fd %d)\n", pathname, dirfd);
}
} else {
const char* type = (flags & AT_REMOVEDIR) ? "目录" : "文件";
switch (errno) {
case EACCES:
fprintf(stderr, "权限不足,无法删除%s: %s\n", type, pathname);
break;
case EBUSY:
fprintf(stderr, "%s正在被使用: %s\n", type, pathname);
break;
case EISDIR:
if (flags & AT_REMOVEDIR) {
fprintf(stderr, "目标不是目录: %s\n", pathname);
} else {
fprintf(stderr, "不能使用unlink删除目录: %s\n", pathname);
}
break;
case ENOTDIR:
fprintf(stderr, "路径前缀不是目录: %s\n", pathname);
break;
case ENOENT:
fprintf(stderr, "%s不存在: %s\n", type, pathname);
break;
default:
fprintf(stderr, "删除%s失败 %s: %s\n", type, pathname, strerror(errno));
break;
}
}
return result;
}
// 递归删除目录(使用unlinkat)
int recursive_remove_directory(const char* path) {
DIR* dir = opendir(path);
if (dir == NULL) {
if (errno == ENOENT) {
return 0; // 目录不存在,视为成功
}
perror("打开目录失败");
return -1;
}
// 打开目录文件描述符用于unlinkat
int dirfd = open(path, O_RDONLY);
if (dirfd == -1) {
perror("打开目录文件描述符失败");
closedir(dir);
return -1;
}
struct dirent* entry;
int result = 0;
// 遍历目录中的所有项
while ((entry = readdir(dir)) != NULL && result == 0) {
// 跳过.和..
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
continue;
}
if (entry->d_type == DT_DIR) {
// 递归删除子目录
char subpath[PATH_MAX];
snprintf(subpath, sizeof(subpath), "%s/%s", path, entry->d_name);
result = recursive_remove_directory(subpath);
} else {
// 删除文件
result = safe_unlinkat(dirfd, entry->d_name, 0);
}
}
closedir(dir);
close(dirfd);
// 删除空目录本身
if (result == 0) {
result = rmdir(path);
if (result == 0) {
printf("成功删除目录: %s\n", path);
} else {
fprintf(stderr, "删除目录失败 %s: %s\n", path, strerror(errno));
}
}
return result;
}
// 创建测试环境
int create_test_environment() {
printf("创建测试环境...\n");
// 创建目录结构
mkdir("test_env", 0755);
mkdir("test_env/dir1", 0755);
mkdir("test_env/dir1/subdir", 0755);
mkdir("test_env/dir2", 0755);
// 创建各种测试文件
int fd;
// 普通文件
fd = open("test_env/file1.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd != -1) {
write(fd, "普通文件内容", 12);
close(fd);
}
// 只读文件
fd = open("test_env/readonly.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd != -1) {
write(fd, "只读文件内容", 12);
close(fd);
chmod("test_env/readonly.txt", 0444);
}
// 在子目录中创建文件
fd = open("test_env/dir1/subfile.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd != -1) {
write(fd, "子目录文件", 10);
close(fd);
}
// 符号链接
symlink("file1.txt", "test_env/link_to_file1");
printf("测试环境创建完成\n");
return 0;
}
// 显示测试环境状态
void show_test_environment() {
printf("\n当前测试环境状态:\n");
DIR* dir = opendir("test_env");
if (dir) {
printf("test_env/ 目录内容:\n");
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) {
printf(" %s (%s)\n", entry->d_name,
entry->d_type == DT_DIR ? "目录" :
entry->d_type == DT_LNK ? "链接" : "文件");
}
}
closedir(dir);
}
}
int main() {
printf("=== unlink和unlinkat错误处理与最佳实践 ===\n");
// 创建测试环境
if (create_test_environment() == -1) {
exit(EXIT_FAILURE);
}
show_test_environment();
// 演示安全删除函数
printf("\n=== 安全删除函数演示 ===\n");
// 正常删除
safe_unlink("test_env/file1.txt");
// 删除不存在的文件
safe_unlink("test_env/nonexistent.txt");
// 删除只读文件
safe_unlink("test_env/readonly.txt");
// 尝试删除目录
safe_unlink("test_env/dir1");
show_test_environment();
// 演示unlinkat的高级用法
printf("\n=== unlinkat高级用法演示 ===\n");
// 相对于目录文件描述符删除
int dirfd = open("test_env", O_RDONLY);
if (dirfd != -1) {
printf("使用unlinkat删除符号链接:\n");
safe_unlinkat(dirfd, "link_to_file1", 0);
printf("使用unlinkat删除子目录中的文件:\n");
safe_unlinkat(dirfd, "dir1/subfile.txt", 0);
close(dirfd);
}
show_test_environment();
// 演示AT_REMOVEDIR用法
printf("\n=== AT_REMOVEDIR用法演示 ===\n");
// 创建空目录用于测试
mkdir("test_env/empty_dir", 0755);
show_test_environment();
dirfd = open("test_env", O_RDONLY);
if (dirfd != -1) {
printf("使用AT_REMOVEDIR删除空目录:\n");
safe_unlinkat(dirfd, "empty_dir", AT_REMOVEDIR);
close(dirfd);
}
show_test_environment();
// 演示递归删除(使用unlinkat)
printf("\n=== 递归删除演示 ===\n");
printf("递归删除整个test_env目录:\n");
recursive_remove_directory("test_env");
// 验证目录已被删除
if (access("test_env", F_OK) == -1) {
printf("✓ test_env目录已被成功删除\n");
} else {
printf("✗ test_env目录仍然存在\n");
}
// 演示最佳实践
printf("\n=== 最佳实践演示 ===\n");
// 1. 创建临时文件并安全删除
printf("1. 安全临时文件管理:\n");
int temp_fd = open("safe_temp.txt", O_CREAT | O_EXCL | O_RDWR, 0600);
if (temp_fd != -1) {
write(temp_fd, "临时数据", 8);
printf(" 创建临时文件\n");
// 立即删除目录项
if (unlink("safe_temp.txt") == 0) {
printf(" ✓ 立即删除目录项,文件更安全\n");
// 可以继续使用文件描述符
lseek(temp_fd, 0, SEEK_SET);
char buffer[32];
read(temp_fd, buffer, sizeof(buffer));
printf(" 可以继续读写文件内容\n");
}
close(temp_fd);
}
// 2. 错误处理最佳实践
printf("\n2. 错误处理最佳实践:\n");
const char* test_paths[] = {
NULL, // NULL指针
"", // 空字符串
"/very/long/path/that/exceeds/the/maximum/path/length/allowed/by/the/system/and/should/cause/an/error", // 路径过长
"nonexistent_file.txt" // 不存在的文件
};
for (int i = 0; i < 4; i++) {
printf(" 测试路径 %d: ", i + 1);
if (safe_unlink(test_paths[i]) == -1) {
if (errno == EINVAL) {
printf("参数无效(预期)\n");
} else if (errno == ENAMETOOLONG) {
printf("路径名过长(预期)\n");
} else {
printf("其他错误: %s\n", strerror(errno));
}
} else {
printf("成功\n");
}
}
// 清理可能残留的文件
unlink("safe_temp.txt");
printf("\n=== 错误处理与最佳实践演示完成 ===\n");
return 0;
}
编译和运行 Link to heading
# 编译示例1
gcc -o unlink_example1 unlink_example1.c
./unlink_example1
# 编译示例2
gcc -o unlink_example2 unlink_example2.c
./unlink_example2
# 编译示例3
gcc -o unlink_example3 unlink_example3.c
./unlink_example3
# 编译示例4
gcc -o unlink_example4 unlink_example4.c
./unlink_example4
重要注意事项 Link to heading
- 链接计数: unlink减少文件链接计数,不一定会立即释放磁盘空间
- 打开文件: 已打开的文件即使被unlink也不会立即删除
- 权限要求: 需要对包含文件的目录有写权限
- 目录删除: unlink不能删除目录,需要使用rmdir或unlinkat+AT_REMOVEDIR
- 符号链接: unlink删除符号链接本身,不删除目标文件
- 原子性: unlink操作是原子的
- 错误处理: 必须检查返回值并适当处理错误
- 安全性: 可用于安全删除临时文件
最佳实践 Link to heading
- 使用安全包装函数: 对unlink和unlinkat进行封装以提供更好的错误处理
- 检查返回值: 始终检查函数返回值
- 权限检查: 在删除前检查必要的权限
- 安全临时文件: 创建后立即unlink以提高安全性
- 相对路径: 在批量操作时使用unlinkat提高效率
- 错误日志: 记录删除操作的详细信息
- 资源清理: 确保及时清理不需要的文件
通过这些示例,你可以理解unlink和unlinkat在文件管理方面的强大功能,它们为Linux系统提供了灵活、安全的文件删除机制。