web-dev-qa-db-fra.com

Comment parcourir de manière récursive tous les fichiers / répertoires en C ++ standard?

Comment parcourir de manière récursive tous les fichiers/répertoires en C++ standard?

102
robottobor

En C++ standard, techniquement, il n’ya aucun moyen de le faire car le C++ standard n’a aucune conception de répertoires. Si vous voulez élargir un peu votre réseau, vous pouvez utiliser Boost.FileSystem . Cela a été accepté pour inclusion dans TR2, ce qui vous donne donc la meilleure chance de garder votre implémentation aussi proche que possible de la norme.

Un exemple, tiré directement du site:

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;
}
94
1800 INFORMATION

Si vous utilisez l'API Win32, vous pouvez utiliser les fonctions FindFirstFile et FindNextFile .

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

Pour la traversée récursive de répertoires, vous devez inspecter chaque WIN32_FIND_DATA.dwFileAttributes pour vérifier si le FILE_ATTRIBUTE_DIRECTORY le bit est activé. Si le bit est défini, vous pouvez appeler de manière récursive la fonction avec ce répertoire. Vous pouvez également utiliser une pile pour obtenir le même effet qu'un appel récursif, tout en évitant le débordement de pile pour les arbres de chemin très longs.

#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;
}
42
Jorge Ferreira

Avec C++ 17, le <filesystem> en-tête et plage -for, vous pouvez simplement faire ceci:

#include <filesystem>

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

À partir de C++ 17, std::filesystem fait partie de la bibliothèque standard et se trouve dans le <filesystem> en-tête (n'est plus "expérimental").

36
Adi Shavit

Vous pouvez le rendre encore plus simple avec le nouveau C++ 11 basé sur la plage for et 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;
}
31
Matthieu G

Une solution rapide utilise la bibliothèque Dirent.h de C.

Fragment de code de travail de 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;
}
23
Alex

En plus du boost :: système de fichiers mentionné ci-dessus, vous voudrez peut-être examiner wxWidgets :: wxDir et Qt :: QDir .

WxWidgets et Qt sont des infrastructures C++ multi-plateformes à code source ouvert.

wxDir fournit un moyen flexible de parcourir les fichiers de manière récursive en utilisant Traverse() ou une fonction plus simple GetAllFiles(). En outre, vous pouvez implémenter le parcours avec les fonctions GetFirst() et GetNext() (je suppose que Traverse () et GetAllFiles () sont des wrappers qui utilisent finalement les fonctions GetFirst () et GetNext ()).

QDir donne accès aux structures de répertoires et à leur contenu. Il existe plusieurs façons de parcourir les répertoires avec QDir. Vous pouvez parcourir le contenu du répertoire (y compris les sous-répertoires) avec QDirIterator qui a été instancié avec l'indicateur QDirIterator :: Subdirectories. Une autre méthode consiste à utiliser la fonction GetEntryList () de QDir et à implémenter un parcours récursif.

Voici un exemple de code (tiré de here # Exemple 8-5) qui montre comment itérer sur tous les sous-répertoires.

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

Boost :: filesystem fournit recursive_directory_iterator, ce qui est très pratique pour cette tâche:

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

Vous pouvez utiliser ftw(3) ou nftw(3) pour faire défiler une hiérarchie de système de fichiers en C ou C++ sur (~ ~ ~]) posix [~ # ~] systèmes.

4
leif

Vous auriez probablement intérêt à utiliser boost ou le système de fichiers expérimental de c ++ 14. [~ # ~] si [~ # ~] vous analysez un répertoire interne (c'est-à-dire utilisé pour que votre programme stocke les données après sa fermeture. ), puis créez un fichier d’index contenant un index du contenu du fichier. En passant, vous aurez probablement besoin d'utiliser boost dans le futur, donc si vous ne l'avez pas installé, installez-le! Deuxièmement, vous pouvez utiliser une compilation conditionnelle, par exemple:

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

Le code de chaque cas provient de 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.
4
ndrewxie

Vous ne. La norme C++ n'a pas de concept de répertoires. Il appartient à l’implémentation de transformer une chaîne en descripteur de fichier. Le contenu de cette chaîne et de ses correspondances dépend du système d'exploitation. Gardez à l'esprit que C++ peut être utilisé pour écrire ce système d'exploitation, il est donc utilisé à un niveau où il n'est pas encore défini comment itérer dans un répertoire (car vous écrivez le code de gestion de répertoires).

Consultez la documentation de votre API de système d'exploitation pour savoir comment procéder. Si vous devez être portable, vous devrez disposer de # ifdef s pour différents systèmes d’exploitation.

3
Matthew Scouten

Vous devez appeler des fonctions spécifiques au système d’exploitation pour la traversée du système de fichiers, comme open() et readdir(). La norme C ne spécifie aucune fonction liée au système de fichiers.

2
John Millikin

Vous ne. Le standard C++ n'expose pas à la notion de répertoire. Plus précisément, cela ne donne aucun moyen de lister tous les fichiers d'un répertoire.

Un hack horrible serait d'utiliser des appels system () et d'analyser les résultats. La solution la plus raisonnable serait d'utiliser une sorte de bibliothèque multiplate-forme telle que Qt ou même POSIX .

1
shoosh

Nous sommes en 2019. Nous avons système de fichiers bibliothèque standard dans C++. Le Filesystem library Fournit des fonctionnalités pour effectuer des opérations sur les systèmes de fichiers et leurs composants, tels que les chemins, les fichiers normaux et les répertoires.

Il y a une note importante sur ce lien si vous envisagez des problèmes de portabilité. Ça dit:

Les installations de la bibliothèque du système de fichiers peuvent ne pas être disponibles si un système de fichiers hiérarchique n'est pas accessible pour la mise en œuvre ou s'il ne fournit pas les fonctionnalités nécessaires. Certaines fonctionnalités peuvent ne pas être disponibles si elles ne sont pas prises en charge par le système de fichiers sous-jacent (le système de fichiers FAT, par exemple, manque de liens symboliques et interdit plusieurs liens en dur). Dans ces cas, les erreurs doivent être signalées.

La bibliothèque du système de fichiers a été initialement développée sous la forme boost.filesystem, A été publiée sous la spécification technique ISO/IEC TS 18822: 2015 et a finalement été fusionnée avec ISO C++ à partir de C++ 17. L'implémentation boost est actuellement disponible sur davantage de compilateurs et de plates-formes que la bibliothèque C++ 17.

@ adi-shavit a répondu à cette question lorsqu'il faisait partie de std :: experimental et il a mis à jour cette réponse en 2017. Je souhaite donner plus de détails sur la bibliothèque et donner un exemple plus détaillé.

std :: filesystem :: recursive_directory_iterator est un LegacyInputIterator qui itère sur les éléments directory_entry d'un répertoire et, de manière récursive, sur les entrées de tous les sous-répertoires. L'ordre des itérations n'est pas spécifié, sauf que chaque entrée de répertoire n'est visitée qu'une seule fois.

Si vous ne voulez pas effectuer une itération récursive sur les entrées de sous-répertoires, alors directory_iterator doit être utilisé.

Les deux itérateurs retournent un objet de directory_entry . directory_entry A diverses fonctions de membre utiles comme is_regular_file, is_directory, is_socket, is_symlink Etc. Le path() membre fonction retourne un objet de std :: filesystem :: path et peut être utilisé pour obtenir file extension, filename, root name.

Considérons l'exemple ci-dessous. J'ai utilisé Ubuntu et l'ai compilé sur le terminal à l'aide de

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