linux目录遍历

众所周知,Linux的目录是一个树状结构,了解数据结构的小伙伴都明白,遍历一棵树最简单的方式是递归。在我们已经掌握了递归的使用方法之后,遍历树状目录也不是一件难事儿。

Linux给我们提供了相关的目录遍历的函数,分别为:opendir(), readdir(), closedir()。目录的操作方式和标准C库提供的文件操作步骤是类似的。下面来依次介绍一下这几个函数。

  1. 目录三剑客
    1.1 opendir
    在目录操作之前必须要先通过 opendir() 函数打开这个目录,函数原型如下:

C
1
2
3
4
#include <sys/types.h>
#include <dirent.h>
// 打开目录
DIR *opendir(const char name);
参数: name -> 要打开的目录的名字
返回值: DIR
, 结构体类型指针。打开成功返回目录的实例,打开失败返回 NULL
1.2 readdir
目录打开之后,就可以通过 readdir() 函数遍历目录中的文件信息了。每调用一次这个函数就可以得到目录中的一个文件信息,当目录中的文件信息被全部遍历完毕会得到一个空对象。先来看一下这个函数原型:

C
1
2
3
// 读目录
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
参数:dirp -> opendir() 函数的返回值
返回值:函数调用成功,返回读到的文件的信息, 目录文件被读完了或者函数调用失败返回 NULL
函数返回值 struct dirent 结构体原型如下:

C
1
2
3
4
5
6
7
struct dirent {
ino_t d_ino; /* 文件对应的inode编号, 定位文件存储在磁盘的那个数据块上 /
off_t d_off; /
文件在当前目录中的偏移量 /
unsigned short d_reclen; /
文件名字的实际长度 /
unsigned char d_type; /
文件的类型, linux中有7中文件类型 /
char d_name[256]; /
文件的名字 */
};
关于结构体中的文件类型d_type,可使用的宏值如下:

DT_BLK:块设备文件
DT_CHR:字符设备文件
DT_DIR:目录文件
DT_FIFO :管道文件
DT_LNK:软连接文件
DT_REG :普通文件
DT_SOCK:本地套接字文件
DT_UNKNOWN:无法识别的文件类型
那么,如何通过 readdir() 函数遍历某一个目录中的文件呢?

C
1
2
3
4
5
6
7
8
// 打开目录
DIR* dir = opendir(“/home/test”);
struct dirent* ptr = NULL;
// 遍历目录
while( (ptr=readdir(dir)) != NULL)
{

}
1.3 closedir
目录操作完毕之后, 需要通过 closedir()关闭通过opendir()得到的实例,释放资源。函数原型如下:

C
1
2
// 关闭目录, 参数是 opendir() 的返回值
int closedir(DIR *dirp);
参数:dirp-> opendir() 函数的返回值
返回值: 目录关闭成功返回0, 失败返回 -1
2.遍历目录
2.1 遍历单层目录
如果只遍历单层目录是不需要递归的,按照上边介绍的函数的使用方法,依次继续调用即可。假设我们需要得到某个指定目录下 mp3 格式文件的个数,示例代码如下:

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
// filenum.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>

int main(int argc, char* argv[])
{
// 1. 打开目录
DIR* dir = opendir(argv[1]);
if(dir == NULL)
{
perror(“opendir”);
return -1;
}

// 2. 遍历当前目录中的文件
int count = 0;
while(1)
{
    struct dirent* ptr = readdir(dir);
    if(ptr == NULL)
    {
        printf("目录读完了...\n");
        break;
    }
    // 读到了一个文件
    // 判断文件类型
    if(ptr->d_type == DT_REG)
    {
        char* p = strstr(ptr->d_name, ".mp3");
        if(p != NULL && *(p+4) == '\0')
        {
            count++;
            printf("file %d: %s\n", count, ptr->d_name);
        }
    }
}

printf("%s目录中mp3文件的个数: %d\n", argv[1], count);

// 关闭目录
closedir(dir);

return 0;

}
编译名执行程序

SHELL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ gcc filenum.c

读当前目录中mp3文件个数

$ ./a.out .
file 1: 1.mp3
目录读完了…
.目录中mp3文件的个数: 1

读 ./sub 目录中mp3文件个数

$ ./a.out ./sub/
file 1: 3.mp3
file 2: 1.mp3
file 3: 5.mp3
file 4: 4.mp3
file 5: 2.mp3
目录读完了…
./sub/目录中mp3文件的个数: 5
2.2 遍历多层目录
Linux 的目录是树状结构,遍历每层目录的方式都是一样的,也就是说最简单的遍历方式是递归。程序的重点就是确定递归结束的条件:遍历的文件如果不是目录类型就结束递归。

示例代码如下:

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
// filenum.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>

int getMp3Num(const char* path)
{
// 1. 打开目录
DIR* dir = opendir(path);
if(dir == NULL)
{
perror(“opendir”);
return 0;
}
// 2. 遍历当前目录
struct dirent* ptr = NULL;
int count = 0;
while((ptr = readdir(dir)) != NULL)
{
// 如果是目录 . … 跳过不处理
if(strcmp(ptr->d_name, “.”)==0 ||
strcmp(ptr->d_name, “…”) == 0)
{
continue;
}
// 假设读到的当前文件是目录
if(ptr->d_type == DT_DIR)
{
// 目录
char newPath[1024];
sprintf(newPath, “%s/%s”, path, ptr->d_name);
// 读当前目录的子目录
count += getMp3Num(newPath);
}
else if(ptr->d_type == DT_REG)
{
// 普通文件
char* p = strstr(ptr->d_name, “.mp3”);
// 判断文件后缀是不是 .mp3
if(p != NULL && *(p+4) == ‘\0’)
{
count++;
printf(“%s/%s\n”, path, ptr->d_name);
}
}
}

closedir(dir);
return count;

}

