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

StackOverflow https://stackoverflow.com/questions/67273

  •  09-06-2019
  •  | 
  •  

Вопрос

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

Это было полезно?

Решение

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

Другие советы

Если вы используете Win32 API, вы можете использовать НайтиFirstFile и НайтиСледующийФайл функции.

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

Вы можете сделать это еще проще с новым С++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;
}

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

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

Быстрое решение — использование 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;
}

В дополнение к вышеупомянутому boost::filesystem вы можете изучить wxWidgets::wxDir и Qt::QDir.

И wxWidgets, и Qt — это кроссплатформенные C++-фреймворки с открытым исходным кодом.

wxDir предоставляет гибкий способ рекурсивного перемещения по файлам, используя Traverse() или более простой GetAllFiles() функция.Также вы можете реализовать обход с помощью 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;
}

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++ на ПОСИКС системы.

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

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

Вероятно, вам лучше всего использовать 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.

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

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

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

Мы в 2019 году.У нас есть файловая система стандартная библиотека в C++Filesystem library предоставляет средства для выполнения операций с файловыми системами и их компонентами, такими как пути, обычные файлы и каталоги.

Есть важное замечание по поводу эта ссылка если вы рассматриваете вопросы переносимости.Там говорится:

Возможности библиотеки файловой системы могут быть недоступны, если иерархическая файловая система недоступна для реализации или если она не предоставляет необходимые возможности.Некоторые функции могут быть недоступны, если они не поддерживаются базовой файловой системой (например,в файловой системе FAT отсутствуют символические ссылки и запрещены множественные жесткие ссылки).В таких случаях необходимо сообщать об ошибках.

Библиотека файловой системы изначально была разработана как boost.filesystem, был опубликован как техническая спецификация ISO/IEC TS 18822:2015 и, наконец, объединен с ISO C++, начиная с C++17.Реализация boost в настоящее время доступна на большем количестве компиляторов и платформ, чем библиотека C++17.

@adi-shavit ответил на этот вопрос, когда он был частью std::experimental, и обновил этот ответ в 2017 году.Хочу подробнее рассказать о библиотеке и показать более подробный пример.

std::filesystem::recursive_directory_iterator является LegacyInputIterator который перебирает элементы каталога_entry каталога и рекурсивно перебирает записи всех подкаталогов.Порядок итерации не указан, за исключением того, что каждая запись каталога посещается только один раз.

Если вы не хотите рекурсивно перебирать записи подкаталогов, то каталог_итератор должен быть использован.

Оба итератора возвращают объект каталог_запись. directory_entry имеет различные полезные функции-члены, такие как is_regular_file, is_directory, is_socket, is_symlink и т. д.А path() функция-член возвращает объект std::filesystem::путь и его можно использовать для получения file extension, filename, root 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;
}

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top