您如何在标准C ++中递归地遍历每个文件/目录?


115

您如何在标准C ++中递归地遍历每个文件/目录?


您可能需要检查boost.filesystem boost.org/doc/libs/1_31_0/libs/filesystem/doc/index.htm
Doug T.


1
这应该很快通过Filesystem TS成为标准,带有recursive_directory_iterator
Adi Shavit

如果使用标准C库不会妨碍将C ++程序称为“标准”,请使用nftw()。这里有一个实际的例子
六ķ

2
知道他们在做什么的人应该花一个小时来更新它。
乔什(Josh C)

Answers:


99

在标准C ++中,从技术上讲,由于标准C ++没有目录概念,因此无法执行此操作。如果您想稍微扩展一下网络,可以考虑使用Boost.FileSystem。TR2中已包含此功能,因此,这为您提供了使实现尽可能接近标准的最佳机会。

直接从网站获取的示例:

bool find_file( const path & dir_path,         // in this directory,
                const std::string & file_name, // search for this name,
                path & path_found )            // placing path here if found
{
  if ( !exists( dir_path ) ) return false;
  directory_iterator end_itr; // default construction yields past-the-end
  for ( directory_iterator itr( dir_path );
        itr != end_itr;
        ++itr )
  {
    if ( is_directory(itr->status()) )
    {
      if ( find_file( itr->path(), file_name, path_found ) ) return true;
    }
    else if ( itr->leaf() == file_name ) // see below
    {
      path_found = itr->path();
      return true;
    }
  }
  return false;
}

5
C ++没有文件的概念吗?那么std :: fstream呢?还是开着?
凯文

30
文件,而不是目录
1800信息

22
关于最新的boost版本的更新:万一有人偶然发现了这个答案,最新的boost包含一个便利类boost :: recursive_directory_iterator,因此不再需要使用显式的递归调用编写上述循环。链接: boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/…– JasDev 2011
6

5
VC ++ 11在std :: tr2 :: sys命名空间下的<filesystem>标头中具有几乎相同的功能。
mheyman 2013年

3
以前这是一个很好的答案,但是现在<filesystem>是标准的,所以最好简单地使用is(请参见其他答案作为示例)。
Gathar

53

从C ++ 17开始,可以使用<filesystem>标头和range- for,只需执行以下操作:

#include <filesystem>

using recursive_directory_iterator = std::filesystem::recursive_directory_iterator;
...
for (const auto& dirEntry : recursive_directory_iterator(myPath))
     std::cout << dirEntry << std::endl;

从C ++ 17开始,它std::filesystem是标准库的一部分,可以在<filesystem>标头中找到(不再是“实验性”)。


避免使用usingnamespace而改为使用。
罗伊·丹顿

2
那为什么呢?比引入不使用的东西更具体。
Adi Shavit

请检查我的编辑,我还添加了缺少的命名空间std。
罗伊·丹顿

5
<文件系统>不再是TS。它是C ++ 17的一部分。您可能应该相应地更新此答案。
IInspectable

对于Mac用户请注意,这至少需要OSX 10.15(Catalina)。
贾斯汀

45

如果使用Win32 API,则可以使用FindFirstFileFindNextFile函数。

http://msdn.microsoft.com/zh-CN/library/aa365200(VS.85).aspx

对于目录的递归遍历,必须检查每个WIN32_FIND_DATA.dwFileAttributes,以检查FILE_ATTRIBUTE_DIRECTORY位是否已设置。如果该位置1,则可以使用该目录递归调用该函数。或者,您可以使用堆栈来提供与递归调用相同的效果,但避免在很长的路径树上出现堆栈溢出。

#include <windows.h>
#include <string>
#include <vector>
#include <stack>
#include <iostream>

using namespace std;

bool ListFiles(wstring path, wstring mask, vector<wstring>& files) {
    HANDLE hFind = INVALID_HANDLE_VALUE;
    WIN32_FIND_DATA ffd;
    wstring spec;
    stack<wstring> directories;

    directories.push(path);
    files.clear();

    while (!directories.empty()) {
        path = directories.top();
        spec = path + L"\\" + mask;
        directories.pop();

        hFind = FindFirstFile(spec.c_str(), &ffd);
        if (hFind == INVALID_HANDLE_VALUE)  {
            return false;
        } 

        do {
            if (wcscmp(ffd.cFileName, L".") != 0 && 
                wcscmp(ffd.cFileName, L"..") != 0) {
                if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
                    directories.push(path + L"\\" + ffd.cFileName);
                }
                else {
                    files.push_back(path + L"\\" + ffd.cFileName);
                }
            }
        } while (FindNextFile(hFind, &ffd) != 0);

        if (GetLastError() != ERROR_NO_MORE_FILES) {
            FindClose(hFind);
            return false;
        }

        FindClose(hFind);
        hFind = INVALID_HANDLE_VALUE;
    }

    return true;
}

