Як ви повторюєте кожен файл / каталог рекурсивно в стандартному C ++?


115

Як ви повторюєте кожен файл / каталог рекурсивно в стандартному C ++?


Ви можете вивчити boost.filesystem boost.org/doc/libs/1_31_0/libs/filesystem/doc/index.htm
Doug T.


1
Це незабаром повинно бути в стандарті через Filesystem TS , з рекурсивним_директоріальним_тератором
Аді Шавіт

Якщо використання стандартної бібліотеки C не заважає викликати програму C ++ як "стандартну", nftw () . Ось практичний приклад
шість-к

2
Хтось, хто знає, що вони роблять, повинен пройти годину, щоб оновити це.
Джош 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
Оновлення стосовно останньої версії підсилення: Якщо хтось наткнеться на цю відповідь, останній прискорення включає підвищення класу зручності :: recursive_directory_iterator, тому писати вищезазначений цикл із явним рекурсивним викликом більше не потрібно. Посилання: boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/…
JasDev

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

3
Це було гарною відповіддю, але тепер, коли <filesystem> є стандартним, краще просто скористатися (див. Інші відповіді для прикладу).
Гатар

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
І чому це? Краще конкретніше, ніж приносити речі, якими ви не користуєтесь.
Аді Шавіт

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

5
<filesystem> більше не є TS. Це частина С ++ 17. Вам, ймовірно, слід відповідно оновити цю відповідь.
Неочікуваний

Примітка для користувачів Mac, для цього потрібно як мінімум OSX 10.15 (Каталіна).
Джастін

45

Якщо ви використовуєте API Win32, ви можете використовувати функції 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
Це приємне нерекурсивне рішення (що іноді зручно!).
джм.

1
Btw, якщо хтось хоче трохи відредагувати програму, щоб прийняти параметр командного рядка argv [1] для шляху замість жорсткого коду ("F: \\ cvsrepos"), підпис для main (int, char) зміниться to wmain (int, wchar_t) так: int wmain (int argc, wchar_t * argv [])
JasDev

1
Дякую, але ця функція не працює з кирилицею. Чи є якийсь спосіб змусити його працювати з кириличними символами на кшталт - б, в, г тощо?
unknown_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
Немає потреби в імпульсі. ОП спеціально просила стандартний c ++.
Крейг Б

23

Швидке рішення - використання бібліотеки C Dirent.h C.

Фрагмент робочого коду з Вікіпедії:

#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 - це позікс.
Vorac

1
На насправді це робить роботу на VC ++ , якщо ви отримуєте порт dirent.h для Visual C ++ Тоні Ronkko. Це FOSS. Я просто спробував це, і це працює.
користувач1741137

10

На додаток до вищезазначеної файлової системи boost :: ви можете вивчити 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;
}

Doxygen використовує QT в якості свого рівня сумісності з ОС. Основні інструменти взагалі не використовують графічний інтерфейс, а лише ті речі, що містяться в каталозі (та інші компетенції).
deft_code

7

Boost :: файлова система забезпечує 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
Що таке "це", будь ласка? Чи не є помилка синтаксису? А як ви годуєте «кінець»? (= як знаємо, ми розібрали весь dir?)
yO_

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


4

Ви цього не робите. Стандарт C ++ не має поняття каталогів. Це залежить від реалізації, щоб перетворити рядок у файлову ручку. Зміст цього рядка і те, що він відображає, залежить від ОС. Майте на увазі, що C ++ можна використовувати для написання цієї ОС, тому вона звикає на рівні, коли запитання про те, як повторити через каталог, ще не визначено (оскільки ви пишете код управління каталогом).

Перегляньте документацію API для ОС, як це зробити. Якщо вам потрібно бути портативним, вам доведеться мати купу #ifdef s для різних ОС.


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
Тільки для файлів. Немає ніяких функцій "показати мені всі файли в каталозі".
1800 р. ІНФОРМАЦІЯ

1
@ 1800: Довідники - це файли.
Гонки легкості по орбіті

2

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

На цьому посиланні є важлива примітка, якщо ви розглядаєте проблеми переносу. Він говорить:

Засоби бібліотеки файлової системи можуть бути недоступними, якщо ієрархічна файлова система недоступна для реалізації або якщо вона не забезпечує необхідні можливості. Деякі функції можуть бути недоступними, якщо вони не підтримуються базовою файловою системою (наприклад, у файловій системі FAT відсутні символічні посилання та забороняється численні жорсткі посилання). У цих випадках потрібно повідомляти про помилки.

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

@ adi-shavit відповів на це питання, коли він був частиною std :: eksperimental, і він оновив цю відповідь у 2017 році. Хочу дати докладнішу інформацію про бібліотеку та показати більш детальний приклад.

std :: fileystem :: recursive_directory_iterator - це LegacyInputIteratorітерація над елементами каталогу_entry каталогу та, рекурсивно, над записами всіх підкаталогів. Порядок ітерації не визначений, за винятком того, що кожен запис каталогу відвідується лише один раз.

Якщо ви не хочете рекурсивно ітерацію по записах підкаталогів, то directory_iterator слід використовувати.

Обидва ітератори повертають об'єкт каталогу_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 ++ не піддається поняттю каталогу. Зокрема, це не дає жодного способу перерахувати всі файли в каталозі.

Жахливим злом буде використання системних () викликів і для аналізу результатів. Найбільш розумним рішенням буде використання якоїсь бібліотеки між платформами, наприклад, 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 разом з API FindNextFile. Ви можете використовувати 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.