Linux操作系统上关于目录操作的系统API是readdir(),”man 2 readdir”可以看到:
因该函数会涉及到比较复杂的原理和调用过程,所以标准库已帮我们做了封装。事实上,在上面man结果中也明确说明
“一切皆是文件”是Linux的特点,因此,目录也是如此,只是目录存储的是该目录下所有文件及子目录文件的信息。
1. 打开 / 关闭目录文件
opendir()和closedir()分别是打开和关闭文件,返回一个目录流指针,原型如下:
DIR *opendir(const char *name);
int closedir(DIR *dirp);
opendir()实现操作是打开name所指向的文件(含路径),返回一个目录流指针。close则是关闭dirp所指向的目录流指针。
2. 读取目录内容
2.1 readdir读取目录内容
readdir()的原型为(注意是man 3 readdir)
struct dirent *readdir(DIR *dirp);
函数的功能是每被调用一次,将返回指向指向下一个目录下的文件信息的指针,到了目录结尾则返回NULL。
struct dirent结构体原型为:
struct dirent {
ino_t d_ino; /* inode number */
off_t d_off; /* not an offset; see NOTES */
unsigned short d_reclen; /* length of this record */
unsigned char d_type; /* type of file; not supported
by all filesystem types */
char d_name[256]; /* filename */
};
注意,d_name是一个字符型数组,它是用来存放该目录下的文件名的,而是只是一个文件名的,因此它的功能如上述所言。其实在该函数是用一个全局变量来记录当前遍历目录的位置的,所以它是一个不可重入函数。
范例代码:
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
int main(void)
{
DIR* dir = NULL;
struct dirent *dirp = NULL;
dir = opendir("./dir_test");
if (NULL == dir)
{
perror("opendir");
return -1;
}
while ((dirp = readdir(dir)) != NULL)
{
printf("dirp->d_name = %s\n", dirp->d_name);
}
closedir(dir);
return 0;
}
运行结果:
readdir()函数会列出隐藏文件“.”和“..”,若不想先显示可以加以判断屏蔽之。
因为全局变量存储的是上次遍历的目录下的位置,所以遍历完整个目录后需要对该全局变量清零处理才可以重新遍历目录,用到的函数是:
void rewinddir(DIR *dirp);
2.2 readdir_r
readdir_r()的函数原型为:
int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);
这个函书是readdir函数的可重入版本,其中entry记录了上次遍历到哪一个位置的信息,result则是返回目录下的当前文件的信息,也就是说,readdir_r()实现的操作是打来参数1 dirp 所指向的目录,首次调用时候会初始化第个参数 entry,以表示dirp所用的目录流的当前位置,然后在第3个参数保存该位置存储的目录文件信息。当然,首次调用了就会以上次entry记录的位置偏移1来读取目录文件信息。所以注意要对entry的初始化(为NULL)。
范例代码:
int main(void)
{
DIR* dir = NULL;
struct dirent *dirp = NULL;
// 记录遍历的位置信息
struct dirent *dirp1 = (struct dirent *)malloc(sizeof(struct dirent));
// 记录某位置的目录文件信息
struct dirent *dirp2 = (struct dirent *)malloc(sizeof(struct dirent));
dir = opendir("./dir_test");
if (NULL == dir)
{
perror("opendir");
return -1;
}
while (1)
{
if ((readdir_r(dir, dirp1, &dirp2)) != 0)
{
perror("readdir");
return -1;
}
// 已遍历到目录结尾处
if (NULL == dirp2)
break;
// 不显示隐藏文件.和..
if ('.' == dirp2->d_name[0])
continue;
printf("dirp2->d_name = %s\n", dirp2->d_name);
}
closedir(dir);
free(dirp1);
free(dirp2);
return 0;
}
运行结果:
3. glob()
上面两个函数都是需要一个while循环才能获取该目录信息,其实真正适用的API应该是调用者给获取目录信息的函数一个指针数组,函数返回后这个指针数组装载的就是目录的所有信息,也就是我们可以封装opendir()、readdir()、closedir()函数。我们能够想到,其实库函数设计者也想到了,而且已经封装好了,这就是glob()函数,其原型为:
int glob(const char *pattern, int flags,
int (*errfunc) (const char *epath, int eerrno),
glob_t *pglob);
参数一 pattern它不是一个路径,而是“模式”,也就是诸如“.c”、”./ 等”
参数二 flag是标志,一般我们取默认值0即可;
参数三 errfunc是一个函数指针,它是在glob函数执行出错的时候才调用的,我们也可以直接置为NULL;
参数四 pglob,它的原型为:
typedef struct {
size_t gl_pathc; /* Count of paths matched so far */
char **gl_pathv; /* List of matched pathnames. */
size_t gl_offs; /* Slots to reserve in gl_pathv. */
} glob_t;
gl_pathc和**gl_pathv这两个参数让我们想起传参的main函数原型,在这里的用法确实如此。gl_pathc保存了该目录下文件的个文件,gl_pathv保存了该目录下的所有文件的文件名称。
一个目录下有多少文件,glob()函数肯定在执行前是不知道的,所以这里面对glob_t结构体肯定是用到动态分配空间,所以需要调用glob_t的销毁函数,即
void globfree(glob_t *pglob);
范例代码:
int main(void)
{
glob_t g;
int i;
if (glob("./dir_test/*", 0, NULL, &g))
{
perror("glob");
return 0;
}
for (i = 0; i < g.gl_pathc; i++)
printf("pathv[%d] = %s\n", i, g.gl_pathv[i]);
globfree(&g);
return 0;
}
运行结果:
这个函数的另一强大之处就是会在目录信息中加上路径名,这样对我们接下来的代码操作十分方便。
这个就是glob()函数的基本使用。