Come si esegue l'iterazione ricorsiva di ogni file/directory nel C++ standard?

StackOverflow https://stackoverflow.com/questions/67273

  •  09-06-2019
  •  | 
  •  

Domanda

Come si esegue l'iterazione ricorsiva di ogni file/directory nel C++ standard?

È stato utile?

Soluzione

Nel C++ standard, tecnicamente non c'è modo di farlo poiché il C++ standard non ha il concetto di directory.Se vuoi espandere un po' la tua rete, ti potrebbe interessare l'utilizzo Boost.FileSystem.Questo è stato accettato per l'inclusione in TR2, quindi ti offre 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;
}

Altri suggerimenti

Se usi l'API Win32 puoi usare il file TrovaPrimoFile E TrovaFileSuccessivo funzioni.

http://msdn.microsoft.com/en-us/library/aa365200(VS.85).aspx

Per l'attraversamento ricorsivo delle directory è necessario ispezionarle ciascuna WIN32_FIND_DATA.dwFileAttributes per verificare se il FILE_ATTRIBUTE_DIRECTORY bit è impostato.Se il bit è impostato, puoi 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 il nuovo C++11 basato sulla gamma for E Aumento:

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

In C++11/14 con il "Filesystem TS", il <experimental/filesystem> intestazione e intervallo-for puoi semplicemente fare questo:

#include <experimental/filesystem>

using std::experimental::filesystem::recursive_directory_iterator;
...
for (auto& dirEntry : recursive_directory_iterator(myPath))
     cout << dirEntry << endl;

A partire da C++17, std::filesystem fa parte della libreria standard e può essere trovato nel file <filesystem> header (non più "sperimentale").

Una soluzione rapida è usare C Dirent.h biblioteca.

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 sopra citato boost::filesystem che 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 uno più semplice GetAllFiles() funzione.Inoltre puoi implementare l'attraversamento con GetFirst() E GetNext() funzioni (presumo che Traverse() e GetAllFiles() siano wrapper che eventualmente utilizzano le funzioni GetFirst() e GetNext()).

QDir fornisce l'accesso alle strutture delle directory e al loro contenuto.Esistono diversi modi per attraversare le directory con QDir.È possibile scorrere il contenuto della directory (incluse le sottodirectory) con QDirIterator istanziato con il flag QDirIterator::Subdirectories.Un altro modo è utilizzare la funzione GetEntryList() di QDir e implementare un attraversamento ricorsivo.

Ecco il codice di esempio (tratto da Qui # Esempio 8-5) che mostra come scorrere 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 questa attività:

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

Puoi usare ftw(3) O nftw(3) per percorrere una gerarchia di filesystem in C o C++ POSIX sistemi.

Tu no.Lo standard C++ non prevede il concetto di directory.Spetta all'implementazione trasformare una stringa in un handle di file.Il contenuto di quella stringa e ciò a cui è associato dipende dal sistema operativo.Tieni presente che C++ può essere utilizzato per scrivere quel sistema operativo, quindi viene utilizzato a un livello in cui la richiesta di come scorrere una directory non è ancora definita (perché stai scrivendo il codice di gestione della directory).

Guarda la documentazione dell'API del tuo sistema operativo per sapere come eseguire questa operazione.Se hai bisogno di essere portatile, dovrai averne un sacco #ifdefs per vari sistemi operativi.

Probabilmente saresti migliore con boost o con il materiale sperimentale del filesystem di c++14. SE stai analizzando una directory interna (es.utilizzato dal programma per memorizzare i dati dopo la chiusura del programma), quindi creare un file indice con un indice del contenuto del file.A proposito, probabilmente avrai bisogno di usare Boost in futuro, quindi se non lo hai installato, installalo!In secondo luogo, potresti utilizzare una compilazione condizionale, ad esempio:

#ifdef WINDOWS //define WINDOWS in your code to compile for windows
#endif

Il codice per ciascun caso è tratto 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 filesystem, come open() E readdir().Lo standard C non specifica alcuna funzione relativa al filesystem.

Tu no.Il C++ standard non espone al concetto di directory.Nello specifico 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 quella di utilizzare una sorta di libreria multipiattaforma come Qt o anche POSIX.

Siamo nel 2019.Abbiamo file system libreria standard in C++.IL Filesystem library fornisce funzionalità per eseguire operazioni sui file system e sui loro componenti, come percorsi, file normali e directory.

C'è una nota importante su questo link se stai considerando problemi di portabilità.Dice:

Le funzionalità della libreria del filesystem potrebbero non essere disponibili se un filesystem gerarchico non è accessibile all'implementazione o se non fornisce le funzionalità necessarie.Alcune funzionalità potrebbero non essere disponibili se non sono supportate dal file system sottostante (ad es.il filesystem FAT non dispone di collegamenti simbolici e vieta più collegamenti fisici).In questi casi gli errori devono essere segnalati.

La libreria del filesystem è 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::experimental e ha aggiornato questa risposta nel 2017.Voglio fornire maggiori dettagli sulla libreria e mostrare esempi più dettagliati.

std::filesystem::recursive_directory_iterator è un LegacyInputIterator che scorre sugli elementi directory_entry di una directory e, ricorsivamente, sulle voci di tutte le sottodirectory.L'ordine di iterazione non è specificato, tranne per il fatto che ciascuna voce di directory viene visitata solo una volta.

Se non vuoi scorrere ricorsivamente le voci delle sottodirectory, allora directory_iteratore dovrebbe essere usato.

Entrambi gli iteratori restituiscono un oggetto di voce_directory. directory_entry ha varie funzioni membro utili come is_regular_file, is_directory, is_socket, is_symlink eccetera.IL path() la funzione membro restituisce un oggetto di std::filesystem::percorso e può essere usato per ottenere file extension, filename, root name.

Considera l'esempio seguente.Ho usato Ubuntu e compilato sul terminale utilizzando

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

Se utilizzi Windows, puoi utilizzare FindFirstFile insieme all'API FindNextFile.È possibile utilizzare FindFileData.dwFileAttributes per verificare se un determinato percorso è un file o una directory.Se si tratta di una directory, puoi ripetere ricorsivamente l'algoritmo.

Qui, ho messo insieme del codice che elenca tutti i file su una macchina Windows.

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top