145

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

4

19 回答 19

102

在标准 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;
}
于 2008-09-15T21:44:42.700 回答
96

从 C++17 开始,<filesystem>标头和范围- 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>标头中找到(不再是“实验性的”)。

于 2015-10-01T13:55:00.373 回答
49

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

http://msdn.microsoft.com/en-us/library/aa365200(VS.85).aspx

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

#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;
}
于 2008-09-15T21:47:50.260 回答
33

您可以使用新的基于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;
}
于 2013-10-18T15:11:32.743 回答
26

一个快速的解决方案是使用 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;
}
于 2012-09-02T23:15:00.170 回答
11

除了上面提到的 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;
}
于 2009-09-24T11:11:31.850 回答
9

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;                                    
}
于 2014-05-05T14:52:23.810 回答
9

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

size_t directory_size(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;
}
于 2020-05-28T05:43:44.760 回答
7

您可以在POSIX系统上使用ftw(3)nftw(3)遍历 C 或 C++ 中的文件系统层次结构。

于 2013-11-13T20:06:01.403 回答
6

最好使用 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.
于 2017-03-17T20:54:17.947 回答
6

我们在 2019 年。我们有文件系统标准库C++。它Filesystem library提供了对文件系统及其组件(例如路径、常规文件和目录)执行操作的工具。

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

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

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

@adi-shavit 在 std::experimental 中回答了这个问题,他在 2017 年更新了这个答案。我想提供有关该库的更多详细信息并显示更详细的示例。

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

如果您不想递归迭代子目录的条目,则应使用directory_iterator 。

两个迭代器都返回一个directory_entry对象。directory_entry具有各种有用的成员函数,如, ,is_regular_fileis_directory。该成员函数返回std::filesystem::path的对象,可用于获取, , .is_socketis_symlinkpath()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;
}
于 2019-05-30T10:38:17.013 回答
5

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

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

于 2008-09-15T21:48:21.833 回答
3

您需要为文件系统遍历调用特定于操作系统的函数,例如open()readdir(). C 标准没有指定任何与文件系统相关的函数。

于 2008-09-15T21:42:41.863 回答
2

你没有。标准 C++ 不暴露目录的概念。具体来说,它没有提供任何方法来列出目录中的所有文件。

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

于 2008-09-15T21:47:25.207 回答
2

在 C++17 上,您可以通过以下方式:

#include <filesystem>
#include <iostream>
#include <vector>
namespace fs = std::filesystem;

int main()
{
    std::ios_base::sync_with_stdio(false);
    for (const auto &entry : fs::recursive_directory_iterator(".")) {
        if (entry.path().extension() == ".png") {
            std::cout << entry.path().string() << std::endl;
            
        }
    }
    return 0;
}
于 2020-10-20T10:56:25.730 回答
1

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

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

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

于 2014-05-17T15:34:41.383 回答
0

文件树遍历ftw是一种递归方式,可以将整个目录树挂在路径中。更多细节在这里

注意:您也可以使用fts它可以跳过隐藏文件,如.or...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;
 }
于 2020-07-21T02:25:59.917 回答
0

使用 C++11 for Windows 和 Linux(with experimental/filesystem) 递归获取所有文件名的答案:
对于 Windows:

#include <io.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <windows.h>
void getFiles_w(string path, vector<string>& files) {
    intptr_t hFile = 0; 
    struct _finddata_t fileinfo;  
    string p; 
    if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1) {
        do {
            if ((fileinfo.attrib & _A_SUBDIR)) {
                if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
                    getFiles(p.assign(path).append("/").append(fileinfo.name), files);
            }
            else {
                files.push_back(p.assign(path).append("/").append(fileinfo.name));
            }
        } while (_findnext(hFile, &fileinfo) == 0);
    }
}

对于 Linux:

#include <experimental/filesystem>
bool getFiles(std::experimental::filesystem::path path, vector<string>& filenames) {
    namespace stdfs = std::experimental::filesystem;
    // http://en.cppreference.com/w/cpp/experimental/fs/directory_iterator
    const stdfs::directory_iterator end{} ;
    
    for (stdfs::directory_iterator iter{path}; iter != end ; ++iter) {
        // http://en.cppreference.com/w/cpp/experimental/fs/is_regular_file 
        if (!stdfs::is_regular_file(*iter)) { // comment out if all names (names of directories tc.) are required 
            if (getFiles(iter->path(), filenames)) 
                return true;
        }
        else {
            filenames.push_back(iter->path().string()) ;
            cout << iter->path().string() << endl;  
        }
    }
    return false;
}

请记住在 Linux 中-lstdc++fs编译时链接。g++

于 2020-12-01T12:47:13.583 回答
0

员工 Visual C++ 和 WIN API:

bool Parser::queryDIR(string dir_name) {
    vector<string> sameLayerFiles;
    bool ret = false;
    string dir = "";
    //employee wide char
    dir = dir_name  + "\\*.*";;
    //employee WIN File API
    WIN32_FIND_DATA  fd;
    WIN32_FIND_DATA  fd_dir;
    HANDLE hFind = ::FindFirstFile(getWC(dir.c_str()), &fd);
    HANDLE hFind_dir = ::FindFirstFile(getWC(dir.c_str()), &fd_dir);
    string str_subdir;
    string str_tmp;
    //recursive call for diving into sub-directories
    do {
        if ((fd_dir.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ) {
            //ignore trival file node
            while(true) {
                FindNextFile(hFind_dir, &fd_dir);
                str_tmp = wc2str(fd_dir.cFileName);
                if (str_tmp.compare(".") && str_tmp.compare("..")){
                    break;
                }
            }
            if ((fd_dir.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ) {
                str_subdir = wc2str(fd_dir.cFileName);
                ret = queryDIR(dir_name + "\\" + str_subdir);
            }
        }
    } while(::FindNextFile(hFind_dir, &fd_dir));

    //iterate same layer files
    do { 
        if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
            str_tmp = wc2str(fd.cFileName);
            string fname = dir_name + "\\" + str_tmp;
            sameLayerFiles.push_back(fname);
        }
    } while(::FindNextFile(hFind, &fd));    

    for (std::vector<string>::iterator it=sameLayerFiles.begin(); it!=sameLayerFiles.end(); it++) {
        std::cout << "iterated file:" << *it << "..." << std::endl;
        //Doing something with every file here
    }
    return true;   
}

希望我的代码可以帮助:)

您可以在My GitHub 上查看更多详细信息和程序屏幕截图

于 2021-03-18T09:08:39.250 回答