Как вы рекурсивно перебираете каждый файл / каталог в стандартном C ++?
Как вы рекурсивно перебираете каждый файл / каталог в стандартном 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;
}
Начиная с 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
вместо этого.
При использовании 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 ++ 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;
}
Быстрое решение - использование библиотеки 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. Другой способ - использовать функцию 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;
}
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 .
nftw()
использования.
Вы этого не сделаете. В стандарте C ++ нет понятия каталогов. Преобразование строки в дескриптор файла зависит от реализации. Содержимое этой строки и то, чему она сопоставляется, зависит от ОС. Имейте в виду, что C ++ можно использовать для написания этой ОС, поэтому он используется на уровне, на котором вопрос о том, как выполнять итерацию по каталогу, еще не определен (поскольку вы пишете код управления каталогом).
Посмотрите документацию по API вашей ОС, чтобы узнать, как это сделать. Если вам нужно быть портативным, вам понадобится набор #ifdef для различных ОС.
Вам, вероятно, лучше всего подойдет либо 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 не определяет никаких функций, связанных с файловой системой.
Мы живем в 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;
}
Вы этого не сделаете. Стандартный C ++ не раскрывает понятие каталога. В частности, он не дает возможности перечислить все файлы в каталоге.
Ужасным взломом было бы использование вызовов system () и анализ результатов. Наиболее разумным решением было бы использовать какую-то кроссплатформенную библиотеку, такую как Qt или даже POSIX .
Вы можете использовать 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;
}
Если вы работаете в Windows, вы можете использовать FindFirstFile вместе с FindNextFile API. Вы можете использовать FindFileData.dwFileAttributes, чтобы проверить, является ли данный путь файлом или каталогом. Если это каталог, вы можете рекурсивно повторить алгоритм.
Здесь я собрал код, в котором перечислены все файлы на машине Windows.
Обход дерева файлов 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;
}