int main(int argc, char* argv[])
{
    vector<wstring> files;

    if (ListFiles(L"F:\\cvsrepos", L"*", files)) {
        for (vector<wstring>::iterator it = files.begin(); 
             it != files.end(); 
             ++it) {
            wcout << it->c_str() << endl;
        }
    }
    return 0;
}

19
你写了多久了?我认为将C ++粘合到python并在一行中完成将花费更少的时间。
达斯汀·盖兹

2
这是一个很好的非递归解决方案(有时很方便!)。
jm。

1
顺便说一句,如果有人想稍微修改程序以接受命令行参数argv [1]代替硬编码的路径(“ F:\\ cvsrepos”),则main(int,char)的签名将发生变化像这样去wmain(int,wchar_t):int wmain(int argc,wchar_t * argv [])
JasDev 2011年

1
谢谢,但是此功能不适用于Cyrilic。有什么方法可以使它与-б,в,г等西里尔字符一起使用吗?
unresolved_external 2012年

31

您可以使用基于C ++ 11的新范围forBoost使它变得更加简单:

#include <boost/filesystem.hpp>

using namespace boost::filesystem;    
struct recursive_directory_range
{
    typedef recursive_directory_iterator iterator;
    recursive_directory_range(path p) : p_(p) {}

    iterator begin() { return recursive_directory_iterator(p_); }
    iterator end() { return recursive_directory_iterator(); }

    path p_;
};

for (auto it : recursive_directory_range(dir_path))
{
    std::cout << it << std::endl;
}

5
无需提升。OP专门要求使用标准c ++。
Craig B

23

一种快速的解决方案是使用C的Dirent.h库。

维基百科的工作代码片段:

#include <stdio.h>
#include <dirent.h>

int listdir(const char *path) {
    struct dirent *entry;
    DIR *dp;

    dp = opendir(path);
    if (dp == NULL) {
        perror("opendir: Path does not exist or could not be read.");
        return -1;
    }

    while ((entry = readdir(dp)))
        puts(entry->d_name);

    closedir(dp);
    return 0;
}

5
此例程不是递归的。
user501138 2015年

@TimCooper,当然不是,dirent是posix特定的。
Vorac

1
实际上,它确实在VC ++的工作,如果你得到dirent.h的为Visual C端口++托尼Ronkko。是FOSS。我只是试过了,它有效。
user1741137

10

除了上面提到的boost :: filesystem之外,您可能还需要检查wxWidgets :: wxDirQt :: QDir

wxWidgets和Qt都是开源的,跨平台的C ++框架。

wxDir提供了一种使用Traverse()或更简单的GetAllFiles()函数递归遍历文件的灵活方法。您还可以使用GetFirst()GetNext()函数实现遍历(我假设Traverse()和GetAllFiles()是包装器,最终使用GetFirst()和GetNext()函数)。

QDir提供对目录结构及其内容的访问。使用QDir遍历目录有几种方法。您可以使用QDirIterator :: Subdirectories标志实例化的QDirIterator遍历目录内容(包括子目录)。另一种方法是使用QDir的GetEntryList()函数并实现递归遍历。

