C++

Linux下的dirent.h与C++17的std::filesystem命名空间

Posted by r3kind1e on October 14, 2021

Linux下的dirent.h与C++17的std::filesystem命名空间

实例

方法一:UNIX下的dirent.h

例子1:列出一个目录中所有文件的名字,下面代码是ls -aR命令的简要实现。

我们包含一个系统头文件dirent.h,以便使用opendirreaddir的函数原型,以及dirent结构的定义。

因为各种不同UNIX系统目录项的实际格式是不一样的,所以使用函数opendirreaddirclosedir对目录进行处理。

opendir函数返回指向DIR结构的指针,我们将指针传送给readdir函数。我们并不关心DIR结构中包含了什么。然后,在循环中调用readdir来读每个目录项。它返回一个指向dirent结构的指针,而当目录中已无目录项可读时则返回null指针。在dirent结构中取出的只是每个目录项的名字(d_name)。使用该名字,此后就可以调用stat函数以获得该文件的所有属性。

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
#include <unistd.h>
#include <cstdio>
#include <dirent.h>
#include <cstring>
#include <sys/stat.h>
#include <cstdlib>

void printdir(char *dir, int depth)
{
    DIR *dp;
    struct dirent *entry;
    struct stat statbuf;

    if ((dp = opendir(dir)) == NULL){
        fprintf(stderr, "Can't open directory %s\n", dir);
        return ;
    }
    chdir(dir);
    while((entry = readdir(dp)) != NULL){
        lstat(entry->d_name, &statbuf);
        if (S_ISDIR(statbuf.st_mode)){
            if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
                continue;
            printf("%*s%s\n", depth, "", entry->d_name);
            printdir(entry->d_name, depth+4);;
        }else
            printf("%*s%s\n", depth, "", entry->d_name);
    }
    chdir("..");
    closedir(dp);
}

int main(int argc, char *argv[])
{
    char *topdir = ".";
    if (argc >= 2)
        topdir = argv[1];

    printf("Directory scan of %s\n", topdir);
    printdir(topdir, 0);
    printf("done.\n");
    exit(0);
}

例子2:在小而简单的任务中,使用dirent.h。它可用作 UNIX 中的标准头文件,也可通过 Toni Ronkko 创建的兼容层 用于Windows 。以下代码的功能类似于ls -a

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <cstdio>
#include <dirent.h>
#include <cstdlib>

int main(int argc, char *argv[])
{
    DIR *dir;
    struct dirent *ent;
    if ((dir = opendir("/root")) != NULL){
        /* 打印目录中的所有文件和目录 */
        while((ent = readdir(dir)) != NULL){
            printf("%s\n", ent->d_name);
        }
        closedir(dir);
    }else{
        /* 无法打开目录 */
        perror("");
        return EXIT_FAILURE;
    }
}

获取目录中的文件列表的算法:

1
2
3
4
5
6
7
8
9
10
开始
    声明一个指向 DIR 类型的指针 dr
    声明另一个指向 dirent 结构的指针 en
    调用 opendir() 函数打开当前目录下的所有文件。
     dr 指针初始化为 dr = opendir(".")
    ifdr
       while ((en = readdir(dr)) != NULL)
          使用 en->d_name 打印所有文件名。
       调用 closedir() 函数关闭目录。
结尾。 
1
2
3
4
5
6
7
8
9
10
Begin
   Declare a poniter dr to the DIR type.
   Declare another pointer en of the dirent structure.
   Call opendir() function to open all file in present directory.
   Initialize dr pointer as dr = opendir(".").
   If(dr)
      while ((en = readdir(dr)) != NULL)
         print all the file name using en->d_name.
      call closedir() function to close the directory.
End.

例子3:

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
/*
 * 演示基本目录列表的示例。
 *
 * 使用 Visual Studio 编译此文件,并在控制台中使用目录名称参数运行生成的命令。 例如,命令
 *
 * ls "c:\Program Files"
 *
 * 可能会输出类似的东西
 *
 *     ./
 *     ../
 *     7-Zip/
 *     Internet Explorer/
 *     Microsoft Visual Studio 9.0/
 *     Microsoft.NET/
 *     Mozilla Firefox/
 *
 * 该文件提供的ls命令只是一个例子:该命令在Linux中没有像“ls -al”这样的花哨选项,并且该命令不支持像“ls *.c”这样的文件名匹配。
 *
 */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>
#include <locale.h>

static void list_directory(const char* dirname);

int main(int argc, char* argv[])
{
	/* 选择默认语言环境 */
	setlocale(LC_ALL, "LC_CTYPE=.utf8");

	/* 对于命令行中的每个目录 */
	int i = 1;
	while (i < argc)
	{
		list_directory(argv[i]);
		i++;
	}

	/* 如果命令行上没有参数,则列出当前工作目录 */
	if (argc == 1)
	{
		list_directory(".");
	}

	return EXIT_SUCCESS;
}

