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


115

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


Вы можете изучить boost.filesystem boost.org/doc/libs/1_31_0/libs/filesystem/doc/index.htm
Дуг Т.

1
Нестандартный C ++: pocoproject.org/docs/Poco.DirectoryIterator.html
Agnel

1
Скоро это должно быть в стандарте через файловую систему TS , с recursive_directory_iterator
Ади Шавит

Если использование стандартной библиотеки C не мешает вызову программы C ++ как «стандартной», используется nftw () . Вот практический пример
six-k

2
Кто-то, кто знает, что делает, должен потратить час, чтобы обновить это.
Josh C

Ответы:


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? Или fopen?
Кевин,

30
файлы, а не каталоги
1800 ИНФОРМАЦИЯ

22
Обновление в отношении последней версии повышения: в случае, если кто-то наткнется на этот ответ, последнее повышение включает в себя вспомогательный класс boost :: recursive_directory_iterator, поэтому писать вышеуказанный цикл с явным рекурсивным вызовом больше не нужно. Ссылка: boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/…
JasDev 05

5
VC ++ 11 имеет почти такую ​​же функциональность в заголовке <filesystem> в пространстве имен std :: tr2 :: sys.
mheyman

3
Раньше это был хороший ответ, но теперь, когда <filesystem> является стандартным, лучше просто использовать is (см. Другие ответы для примера).
Gathar

54

Начиная с 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>заголовке (больше не «экспериментальный»).


Избегайте использования using, используйте namespaceвместо этого.
Рой Дантон

2
И почему так? Лучше конкретнее, чем приносить вещи, которыми вы не пользуетесь.
Ади Шавит

Просмотрите мою правку, я также добавил отсутствующее пространство имен std.
Рой Дантон

5
<filesystem> больше не является TS. Это часть C ++ 17. Вероятно, вам следует соответствующим образом обновить этот ответ.
Inspectable

Примечание для пользователей Mac: для этого требуется как минимум OSX 10.15 (Catalina).
Джастин

45

При использовании 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;
}

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 02

1
Спасибо, но с кириллицей эта функция не работает. Есть ли способ заставить его работать с кириллическими символами, такими как - б, в, г и т. Д.?
unresolved_external

31

Вы можете сделать это еще проще с помощью нового диапазона C ++ 11for и Boost :

#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

@TimCooper, конечно, нет, dirent специфичен для posix.
Vorac

1
На самом деле это делает работу на VC ++ , если вы получаете порт dirent.h для Visual C ++ Тони Ronkko. Это FOSS. Я просто попробовал это, и это работает.
user1741137 02

10

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

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

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

QDirобеспечивает доступ к структурам каталогов и их содержимому. Есть несколько способов перемещаться по каталогам с помощью QDir. Вы можете перебирать содержимое каталога (включая подкаталоги) с помощью QDirIterator, экземпляр которого был создан с помощью флага QDirIterator :: Subdirectories. Другой способ - использовать функцию 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 в качестве уровня совместимости с ОС. Основные инструменты не используют графический интерфейс, только каталог (и другие компоненты).
deft_code 09

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
Что такое "это", пожалуйста? Нет ли синтаксической ошибки? А как кормить "конец"? (= откуда мы знаем, что мы
разобрали весь каталог

1
@yO_, вы правы, произошла опечатка, конструктор по умолчанию для recursive_directory_iterator создаст «недопустимый» итератор, когда вы закончите итерацию по dir, он превратится в «it» станет недействительным и будет равно «end»
DikobrAz


4

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

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


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.

2

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


А как насчет C ++? Есть ли в iostream такие функции?
Аарон Маенпаа,

2
Только для файлов. Нет никаких функций типа «показать мне все файлы в каталоге».
ИНФОРМАЦИЯ 18

1
@ 1800: Каталоги - это файлы.
Гонки легкости на орбите

2

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

По этой ссылке есть важное примечание, если вы рассматриваете проблемы переносимости. Он говорит:

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

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

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

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

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

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

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
И последнее, но не менее важное: на самом деле лучше, чем предыдущие ответы.
Сейед Мехран Сиадати,

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;
 }
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.