Come iterate ricorsivamente attraverso ogni file / directory nel C ++ standard?


115

Come iterate ricorsivamente attraverso ogni file / directory nel C ++ standard?




1
Questo dovrebbe presto essere nello standard tramite Filesystem TS , con recursive_directory_iterator
Adi Shavit

Se l'uso di una libreria C standard non impedisce di chiamare un programma C ++ come "standard", nftw () . Ecco un esempio
six-k

2
Qualcuno, che sa cosa sta facendo, dovrebbe impiegare un'ora per aggiornarlo.
Josh C

Risposte:


99

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;
}

5
Il C ++ non ha il concetto di file? Che dire di std :: fstream? O fopen?
Kevin

30
file, non directory
1800 INFORMAZIONI

22
Aggiornamento per quanto riguarda l'ultima versione boost: nel caso in cui qualcuno si imbattesse in questa risposta, l'ultimo boost include un boost di classe di convenienza :: recursive_directory_iterator, quindi non è più necessario scrivere il ciclo precedente con una chiamata ricorsiva esplicita. Link: boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/…
JasDev

5
VC ++ 11 ha più o meno la stessa funzionalità nell'intestazione <filesystem> nello spazio dei nomi std :: tr2 :: sys.
mheyman

3
Questa era una buona risposta, ma ora che <filesystem> è standard, è meglio usare semplicemente is (vedi altre risposte per un esempio).
Gathar

54

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::filesystemfa parte della libreria standard e si trova <filesystem>nell'header (non più "sperimentale").


Evita l'uso di using, usa namespaceinvece.
Roi Danton,

2
E perché è così? Meglio più specifico che portare cose che non usi.
Adi Shavit

Controlla la mia modifica per favore, ho anche aggiunto lo spazio dei nomi std mancante.
Roi Danton,

5
<filesystem> non è più un TS. Fa parte di C ++ 17. Probabilmente dovresti aggiornare questa risposta di conseguenza.
Rilevabile

Nota per gli utenti Mac, questo richiede OSX 10.15 (Catalina) come minimo.
Justin

45

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;
}

19
quanto tempo ci hai messo a scriverlo? Penso che ci vorrebbe meno tempo per incollare C ++ a Python e farlo in una riga.
Dustin Getz

2
Questa è una bella soluzione non ricorsiva (che a volte è utile!).
jm.

1
A proposito, se qualcuno vuole modificare leggermente il programma per accettare un parametro della riga di comando argv [1] per il percorso invece di uno hardcoded ("F: \\ cvsrepos"), la firma per main (int, char) cambierebbe to wmain (int, wchar_t) in questo modo: int wmain (int argc, wchar_t * argv [])
JasDev

1
Grazie, ma questa funzione non funziona con Cyrilic. C'è un modo per farlo funzionare con caratteri cirilici come - б, в, г ecc?
unresolved_external

31

Puoi renderlo ancora più semplice con la nuova gamma C ++ 11 basata fore 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
Non c'è bisogno di boost. L'OP ha richiesto specificatamente lo standard c ++.
Craig B

23

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;
}

5
Questa routine non è ricorsiva.
user501138

@TimCooper, ovviamente no, dirent è specifico per posix.
Vorac

1
In realtà fa lavoro sulla VC ++ se si ottiene un porto di dirent.h per C ++ visivo da Tony Rönkkö. È FOSS. L'ho appena provato e funziona.
user1741137

10

Oltre al summenzionato boost :: filesystem potresti voler esaminare wxWidgets :: wxDir e Qt :: QDir .

Sia wxWidgets che Qt sono framework C ++ open source e multipiattaforma.

wxDirfornisce 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 ()).

QDirfornisce 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;
}

Doxygen utilizza QT come livello di compatibilità del sistema operativo. Gli strumenti principali non usano affatto una GUI, solo le cose della directory (e altri componenti).
deft_code

7

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;                                    
}

1
Che cos'è "esso" per favore? Non c'è un errore di sintassi? E come si alimenta la "fine"? (= come
fai a

1
@yO_ hai ragione c'è stato un errore di battitura, il costruttore predefinito per recursive_directory_iterator costruirà un iteratore "non valido", quando hai finito di iterare su dir, "esso" diventerà non valido e sarà uguale a "fine"
DikobrAz


4

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.


4

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.

2

È 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.


E il C ++? Esistono tali funzioni in iostream?
Aaron Maenpaa

2
Solo per file. Non esiste alcun tipo di funzione "mostrami tutti i file in una directory".
1800 INFORMAZIONI

1
@ 1800: le directory sono file.
Gare di leggerezza in orbita il

2

Siamo nel 2019. Abbiamo la libreria standard del file system in formato C++. Il Filesystem libraryfornisce 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 LegacyInputIteratorche 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_entryha varie funzioni membro utili come is_regular_file, is_directory, is_socket, is_symlinkecc 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 Ubuntue 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;
}

1

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 .


1

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;
}

1
Ultimo ma non meno importante, effettivamente migliore delle risposte precedenti.
Seyed Mehran Siadati il

0

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.

http://dreams-soft.com/projects/traverse-directory


0

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 ftssaltare 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, *.pngfile.) 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;
 }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.