int main(int argc, char* argv[])
{
// ./a.out path
if(argc < 2)
{
printf(“./a.out path\n”);
return 0;
}

int num = getMp3Num(argv[1]);
printf("%s 目录中mp3文件个数: %d\n", argv[1], num);

return 0;

}
编译并运行程序:

SHELL
1
2
3
4
5
6
7
8
9
10
11
12
13
$ gcc filenum.c

查看 abc 目录中mp3 文件个数

$ ./a.out abc
abc/sub/3.mp3
abc/sub/1.mp3
abc/sub/5.mp3
abc/sub/4.mp3
abc/sub/2.mp3
abc/sub/music/test2.mp3
abc/sub/music/test3.mp3
abc/sub/music/test1.mp3
abc/hello.mp3
abc 目录中mp3文件个数: 9
3. scandir函数
除了使用上边介绍的目录三剑客遍历目录,也可以使用scandir()函数进行目录的遍历(只遍历指定目录,不进入到子目录中进行递归遍历),它的参数并不简单,涉及到三级指针和回调函数的使用。

其函数原型如下:

C
1
2
3
4
5
6
7
// 头文件
#include <dirent.h>
int scandir(const char *dirp, struct dirent ***namelist,
int (*filter)(const struct dirent *),
int (*compar)(const struct dirent **, const struct dirent **));
int alphasort(const struct dirent **a, const struct dirent **b);
int versionsort(const struct dirent **a, const struct dirent **b);
参数:
dirp: 需要遍历的目录的名字
namelist: 三级指针, 传出参数, 需要在指向的地址中存储遍历目录得到的所有文件的信息
在函数内部会给这个指针指向的地址分配内存,要注意在程序中释放内存
filter: 函数指针, 指针指向的函数就是回调函数, 需要在自定义函数中指定如果过滤目录中的文件
如果不对目录中的文件进行过滤, 该函数指针指定为NULL即可
如果自己指定过滤函数, 满足条件要返回1, 否则返回 0
compar: 函数指针, 对过滤得到的文件进行排序, 可以使用提供的两种排序方式:
alphasort: 根据文件名进行排序
versionsort: 根据版本进行排序
返回值: 函数执行成功返回找到的匹配成功的文件的个数,如果失败返回-1。
3.1 文件过滤
scandir() 可以让使用者自定义文件的过滤方式, 然后将过滤函数的地址传递给 scandir() 的第三个参数,我们可以得知过滤函数的原型是这样的:

C
1
2
// 函数的参数就是遍历的目录中的子文件对应的结构体
int (*filter)(const struct dirent *);
基于这个函数指针定义的函数就可以称之为回调函数, 这个函数不是由程序猿调用, 而是通过 scandir() 调用,因此这个函数的实参也是由 scandir() 函数提供的,作为回调函数的编写人员,只需要搞明白这个参数的含义是什么,然后在函数体中直接使用即可。

假设还是判断目录中某一个文件是否为Mp3格式, 函数实现如下:

C
1
2
3
4
5
6
7
8
9
10
11
12
int isMp3(const struct dirent ptr)
{
if(ptr->d_type == DT_REG)
{
char
p = strstr(ptr->d_name, “.mp3”);
if(p != NULL && *(p+4) == ‘\0’)
{
return 1;
}
}
return 0;
}
3.2 遍历目录
了解了 scandir() 函数的使用之后, 下边写一个程序, 来搜索指定目录下的 mp3格式文件个数和文件名, 代码如下:

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

// 文件过滤函数
int isMp3(const struct dirent ptr)
{
if(ptr->d_type == DT_REG)
{
char
p = strstr(ptr->d_name, “.mp3”);
if(p != NULL && *(p+4) == ‘\0’)
{
return 1;
}
}
return 0;
}

int main(int argc, char* argv[])
{
if(argc < 2)
{
printf(“./a.out path\n”);
return 0;
}
struct dirent **namelist = NULL;
int num = scandir(argv[1], &namelist, isMp3, alphasort);
for(int i=0; i<num; ++i)
{
printf(“file %d: %s\n”, i, namelist[i]->d_name);
free(namelist[i]);
}
free(namelist);
return 0;
}
最后再解析一下 scandir() 的第二个参数,传递的是一个二级指针的地址:

C
1
2
struct dirent **namelist = NULL;
int num = scandir(argv[1], &namelist, isMp3, alphasort);
那么在这个 namelist 中存储的什么类型的数据呢?也就是 struct dirent **namelist 指向的什么类型的数据?

答案: 指向的是一个指针数组 struct dirent *namelist[]

数组元素的个数就是遍历的目录中的文件个数
数组的每个元素都是指针类型: struct dirent *, 指针指向的地址是有 scandir() 函数分配的, 因此在使用完毕之后需要释放内存

作者: 苏丙榅
链接: https://subingwen.cn/linux/directory/
来源: 爱编程的大丙
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值