Як ви повторюєте кожен файл / каталог рекурсивно в стандартному 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
замість цього.
Якщо ви використовуєте 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;
}
Ви можете зробити це ще простіше, використовуючи новий діапазон 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 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;
}
На додаток до вищезазначеної файлової системи 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;
}
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;
}
Ви можете використовувати ftw(3)
абоnftw(3)
ходити по ієрархії файлової системи в C або C ++ на системах POSIX .
nftw()
використання.
Ви цього не робите. Стандарт C ++ не має поняття каталогів. Це залежить від реалізації, щоб перетворити рядок у файлову ручку. Зміст цього рядка і те, що він відображає, залежить від ОС. Майте на увазі, що C ++ можна використовувати для написання цієї ОС, тому вона звикає на рівні, коли запитання про те, як повторити через каталог, ще не визначено (оскільки ви пишете код управління каталогом).
Перегляньте документацію API для ОС, як це зробити. Якщо вам потрібно бути портативним, вам доведеться мати купу #ifdef s для різних ОС.
Вам, мабуть, найкраще буде експериментальна файлова система файлів 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 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;
}
Ви цього не робите. Стандартний C ++ не піддається поняттю каталогу. Зокрема, це не дає жодного способу перерахувати всі файли в каталозі.
Жахливим злом буде використання системних () викликів і для аналізу результатів. Найбільш розумним рішенням буде використання якоїсь бібліотеки між платформами, наприклад, 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 разом з API FindNextFile. Ви можете використовувати 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;
}