Как вы перебираете каждый файл/каталог рекурсивно в стандартном C++?


Как вы перебираете каждый файл/каталог рекурсивно в стандартном C++?

14 92

14 ответов:

в стандартном 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;
}

при использовании Win32 API вы можете использовать FindFirstFile и FindNextFile функции.

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

вы можете сделать его еще проще с новым C++11

быстрое решение использует 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/14 с "файловой системой TS",<experimental/filesystem> заголовок и диапазон-for вы можете просто сделать это:

#include <experimental/filesystem>

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

обновление: начиная с C++17,std::filesystem является частью стандартной библиотеки, и может быть найден в <filesystem> заголовок (больше не "экспериментальный").

в дополнение к вышеупомянутой boost:: файловая система, которую вы можете изучить wxWidgets:: wxDir и Qt:: QDir.

оба wxWidgets и Qt являются открытым исходным кодом, кросс-платформенные c++ фреймворки.

wxDir обеспечивает гибкий способ рекурсивного перемещения файлов с помощью Traverse() или проще . Также вы можете реализовать обход с помощью GetFirst() и GetNext() функции (я предполагаю, что Traverse() и GetAllFiles () являются обертки, которые в конечном итоге используют функции GetFirst() и GetNext ().

QDir обеспечивает доступ к структурам каталогов и их содержимому. Существует несколько способов обхода каталогов с помощью QDir. Вы можете перебирать содержимое каталога (включая подкаталоги) с помощью QDirIterator, экземпляр которого был создан с флагом Qdiriterator::Subdirectories. Другой способ-использовать функцию GetEntryList() QDir и реализовать рекурсивный обход.

вот пример кода (взято от здесь # пример 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;
}

вы можете изучить импульс.файловая система

http://www.boost.org/doc/libs/1_31_0/libs/filesystem/doc/index.htm

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

можно использовать ftw(3) или nftw(3) чтобы пройти иерархию файловой системы в C или C++ на POSIX систем.

вам нужно вызвать специфические для ОС функции для обхода файловой системы, например open() и readdir(). Стандарт C не определяет файловую систему функций.

вы не. Стандарт C++ не имеет понятия о каталогах. Это зависит от реализации, чтобы превратить строку в дескриптор файла. Содержимое этой строки и то, что она отображает, зависит от ОС. Имейте в виду, что C++ можно использовать для написания этой ОС, поэтому он используется на уровне, где вопрос о том, как перебирать каталог, еще не определен (потому что вы пишете код управления каталогом).

посмотрите на свою документацию по API ОС, как это сделать. Если вам нужно быть портативный, вы должны будете иметь кучу #ifdefs для различных ОС.

Если вы находитесь в Windows, вы можете использовать FindFirstFile вместе с FINDNEXTFILE API. Вы можете использовать FindFileData.dwFileAttributes, чтобы проверить, является ли данный путь файлом или каталогом. Если это каталог, вы можете рекурсивно повторить алгоритм.

здесь я собрал некоторый код, который перечисляет все файлы на машине Windows.

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

вероятно, вам будет лучше всего использовать либо boost, либо экспериментальную файловую систему c++14. Если вы анализируете внутренний каталог (т. е. используется для вашей программы для хранения данных после закрытия программы), затем создайте индексный файл с индексом содержимого файла. Кстати, вам, вероятно, нужно будет использовать boost в будущем, поэтому, если у вас его нет, установите его! Во-вторых, вы можете использовать условную компиляцию:

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

код 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.

вы не. Стандарт C++ не подвергайте концепции каталога. В частности, он не дает никакого способа перечислить все файлы в каталоге.

ужасный хак будет использовать вызовы system () и анализировать результаты. Наиболее разумным решением было бы использовать какую-то кросс-платформенную библиотеку, такую как Qt или даже POSIX.