Jak rekurencyjnie przeglądać każdy plik / katalog w standardowym C ++?
Jak rekurencyjnie przeglądać każdy plik / katalog w standardowym C ++?
Odpowiedzi:
W standardowym C ++ technicznie nie da się tego zrobić, ponieważ standardowy C ++ nie ma koncepcji katalogów. Jeśli chcesz trochę rozszerzyć swoją sieć, możesz spojrzeć na użycie Boost.FileSystem . Zostało to zaakceptowane do włączenia do TR2, więc daje to największą szansę na utrzymanie implementacji jak najbliżej standardu.
Przykład zaczerpnięty prosto ze strony:
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;
}
Począwszy od C ++ 17, <filesystem>
nagłówek i zakres- for
, możesz po prostu zrobić to:
#include <filesystem>
using recursive_directory_iterator = std::filesystem::recursive_directory_iterator;
...
for (const auto& dirEntry : recursive_directory_iterator(myPath))
std::cout << dirEntry << std::endl;
Od C ++ 17 std::filesystem
jest częścią biblioteki standardowej i można go znaleźć w <filesystem>
nagłówku (nie jest już „eksperymentalny”).
using
, użyj namespace
zamiast tego.
W przypadku korzystania z interfejsu API Win32 można używać funkcji FindFirstFile i FindNextFile .
http://msdn.microsoft.com/en-us/library/aa365200(VS.85).aspx
W przypadku cyklicznego przeglądania katalogów należy sprawdzić wszystkie atrybuty WIN32_FIND_DATA.dwFileAttributes, aby sprawdzić, czy bit FILE_ATTRIBUTE_DIRECTORY jest ustawiony. Jeśli bit jest ustawiony, możesz rekurencyjnie wywołać funkcję z tym katalogiem. Alternatywnie możesz użyć stosu, aby zapewnić ten sam efekt wywołania rekurencyjnego, ale uniknąć przepełnienia stosu dla bardzo długich drzew ścieżek.
#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;
}
Możesz to jeszcze bardziej uprościć dzięki nowej gamie opartej na C ++ 11for
i 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;
}
Szybkim rozwiązaniem jest użycie biblioteki Dirent.h języka C.
Działający fragment kodu z Wikipedii:
#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;
}
Oprócz wspomnianego powyżej boost :: filesystem możesz chcieć sprawdzić wxWidgets :: wxDir i Qt :: QDir .
Zarówno wxWidgets, jak i Qt są open source, wieloplatformowymi frameworkami C ++.
wxDir
zapewnia elastyczny sposób przechodzenia między plikami rekurencyjnie przy użyciu Traverse()
lub prostszej GetAllFiles()
funkcji. Możesz również zaimplementować przechodzenie z funkcjami GetFirst()
i GetNext()
(zakładam, że Traverse () i GetAllFiles () są opakowaniami, które ostatecznie używają funkcji GetFirst () i GetNext ()).
QDir
zapewnia dostęp do struktur katalogów i ich zawartości. Istnieje kilka sposobów przeglądania katalogów za pomocą QDir. Możesz iterować po zawartości katalogu (w tym podkatalogów) za pomocą QDirIterator, którego instancja została utworzona z flagą QDirIterator :: Subdirectories. Innym sposobem jest użycie funkcji GetEntryList () QDir i zaimplementowanie przechodzenia rekurencyjnego.
Oto przykładowy kod (pobrany stąd # Przykład 8-5), który pokazuje, jak iterować po wszystkich podkatalogach.
#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 zapewnia recursive_directory_iterator, co jest dość wygodne w przypadku tego zadania:
#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;
}
Możesz używać ftw(3)
lubnftw(3)
do poruszania się po hierarchii systemu plików w C lub C ++ w systemach POSIX .
nftw()
użytku.
Ty nie. W standardzie C ++ nie ma koncepcji katalogów. Od implementacji zależy, czy ciąg znaków zostanie przekształcony w uchwyt pliku. Zawartość tego ciągu i to, na co jest mapowany, zależy od systemu operacyjnego. Pamiętaj, że C ++ może być użyty do napisania tego systemu operacyjnego, więc jest używany na poziomie, na którym pytanie, jak iterować przez katalog, nie jest jeszcze zdefiniowane (ponieważ piszesz kod zarządzania katalogami).
Zajrzyj do dokumentacji interfejsu API systemu operacyjnego, aby dowiedzieć się, jak to zrobić. Jeśli chcesz być przenośny, będziesz musiał mieć kilka #ifdef dla różnych systemów operacyjnych.
Prawdopodobnie byłbyś najlepszy z eksperymentalnym systemem plików boost lub c ++ 14. JEŚLI analizujesz katalog wewnętrzny (tj. Używany przez program do przechowywania danych po zamknięciu programu), utwórz plik indeksowy, który będzie zawierał indeks zawartości pliku. Nawiasem mówiąc, prawdopodobnie będziesz musiał użyć boost w przyszłości, więc jeśli go nie masz, zainstaluj go! Po drugie, możesz użyć kompilacji warunkowej, np .:
#ifdef WINDOWS //define WINDOWS in your code to compile for windows
#endif
Kod dla każdego przypadku pochodzi z 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.
Musisz wywołać funkcje specyficzne dla systemu operacyjnego do przechodzenia przez system plików, takie jak open()
i readdir()
. Standard C nie określa żadnych funkcji związanych z systemem plików.
Jesteśmy w 2019 roku. Mamy bibliotekę standardowych systemów plików w C++
. PlikFilesystem library
Zapewnia zaplecze do przeprowadzania operacji na systemie plików i ich składników, takich jak ścieżki, zwykłych plików i katalogów.
Pod tym linkiem znajduje się ważna uwaga, jeśli rozważasz problemy z przenoszeniem. To mówi:
Funkcje biblioteki systemu plików mogą być niedostępne, jeśli hierarchiczny system plików nie jest dostępny dla implementacji lub jeśli nie zapewnia niezbędnych możliwości. Niektóre funkcje mogą nie być dostępne, jeśli nie są obsługiwane przez podstawowy system plików (np. System plików FAT nie ma dowiązań symbolicznych i zabrania wielu dowiązań twardych). W takich przypadkach należy zgłosić błędy.
Biblioteka systemu plików została pierwotnie opracowana jako boost.filesystem
, została opublikowana jako specyfikacja techniczna ISO / IEC TS 18822: 2015, a ostatecznie połączona z ISO C ++ od C ++ 17. Implementacja przyspieszenia jest obecnie dostępna na większej liczbie kompilatorów i platform niż biblioteka C ++ 17.
@ adi-shavit odpowiedział na to pytanie, gdy było ono częścią std :: experimental i zaktualizował tę odpowiedź w 2017 roku. Chcę podać więcej szczegółów na temat biblioteki i pokazać bardziej szczegółowy przykład.
std :: filesystem :: recursive_directory_iterator to LegacyInputIterator
iteracja po elementach directory_entry katalogu i rekurencyjnie po wpisach wszystkich podkatalogów. Kolejność iteracji jest nieokreślona, z wyjątkiem tego, że każda pozycja katalogu jest odwiedzana tylko raz.
Jeśli nie chcesz rekurencyjnie iterować po wpisach podkatalogów, powinieneś użyć directory_iterator .
Oba iteratory zwracają obiekt directory_entry . directory_entry
ma różne funkcje przydatne członków jak is_regular_file
, is_directory
, is_socket
, is_symlink
itd path()
funkcja członek zwraca obiekt std :: :: ścieżki systemu plików i może być używana do pobierania file extension
, filename
,root name
.
Rozważ poniższy przykład. Używam Ubuntu
i skompilowane go na terminal za pomocą
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;
}
Ty nie. Standardowy C ++ nie ujawnia koncepcji katalogu. W szczególności nie daje możliwości wyświetlenia wszystkich plików w katalogu.
Okropnym hackiem byłoby użycie wywołań system () i przeanalizowanie wyników. Najbardziej rozsądnym rozwiązaniem byłoby użycie jakiejś wieloplatformowej biblioteki, takiej jak Qt lub nawet POSIX .
Możesz użyć std::filesystem::recursive_directory_iterator
. Ale uwaga, obejmuje to symboliczne (miękkie) linki. Jeśli chcesz ich uniknąć, możesz użyć is_symlink
. Przykładowe użycie:
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;
}
Jeśli korzystasz z systemu Windows, możesz używać FindFirstFile razem z interfejsem API FindNextFile. Możesz użyć FindFileData.dwFileAttributes, aby sprawdzić, czy podana ścieżka jest plikiem lub katalogiem. Jeśli jest to katalog, możesz rekurencyjnie powtórzyć algorytm.
Tutaj utworzyłem kod, który zawiera listę wszystkich plików na komputerze z systemem Windows.
Spacer po drzewie plików ftw
to rekurencyjny sposób umieszczenia ściany w całym drzewie katalogów w ścieżce. Więcej szczegółów tutaj .
UWAGA: Możesz również użyć fts
tego, który może pomijać ukryte pliki, takie jak .
lub ..
lub.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;
}
wyjście wygląda następująco:
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
Powiedzmy, że jeśli chcesz dopasować nazwę pliku (na przykład: wyszukiwanie wszystkich *.jpg, *.jpeg, *.png
plików.) Do określonych potrzeb, użyj 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;
}