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

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

209873 次浏览

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

在标准 C + + 中,由于标准 C + + 没有目录的概念,所以在技术上没有办法做到这一点。如果您想稍微扩展一下网络,可以考虑使用 文件系统。这已被接受纳入 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;
}

你不知道。标准的 C + + 不公开目录的概念。具体来说,它没有提供任何方式来列出一个目录中的所有文件。

使用 system ()调用和解析结果是一种可怕的黑客行为。最合理的解决方案是使用某种跨平台库,比如 QT甚至 POSIX

如果使用 Win32API,可以使用 FindFirstFileFindNextFile函数。

Http://msdn.microsoft.com/en-us/library/aa365200(vs.85).aspx

对于目录的递归遍历,必须检查每个 WIN32 _ FIND _ DATA. dwFileAttritribute以检查是否设置了 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;
}

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

查看操作系统 API 文档了解如何做到这一点。如果您需要可移植性,那么您将不得不为各种操作系统准备一些 # ifdefs。

除了上面提到的升级: : 文件系统之外,您可能还需要检查 WxWidgets: : wxDirQDir

WxWidgets 和 Qt 都是开源的、跨平台的 C + + 框架。

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

QDir提供对目录结构及其内容的访问。使用 QDir 遍历目录有几种方法。您可以使用 QDirIterator 对目录内容(包括子目录)进行迭代,QDirIterator 是用 QDirIterator: : Subdirectory 标志实例化的。另一种方法是使用 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;
}

一个快速的解决方案是使用 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;
}

你可以使它更简单与新的 C + + 11范围为基础的 for加油:

#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;
}

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

文件系统提供了 cursive _ direct _ 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;
}

如果在 Windows 上,则可以将 FindFirstFile 与 FindNextFileAPI 一起使用。可以使用 FindFileData.dwFileAttritribute 检查给定路径是文件还是目录。如果它是一个目录,您可以递归地重复这个算法。

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

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

从 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>头文件中找到(不再是“实验性的”)。

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

#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.

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

如果你正在考虑可移植性问题,那么关于 这个链接有一个重要的注意事项:

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

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

@ adi-shavit 回答了这个问题,当它是性传播疾病实验的一部分时,他在2017年更新了这个答案。我想给出更多关于该库的细节,并展示更详细的示例。

是一个 LegacyInputIterator,它遍历目录的目录 _ entry 元素,并递归遍历所有子目录的条目。迭代顺序未指定,但每个目录条目只访问一次。

如果不希望递归地遍历子目录的条目,那么应该使用 目录 _ 迭代器

两个迭代器都返回 目录 _ entry对象。directory_entry有各种有用的成员函数,如 is_regular_fileis_directoryis_socketis_symlink等。path()成员函数返回一个 is_regular_file0的对象,它可以用来获得 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;
}

你可以使用 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;
}

文件树遍历 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;
}

在 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;
}

用 C + + 11递归获取 Windows 和 Linux 下所有文件名的答案(使用 experimental/filesystem) :
视窗:

#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 中用 g++编译 -lstdc++fs时链接 -lstdc++fs即可。

员工 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;
}

希望我的代码能有所帮助:)

你可以在 我的 GitHub上看到更多的细节和程序屏幕截图