「Linux 系统编程」文件权限与目录操作

1 文件和目录的 rwx 权限差异

  • 文件权限

    • r (读取): 允许查看文件内容
    • w (写入): 允许修改文件内容
    • x (执行): 允许将文件作为程序执行
  • 目录权限

    • r (读取): 允许 列出目录内容需要 x 权限配合
    • w (写入): 允许在目录中 创建、删除和重命名文件(需要 x 权限配合)
    • x (执行): 允许 访问目录中的文件及其元数据



2 目录操作函数

2.1 opendir() 打开目录

opendir() 用于打开一个已存在的目录,并返回一个目录流(directory stream)指针(DIR*)。

  • 函数原型

    1
    2
    3
    4
    #include <sys/types.h>
    #include <dirent.h>

    DIR *opendir(const char *name);
  • 参数

    • name:要打开的目录的路径名。
  • 返回值

    • 成功时返回一个指向 DIR 结构体的指针(可视为目录流的句柄)。
    • 失败时返回 NULL,并设置相应的 errno
  • 代码示例

    1
    2
    3
    4
    5
    6
    DIR* dp;
    dp = opendir("."); // 获取当前目录流
    if (dp == NULL) {
    perror("opendir failed");
    exit(EXIT_FAILURE);
    }

2.2 readdir() 读取目录

readdir()opendir() 返回的目录流中 读取下一个目录项(entry)。

  • 函数原型

    1
    2
    3
    #include <dirent.h>

    struct dirent *readdir(DIR *dirp);
  • 参数

    dirp:由 opendir() 返回的目录流指针。

  • 返回值

    • 成功时返回一个指向 struct dirent 的指针,该结构体包含了目录项的信息(如文件名、inode号等)

    • 到达目录末尾 或 出错时 返回 NULL。如果需要区分是错误还是到达末尾,需要在调用前将 errno 设为 0,调用后如果返回 NULLerrno 被改变,则说明出错。

    struct dirent 内容:

    1
    2
    3
    4
    5
    6
    7
    struct dirent {
    long d_ino; // 文件的 inode 节点号
    off_t d_off; // 目录项在目录文件中的偏移量
    unsigned short d_reclen; // 当前目录项的长度
    unsigned char d_type; // 文件类型
    char d_name[NAME_MAX+1]; // 文件名(以 \0 结尾,最大 255 字符)
    };
  • 代码示例

    1
    2
    3
    4
    5
    6
    7
    8
    struct dirent *entry;
    errno = 0; // 清空错误标志
    while ((entry = readdir(dp)) != NULL) {
    printf("File: %s\n", entry->d_name);
    }
    if (errno != 0) { // 检查循环是否因错误而退出
    perror("readdir failed");
    }

2.3 closedir() 关闭目录

closedir() 关闭由 opendir() 打开的目录流,释放相关资源。

  • 函数原型

    1
    2
    3
    4
    #include <sys/types.h>
    #include <dirent.h>

    int closedir(DIR *dirp);
  • 参数

    • dirp:要关闭的目录流指针。
  • 返回值

    • 成功时返回 0
    • 失败时返回 -1,并设置相应的 errno
  • 代码示例

    1
    2
    3
    if (closedir(dp) == -1) {
    perror("closedir failed");
    }

2.4 实现列出目录下所有文件

可以使用 opendir(), readdir(), closedir() 来列出指定目录下所有文件,实现类似 ls 命令的功能:

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
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <errno.h>

int main(int argc, char* argv[]){
// 如果参数数量 argc == 1, 列出 "." 当前目录下的所有文件
const char* dir_name = (argc > 1) ? argv[1] : ".";
DIR* dp;

// 打开目录
dp = opendir(dir_name);
if(dp == NULL){
perror("opendir failed");
exit(EXIT_FAILURE);
}

printf("%s:\n", dir_name);
struct dirent* entry;
errno = 0;

// 循环读取目录流中的每一个条目
while((entry = readdir(dp)) != NULL){
// 跳过 "." 和 ".."
if(entry -> d_name[0] == '.'){
continue;
}
printf("%s\t", entry -> d_name);
}
printf("\n");

// 检查读取循环是否因错误而结束
if(errno != 0){
perror("readdir failed");
closedir(dp);
exit(EXIT_FAILURE);
}

// 关闭目录流
if(closedir(dp) == -1){
perror("closedir failed");
exit(EXIT_FAILURE);
}

return 0;
}



3 实现递归遍历目录

3.1 整体思路

一开始判断命令行参数,获取查询的目录名称。 如果 argc == 1,表示查询的是当前的目录 .

编写一个递归读取目录函数 recursive_read_dir

  1. 获取当前目录/文件路径信息,并格式化打印出路径信息。

  2. 判断 是否是目录

    • 如果是普通文件,则直接返回。
    • 如果是目录,则遍历目录下的每个条目。对于遍历到的每个条目,构建完整的路径信息,递归调用函数 recursive_read_dir

3.2 实现代码

在实现代码中,为了让输出更加美观(比如类似于 tree 命令那种输出形式),在调用 recursive_read_dir 时另外传入了深度 depth 以及 判断是否是当前目录下最后一项 is_last 的参数,这样可以帮助相同深度的文件对齐显示。

具体细节可以直接看 ls-R.c 代码及注释:

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
// ls-R.c 代码
#include <stdio.h>
#include <dirent.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>

void recursive_read_dir(const char* path, int depth, int is_last){
struct stat path_stat;

// 获取路径信息
if(lstat(path, &path_stat) == -1){
perror("lstat error");
return;
}

// 格式化输出路径信息
for(int i = 0; i < depth - 1; i ++ ){
printf(" ");
}
if(depth > 0){
if(is_last){
printf("└── ");
}else{
printf("├── ");
}
}
char *base_name = strrchr(path, '/'); // 最后一个 '/' 的位置
printf("%s\n", base_name ? base_name + 1 : path);

// 如果是普通文件,直接返回
if(!S_ISDIR(path_stat.st_mode)){
return;
}

// 如果是目录,需要继续读取
DIR* dir = opendir(path);
if(dir == NULL){
perror("opendir error");
return;
}

// 读取目录内容
struct dirent* entry;
int count = 0;

// 第一次遍历计算目录下文件数量
while((entry = readdir(dir)) != NULL){
// 跳过 ".", ".." 及隐藏文件
if(entry -> d_name[0] == '.'){
continue;
}
count ++ ;
}

// 重置目录流位置
rewinddir(dir);

// 第二次遍历,处理每个条目
int current = 0;
while((entry = readdir(dir)) != NULL){
// 跳过 ".", ".." 及隐藏文件
if(entry -> d_name[0] == '.'){
continue;
}

// 构建完整路径
char full_path[1024]; // 增加缓冲区大小以避免截断警告
sprintf(full_path, "%s/%s", path, entry -> d_name);
current ++ ;

// 递归调用函数
recursive_read_dir(full_path, depth + 1, (current == count));
}

// 关闭目录
closedir(dir);
}

int main(int argc, char* argv[]){
if(argc == 1){
recursive_read_dir(".", 0, 1);
}else{
recursive_read_dir(argv[1], 0, 1);
}

return 0;
}

测试结果如下:


「Linux 系统编程」文件权限与目录操作
https://marisamagic.github.io/2025/08/28/20250828/
作者
MarisaMagic
发布于
2025年8月28日
许可协议