Come iterate ricorsivamente attraverso ogni file / directory nel C ++ standard?
Come iterate ricorsivamente attraverso ogni file / directory nel C ++ standard?
Risposte:
Nel C ++ standard, tecnicamente non c'è modo di farlo poiché lo standard C ++ non ha la concezione delle directory. Se vuoi espandere un po 'la tua rete, potresti provare a usare Boost.FileSystem . Questo è stato accettato per l'inclusione in TR2, quindi questo ti dà le migliori possibilità di mantenere la tua implementazione il più vicino possibile allo standard.
Un esempio, preso direttamente dal sito:
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;
}
Da C ++ 17 in poi, l' <filesystem>
intestazione e l'intervallo for
, puoi semplicemente fare questo:
#include <filesystem>
using recursive_directory_iterator = std::filesystem::recursive_directory_iterator;
...
for (const auto& dirEntry : recursive_directory_iterator(myPath))
std::cout << dirEntry << std::endl;
A partire da C ++ 17, std::filesystem
fa parte della libreria standard e si trova <filesystem>
nell'header (non più "sperimentale").
using
, usa namespace
invece.
Se si utilizza l'API Win32 è possibile utilizzare le funzioni FindFirstFile e FindNextFile .
http://msdn.microsoft.com/en-us/library/aa365200(VS.85).aspx
Per l'attraversamento ricorsivo delle directory è necessario ispezionare ogni WIN32_FIND_DATA.dwFileAttributes per verificare se il bit FILE_ATTRIBUTE_DIRECTORY è impostato. Se il bit è impostato, è possibile chiamare ricorsivamente la funzione con quella directory. In alternativa è possibile utilizzare uno stack per fornire lo stesso effetto di una chiamata ricorsiva ma evitando l'overflow dello stack per alberi di percorso molto lunghi.
#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;
}
Puoi renderlo ancora più semplice con la nuova gamma C ++ 11 basata for
e 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;
}
Una soluzione veloce sta usando la libreria Dirent.h di C.
Frammento di codice funzionante da Wikipedia:
#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;
}
Oltre al summenzionato boost :: filesystem potresti voler esaminare wxWidgets :: wxDir e Qt :: QDir .
Sia wxWidgets che Qt sono framework C ++ open source e multipiattaforma.
wxDir
fornisce un modo flessibile per attraversare i file in modo ricorsivo utilizzando Traverse()
o una GetAllFiles()
funzione più semplice . Inoltre puoi implementare l'attraversamento con le funzioni GetFirst()
e GetNext()
(presumo che Traverse () e GetAllFiles () siano wrapper che alla fine utilizzano le funzioni GetFirst () e GetNext ()).
QDir
fornisce l'accesso alle strutture di directory e al loro contenuto. Esistono diversi modi per attraversare le directory con QDir. È possibile iterare sul contenuto della directory (incluse le sottodirectory) con QDirIterator che è stato istanziato con il flag QDirIterator :: Subdirectories. Un altro modo è utilizzare la funzione GetEntryList () di QDir e implementare un attraversamento ricorsivo.
Ecco un esempio di codice (preso da qui # Esempio 8-5) che mostra come iterare su tutte le sottodirectory.
#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 fornisce recursive_directory_iterator, che è abbastanza conveniente per questo compito:
#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;
}
È possibile utilizzare ftw(3)
onftw(3)
per esplorare una gerarchia di file system in C o C ++ su sistemi POSIX .
nftw()
uso.
Non lo fai. Lo standard C ++ non ha il concetto di directory. È compito dell'implementazione trasformare una stringa in un file handle. Il contenuto di quella stringa e ciò a cui è mappato dipende dal sistema operativo. Tieni presente che C ++ può essere utilizzato per scrivere quel sistema operativo, quindi viene utilizzato a un livello in cui chiedere come iterare attraverso una directory non è ancora definito (perché stai scrivendo il codice di gestione della directory).
Guarda la documentazione dell'API del tuo sistema operativo per sapere come farlo. Se devi essere portabile, dovrai avere un sacco di #ifdef per vari sistemi operativi.
Probabilmente staresti meglio con boost o con le cose del filesystem sperimentale di c ++ 14. SE si sta analizzando una directory interna (cioè utilizzata dal programma per memorizzare i dati dopo la chiusura del programma), creare un file indice che abbia un indice del contenuto del file. A proposito, probabilmente in futuro dovrai usare boost, quindi se non lo hai installato, installalo! Secondo, potresti usare una compilazione condizionale, ad esempio:
#ifdef WINDOWS //define WINDOWS in your code to compile for windows
#endif
Il codice per ogni caso è preso da 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.
È necessario chiamare funzioni specifiche del sistema operativo per l'attraversamento del file system, come open()
e readdir()
. Lo standard C non specifica alcuna funzione relativa al file system.
Siamo nel 2019. Abbiamo la libreria standard del file system in formato C++
. Il Filesystem library
fornisce servizi per l'esecuzione di operazioni su file system e dei loro componenti, come i percorsi, file normali e directory.
C'è una nota importante su questo collegamento se stai considerando problemi di portabilità. Dice:
Le funzionalità della libreria del file system potrebbero non essere disponibili se un file system gerarchico non è accessibile all'implementazione o se non fornisce le capacità necessarie. Alcune funzionalità potrebbero non essere disponibili se non sono supportate dal file system sottostante (ad esempio, il file system FAT manca di collegamenti simbolici e vieta collegamenti fisici multipli). In questi casi, è necessario segnalare gli errori.
La libreria del file system è stata originariamente sviluppata come boost.filesystem
, è stata pubblicata come specifica tecnica ISO / IEC TS 18822: 2015 e infine fusa in ISO C ++ a partire da C ++ 17. L'implementazione boost è attualmente disponibile su più compilatori e piattaforme rispetto alla libreria C ++ 17.
@ adi-shavit ha risposto a questa domanda quando faceva parte di std :: sperimentale e ha aggiornato questa risposta nel 2017. Voglio fornire maggiori dettagli sulla libreria e mostrare un esempio più dettagliato.
std :: filesystem :: recursive_directory_iterator è un LegacyInputIterator
che itera sugli elementi directory_entry di una directory e, in modo ricorsivo, sulle voci di tutte le sottodirectory. L'ordine di iterazione non è specificato, tranne per il fatto che ogni voce di directory viene visitata una sola volta.
Se non si desidera iterare ricorsivamente sulle voci delle sottodirectory, è necessario utilizzare directory_iterator .
Entrambi gli iteratori restituiscono un oggetto directory_entry . directory_entry
ha varie funzioni membro utili come is_regular_file
, is_directory
, is_socket
, is_symlink
ecc Le path()
funzioni membro restituisce un oggetto std :: filesystem :: percorso e può essere utilizzato per ottenere file extension
, filename
, root name
.
Considera l'esempio di seguito. L'ho usato Ubuntu
e l'ho compilato sul terminale usando
g ++ esempio.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;
}
Non lo fai. Il C ++ standard non espone al concetto di directory. In particolare, non fornisce alcun modo per elencare tutti i file in una directory.
Un trucco orribile sarebbe usare le chiamate system () e analizzare i risultati. La soluzione più ragionevole sarebbe usare una sorta di libreria multipiattaforma come Qt o anche POSIX .
Puoi usare std::filesystem::recursive_directory_iterator
. Ma attenzione, questo include collegamenti simbolici (soft). Se vuoi evitarli puoi usare is_symlink
. Utilizzo di esempio:
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;
}
Se sei su Windows, puoi utilizzare FindFirstFile insieme all'API FindNextFile. È possibile utilizzare FindFileData.dwFileAttributes per verificare se un determinato percorso è un file o una directory. Se è una directory, puoi ripetere ricorsivamente l'algoritmo.
Qui, ho messo insieme un codice che elenca tutti i file su una macchina Windows.
Il percorso dell'albero dei file ftw
è un modo ricorsivo per murare l'intero albero di directory nel percorso. Maggiori dettagli sono qui .
NOTA: puoi anche usarlo per fts
saltare i file nascosti come .
o ..
o.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;
}
l'output è simile al seguente:
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
Diciamo che se vuoi abbinare un nome di file (esempio: ricerca di tutti i *.jpg, *.jpeg, *.png
file.) Per esigenze specifiche, usa 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;
}