这是示例代码(从此处#例8-5中获取),显示了如何遍历所有子目录。

#include <qapplication.h>
#include <qdir.h>
#include <iostream>

int main( int argc, char **argv )
{
    QApplication a( argc, argv );
    QDir currentDir = QDir::current();

    currentDir.setFilter( QDir::Dirs );
    QStringList entries = currentDir.entryList();
    for( QStringList::ConstIterator entry=entries.begin(); entry!=entries.end(); ++entry) 
    {
         std::cout << *entry << std::endl;
    }
    return 0;
}

Doxygen使用QT作为其OS兼容性层。核心工具完全不使用GUI,而只使用目录(和其他组件)。
deft_code 2010年

7

Boost :: filesystem提供recursive_directory_iterator,对于此任务非常方便:

#include "boost/filesystem.hpp"
#include <iostream>

using namespace boost::filesystem;

recursive_directory_iterator end;
for (recursive_directory_iterator it("./"); it != end; ++it) {
    std::cout << *it << std::endl;                                    
}

1
请问“它”是什么?没有语法错误吗?而你如何养活“结局”呢?(=怎么知道我们解析了所有目录?)
yO_

1
@yO_您说的没错,recursive_directory_iterator的默认构造函数将构造一个“无效”的迭代器,当您完成对dir的迭代时,它将变为“ it”,并且将变为“ end”
DikobrAz


4

使用boost或c ++ 14的实验性文件系统可能是最好的选择。如果要解析内部目录(即,在程序关闭后用于程序存储数据的内部目录),则创建一个索引文件,其中包含文件内容的索引。顺便说一句,将来您可能需要使用boost,所以如果您没有安装,请安装它!其次,您可以使用条件编译,例如:

#ifdef WINDOWS //define WINDOWS in your code to compile for windows
#endif

每种情况的代码均来自https://stackoverflow.com/a/67336/7077165

#ifdef POSIX //unix, linux, etc.
#include <stdio.h>
#include <dirent.h>

int listdir(const char *path) {
    struct dirent *entry;
    DIR *dp;

    dp = opendir(path);
    if (dp == NULL) {
        perror("opendir: Path does not exist or could not be read.");
        return -1;
    }

    while ((entry = readdir(dp)))
        puts(entry->d_name);

    closedir(dp);
    return 0;
}
#endif
#ifdef WINDOWS
#include <windows.h>
#include <string>
#include <vector>
#include <stack>
#include <iostream>

using namespace std;

bool ListFiles(wstring path, wstring mask, vector<wstring>& files) {
    HANDLE hFind = INVALID_HANDLE_VALUE;
    WIN32_FIND_DATA ffd;
    wstring spec;
    stack<wstring> directories;

    directories.push(path);
    files.clear();

    while (!directories.empty()) {
        path = directories.top();
        spec = path + L"\\" + mask;
        directories.pop();

        hFind = FindFirstFile(spec.c_str(), &ffd);
        if (hFind == INVALID_HANDLE_VALUE)  {
            return false;
        } 

        do {
            if (wcscmp(ffd.cFileName, L".") != 0 && 
                wcscmp(ffd.cFileName, L"..") != 0) {
                if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
                    directories.push(path + L"\\" + ffd.cFileName);
                }
                else {
                    files.push_back(path + L"\\" + ffd.cFileName);
                }
            }
        } while (FindNextFile(hFind, &ffd) != 0);

        if (GetLastError() != ERROR_NO_MORE_FILES) {
            FindClose(hFind);
            return false;
        }

        FindClose(hFind);
        hFind = INVALID_HANDLE_VALUE;
    }

    return true;
}
#endif
//so on and so forth.

3

你不知道 C ++标准没有目录的概念。将字符串转换为文件句柄取决于实现。该字符串的内容及其对应的内容取决于操作系统。请记住,可以使用C ++来编写该OS,因此它的使用级别尚未定义询问如何遍历目录的权限(因为您正在编写目录管理代码)。

请查看您的OS API文档以了解如何执行此操作。如果您需要具有便携性,则必须为各种操作系统使用一堆#ifdef


2

您需要调用操作系统特定的函数来进行文件系统遍历,例如open()readdir()。C标准未指定任何与文件系统相关的功能。


C ++呢?iostream中是否有此类功能?
亚伦·曼帕

2
仅用于文件。没有任何一种“向我显示目录中的所有文件”功能。
1800信息

1
@ 1800:目录是文件。
Lightness Races in Orbit

2

我们在2019年。在中有文件系统标准库C++。的Filesystem library对文件系统及其部件,如路径,常规文件和目录执行操作提供设施。

如果您正在考虑可移植性问题,则在此链接上有一个重要说明。它说:

如果实现无法访问分层文件系统,或者文件系统库工具未提供必要的功能,则文件系统库工具可能不可用。如果基础文件系统不支持某些功能(例如FAT文件系统缺少符号链接,并禁止多个硬链接),则这些功能可能不可用。在这种情况下,必须报告错误。

文件系统库最初开发为boost.filesystem,已发布为技术规范ISO / IEC TS 18822:2015,并最终从C ++ 17合并为ISO C ++。目前,与C ++ 17库相比,boost实现在更多的编译器和平台上可用。

@ adi-shavit已成为std :: experimental的一部分时回答了这个问题,他于2017年更新了此答案。我想提供有关该库的更多详细信息,并显示更多详细的示例。

std :: filesystem :: recursive_directory_iterator是一个LegacyInputIterator遍历目录的directory_entry元素,并递归遍历所有子目录条目的对象。未指定迭代顺序,只是每个目录条目仅被访问一次。

如果您不想递归遍历子目录的条目,则应使用directory_iterator

两个迭代器都返回一个directory_entry对象。directory_entry具有像各种有用的成员函数is_regular_fileis_directoryis_socketis_symlink等。path()成员函数返回的目的的std ::文件系统::路径,它可以被用来获得file extensionfilenameroot name