/* 列出目录中的文件和目录 */
static void list_directory(const char* dirname)
{
	/*打开目录流 */
	DIR* dir = opendir(dirname);
	if (!dir)
	{
		/* 无法打开目录 */
		fprintf(stderr, "Cannot open %s (%s)\n", dirname, strerror(errno));
		exit(EXIT_FAILURE);
	}

	/* 打印目录中的所有文件和目录 */
	struct dirent* ent;
	while ((ent = readdir(dir)) != NULL)
	{
		switch (ent->d_type) {
		case DT_REG:
			printf("%s\n", ent->d_name);
			break;
		case DT_DIR:
			printf("%s\n", ent->d_name);
			break;
		case DT_LNK:
			printf("%s@\n", ent->d_name);
			break;
		default:
			printf("%s*\n", ent->d_name);
		}
	}

	closedir(dir);
}

方法二:跨平台,C++ 17的std::filesystem列出文件系统的文件

在 C++17 中,现在有一种正式的方法来列出文件系统的文件:std::filesystem::directory_iterator。此外,std::filesystem::recursive_directory_iterator也可以迭代子目录。

注意:

需要在Visual Studio中启用C++ 17编译,在项目>属性>C/C++语言>C++语言标准下,有一些可用选项:

  • ISO C++14 标准。msvc 命令行选项:/std:c++14
  • ISO C++17 标准。msvc 命令行选项:/std:c++17
  • ISO C++20 标准。msvc 命令行选项:/std:c++20
  • 最新的标准草案。msvc 命令行选项:/std:c++latest
1
2
3
4
5
6
7
8
9
10
#include <string>
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;

int main() {
    std::string path = "C:\\Develop";
    for (const auto& entry : fs::directory_iterator(path))
        std::cout << entry.path() << std::endl;
}

如果你使用CLion,需要将CMakeLists.txt中的set(CMAKE_CXX_STANDARD 14)修改为set(CMAKE_CXX_STANDARD 17)

cmake_minimum_required(VERSION 3.20)
project(untitled1)

set(CMAKE_CXX_STANDARD 17)

add_executable(untitled1 main.cpp)

dirent.h详解

opendir

名字

opendir - 打开一个目录

概要

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

DIR *opendir(const char *dirname)

描述

opendir() 函数打开与由 dirname 参数命名的目录相对应的目录流。 目录流位于第一个条目。 如果使用文件描述符实现 DIR 类型,则应用程序最多只能打开 {OPEN_MAX} 个文件和目录。 对任何 exec 函数的成功调用将关闭在调用过程中打开的任何目录流。

返回值

成功完成后,opendir() 返回一个指向 DIR 类型对象的指针。 否则,返回空指针并设置 errno 以指示错误。

错误

如果出现以下情况,opendir() 函数将失败: [EACCES] dirname 路径前缀部分的搜索权限被拒绝,或者dirname 的读取权限被拒绝。 [ELOOP] 解析路径时遇到太多符号链接。 [ENAMETOOLONG] dirname 参数的长度超过 {PATH_MAX},或者路径名组件长于 {NAME_MAX}[ENOENT] dirname 的组件未命名现有目录或 dirname 是空字符串。 [ENOTDIR] dirname 的组成部分不是目录。 如果出现以下情况,opendir() 函数可能会失败:

[EMFILE] {OPEN_MAX} 个文件描述符当前在调用进程中打开。 [ENAMETOOLONG] 符号链接的路径名解析产生了长度超过 {PATH_MAX} 的中间结果。 [ENFILE] 系统中当前打开的文件过多。

readdir

名字

readdir, readdir_r - 读取目录

概要

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

struct dirent *readdir(DIR *dirp);
int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);

描述

DIR 类型定义在头部 <dirent.h> 中,表示目录流,它是特定目录中所有目录条目的有序序列。目录条目代表文件;文件可以从目录中删除或添加到目录中,以与 readdir() 的操作异步。 readdir() 函数返回一个指向结构的指针,该结构表示由参数 dirp 指定的目录流中当前位置的目录条目,并将目录流定位在下一个条目。它在到达目录流的末尾时返回一个空指针。 <dirent.h> 头文件定义的结构 dirent 描述了一个目录条目。

如果存在dotdot-dot条目,dot返回1个条目,dot-dot返回1个条目;否则他们将不会被退回。

readdir() 返回的指针指向的数据可能会被同一目录流上的另一个readdir() 调用覆盖。此数据不会被另一个目录流上的readdir() 调用覆盖。

如果在最近一次调用 opendir()rewinddir() 之后从目录中删除或添加文件,则未指定对 readdir() 的后续调用是否返回该文件的条目。

readdir() 函数可能会在每次实际读取操作时缓冲多个目录条目; readdir() 标记每次实际读取目录时更新目录的 st_atime 字段。

在调用 fork() 之后,父进程或子进程(但不是两者)可以继续使用 readdir()rewinddir()seekdir()处理目录流。如果父进程和子进程都使用这些函数,则结果未定义。

如果条目命名一个符号链接,则 d_ino 成员的值是未指定的。

readdir() 接口不需要可重入。

readdir_r()函数初始化entry引用的dirent结构,表示dirp引用的目录流中当前位置的目录条目,在result引用的位置存储指向该结构的指针,并将目录流定位在dirp引用的位置下一个条目。

entry 指向的存储空间对于具有至少包含{NAME_MAX} 和一个元素的 char d_name 成员数组的目录来说足够大。

成功返回时,在 *result 处返回的指针将具有与参数条目相同的值。在到达目录流的末尾时,该指针的值为 NULL

readdir_r() 函数不会返回包含空名称的目录条目。未指定是否为点或点点返回条目。

如果在最近一次调用 opendir()rewinddir() 之后从目录中删除或添加文件,则未指定对 readdir_r() 的后续调用是否返回该文件的条目。

readdir_r() 函数可能会在每次实际读取操作时缓冲多个目录条目; readdir_r() 函数标记每次实际读取目录时更新目录的 st_atime 字段。

希望检查错误情况的应用程序应在调用 readdir() 之前将 errno 设置为 0。如果 errno 在返回时设置为非零,则发生错误。

返回值

成功完成后, readdir() 返回一个指向 struct dirent 类型对象的指针。 当遇到错误时,返回空指针并设置 errno 以指示错误。 当遇到目录末尾时,返回一个空指针并且 errno 不变。 如果成功,则 readdir_r() 函数返回零。 否则,返回错误编号以指示错误。

错误

在以下情况下 readdir() 函数将失败: [EOVERFLOW] 要返回的结构中的值之一无法正确表示。

在以下情况下 readdir() 函数可能会失败:

[EBADF] dirp 参数不引用打开的目录流。 [ENOENT] 目录流的当前位置无效。

如果出现以下情况, readdir_r() 函数可能会失败:

[EBADF] dirp 参数不引用打开的目录流。

例子

以下示例代码将在当前目录中搜索条目名称:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
dirp = opendir(".");

while (dirp) {
    errno = 0;
    if ((dp = readdir(dirp)) != NULL) {
        if (strcmp(dp->d_name, name) == 0) {
            closedir(dirp);
            return FOUND;
        }
    } else {
        if (errno == 0) {
            closedir(dirp);
            return NOT_FOUND;
        }
        closedir(dirp);
        return READ_ERROR;
    }
}

return OPEN_ERROR;

closedir

名字

closedir - 关闭目录流

概要

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

int closedir(DIR *dirp);

描述

closedir() 函数关闭由参数 dirp 引用的目录流。 返回时,dirp 的值可能不再指向 DIR 类型的可访问对象。 如果文件描述符用于实现类型 DIR,则该文件描述符将被关闭。

返回值

成功完成后, closedir() 返回 0。否则,返回 -1 并设置 errno 以指示错误。

错误

如果出现以下情况,closedir() 函数可能会失败: [EBADF] dirp 参数不引用打开的目录流。 [EINTR] closedir() 函数被信号中断。

dirent.h

名字

dirent.h - 目录条目的格式

概要

1
#include <dirent.h>

描述

目录的内部格式未指定。

<dirent.h> 头文件通过 typedef 定义了以下数据类型:

DIR

表示目录流的类型。

它还定义了结构 dirent,其中包括以下成员:

1
2
ino_t  d_ino       文件序列号
char   d_name[]    条目名称

ino_t 类型的定义如 <sys/types.h> 中所述。

字符数组d_name 的大小未指定,但终止空字节之前的字节数不会超过 {NAME_MAX}

以下声明为函数,也可以定义为宏。 必须提供函数原型以供 ISO C 编译器使用。

1
2
3
4
5
6
7
int            closedir(DIR *);
DIR           *opendir(const char *);
struct dirent *readdir(DIR *);
int            readdir_r(DIR *, struct dirent *, struct dirent **);
void           rewinddir(DIR *);
void           seekdir(DIR *, long int);
long int       telldir(DIR *);

参考

Linux下DIR,dirent,stat等结构体详解

opendir

readdir

closedir

dirent.h

How can I get the list of files in a directory using C or C++?

c++17 filesystem is not a namespace-name

How to enable C++17 compiling in Visual Studio?

C++17 support in CLion

dirent/examples/ls.c