考虑下面的示例。我一直在使用Ubuntu并在终端上使用它进行编译

g ++ example.cpp --std = c ++ 17 -lstdc ++ fs -Wall

#include <iostream>
#include <string>
#include <filesystem>

void listFiles(std::string path)
{
    for (auto& dirEntry: std::filesystem::recursive_directory_iterator(path)) {
        if (!dirEntry.is_regular_file()) {
            std::cout << "Directory: " << dirEntry.path() << std::endl;
            continue;
        }
        std::filesystem::path file = dirEntry.path();
        std::cout << "Filename: " << file.filename() << " extension: " << file.extension() << std::endl;

    }
}

int main()
{
    listFiles("./");
    return 0;
}

1

你不知道 标准C ++没有公开目录的概念。具体来说,它无法列出目录中的所有文件。

一个可怕的办法是使用system()调用并解析结果。最合理的解决方案是使用某种跨平台的库,例如Qt甚至POSIX


1

您可以使用std::filesystem::recursive_directory_iterator。但是请注意,这包括符号(软)链接。如果要避免它们,可以使用is_symlink。用法示例:

size_t directorySize(const std::filesystem::path& directory)
{
    size_t size{ 0 };
    for (const auto& entry : std::filesystem::recursive_directory_iterator(directory))
    {
        if (entry.is_regular_file() && !entry.is_symlink())
        {
            size += entry.file_size();
        }
    }
    return size;
}

1
最后但并非最不重要的是,实际上比以前的答案要好。
Seyed Mehran Siadati

0

如果您使用的是Windows,则可以将FindFirstFile和FindNextFile API一起使用。您可以使用FindFileData.dwFileAttributes来检查给定路径是文件还是目录。如果是目录,则可以递归地重复该算法。

在这里,我整理了一些代码,列出了Windows计算机上的所有文件。

http://dreams-soft.com/projects/traverse-directory


0

文件树遍历ftw是一种将路径遍历整个目录树的递归方法。更多细节在这里

注意:您也可以使用fts来跳过隐藏的文件,例如....bashrc

#include <ftw.h>
#include <stdio.h>
#include <sys/stat.h>
#include <string.h>

 
int list(const char *name, const struct stat *status, int type)
{
     if (type == FTW_NS)
     {
         return 0;
     }

     if (type == FTW_F)
     {
         printf("0%3o\t%s\n", status->st_mode&0777, name);
     }

     if (type == FTW_D && strcmp(".", name) != 0)
     {
         printf("0%3o\t%s/\n", status->st_mode&0777, name);
     }
     return 0;
}

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

     return 0;
}

输出如下:

0755    ./Shivaji/
0644    ./Shivaji/20200516_204454.png
0644    ./Shivaji/20200527_160408.png
0644    ./Shivaji/20200527_160352.png
0644    ./Shivaji/20200520_174754.png
0644    ./Shivaji/20200520_180103.png
0755    ./Saif/
0644    ./Saif/Snapchat-1751229005.jpg
0644    ./Saif/Snapchat-1356123194.jpg
0644    ./Saif/Snapchat-613911286.jpg
0644    ./Saif/Snapchat-107742096.jpg
0755    ./Milind/
0644    ./Milind/IMG_1828.JPG
0644    ./Milind/IMG_1839.JPG
0644    ./Milind/IMG_1825.JPG
0644    ./Milind/IMG_1831.JPG
0644    ./Milind/IMG_1840.JPG

假设您要匹配文件名(例如:搜索所有*.jpg, *.jpeg, *.png文件。)以满足特定需求,请使用fnmatch

 #include <ftw.h>
 #include <stdio.h>
 #include <sys/stat.h>
 #include <iostream>
 #include <fnmatch.h>

 static const char *filters[] = {
     "*.jpg", "*.jpeg", "*.png"
 };

 int list(const char *name, const struct stat *status, int type)
 {
     if (type == FTW_NS)
     {
         return 0;
     }

     if (type == FTW_F)
     {
         int i;
         for (i = 0; i < sizeof(filters) / sizeof(filters[0]); i++) {
             /* if the filename matches the filter, */
             if (fnmatch(filters[i], name, FNM_CASEFOLD) == 0) {
                 printf("0%3o\t%s\n", status->st_mode&0777, name);
                 break;
             }
         }
     }

     if (type == FTW_D && strcmp(".", name) != 0)
     {
         //printf("0%3o\t%s/\n", status->st_mode&0777, name);
     }
     return 0;
 }

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

     return 0;
 }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.