Comment parcourez-vous chaque fichier / répertoire de manière récursive en C ++ standard?


115

Comment parcourez-vous chaque fichier / répertoire de manière récursive en C ++ standard?


Vous voudrez peut-être examiner boost.filesystem boost.org/doc/libs/1_31_0/libs/filesystem/doc/index.htm
Doug T.


1
Cela devrait bientôt être dans le standard via le système de fichiers TS , avec le recursive_directory_iterator
Adi Shavit

Si l'utilisation d'une bibliothèque C standard n'empêche pas d'appeler un programme C ++ comme «standard», nftw () . Voici un exemple
six-k

2
Quelqu'un, qui sait ce qu'il fait, devrait prendre une heure pour le mettre à jour.
Josh C

Réponses:


99

En C ++ standard, techniquement, il n'y a aucun moyen de le faire puisque le C ++ standard n'a aucune conception des répertoires. Si vous souhaitez élargir un peu votre réseau, vous voudrez peut-être envisager d'utiliser Boost.FileSystem . Cela a été accepté pour inclusion dans TR2, ce qui vous donne les meilleures chances de garder votre implémentation aussi proche que possible de la norme.

Un exemple, tiré directement du site Web:

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
C ++ n'a pas de concept de fichiers? Qu'en est-il de std :: fstream? Ou fopen?
Kevin le

30
fichiers, pas répertoires
1800 INFORMATION

22
Mise à jour par rapport à la dernière version de boost: au cas où quelqu'un trébucherait sur cette réponse, le dernier boost inclut une classe de commodité boost :: recursive_directory_iterator donc l'écriture de la boucle ci-dessus avec un appel récursif explicite n'est plus nécessaire. Lien: boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/…
JasDev

5
VC ++ 11 est livré avec à peu près les mêmes fonctionnalités dans l'en-tête <filesystem> sous l'espace de noms std :: tr2 :: sys.
mheyman

3
C'était une bonne réponse, mais maintenant que <filesystem> est standard, il est préférable d'utiliser simplement is (voir les autres réponses pour un exemple).
Gathar

54

À partir de C ++ 17, l'en- <filesystem>tête et la 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;

Depuis C ++ 17, std::filesystemfait partie de la bibliothèque standard et peut être trouvé dans l'en- <filesystem>tête (plus "expérimental").


Évitez d' usingutiliser, utilisez namespaceplutôt.
Roi Danton

2
Et pourquoi est-ce que? Mieux vaut plus spécifique que d'apporter des choses que vous n'utilisez pas.
Adi Shavit

Vérifiez ma modification s'il vous plaît, j'ai également ajouté std d'espace de noms manquant.
Roi Danton

5
<filesystem> n'est plus un TS. Cela fait partie de C ++ 17. Vous devriez probablement mettre à jour cette réponse en conséquence.
IInspectable

Remarque pour les utilisateurs de Mac, cela nécessite au minimum OSX 10.15 (Catalina).
Justin

45

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 le parcours récursif de répertoires, vous devez inspecter chaque WIN32_FIND_DATA.dwFileAttributes pour vérifier si le bit FILE_ATTRIBUTE_DIRECTORY est défini. Si le bit est défini, vous pouvez appeler la fonction de manière récursive avec ce répertoire. Vous pouvez également utiliser une pile pour fournir le même effet qu'un appel récursif mais en évitant le débordement de pile pour les arbres de chemins 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;
}

19
combien de temps vous a-t-il fallu pour écrire ça? Je pense qu'il faudrait moins de temps pour coller C ++ sur python et le faire en une seule ligne.
Dustin Getz

2
C'est une belle solution non récursive (ce qui est parfois pratique!).
jm.

1
Btw, si quelqu'un veut éditer légèrement le programme pour accepter un paramètre de ligne de commande argv [1] pour le chemin au lieu d'un codé en dur ("F: \\ cvsrepos"), la signature pour main (int, char) changerait à wmain (int, wchar_t) comme ceci: int wmain (int argc, wchar_t * argv [])
JasDev

1
Merci, mais cette fonction ne fonctionne pas avec Cyrilic. Existe-t-il un moyen de le faire fonctionner avec des caractères cyriliques comme - б, в, г etc?
unresolved_external

31

Vous pouvez le rendre encore plus simple avec la nouvelle gamme C ++ 11 basée foret 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
Pas besoin de boost. L'OP a spécifiquement demandé le C ++ standard.
Craig B

23

Une solution rapide consiste à utiliser 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;
}

5
Cette routine n'est pas récursive.
user501138

@TimCooper, bien sûr que non, dirent est spécifique à posix.
Vorac

1
En fait , il fait le travail sur VC ++ si vous obtenez un port de dirent.h pour C ++ visuelle par Tony Rönkkö. C'est FOSS. J'ai juste essayé ceci et ça marche.
user1741137

10

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

WxWidgets et Qt sont des frameworks C ++ multiplateformes open source.

wxDirfournit un moyen flexible de parcourir les fichiers de manière récursive en utilisant Traverse()ou une GetAllFiles()fonction plus simple . De plus, 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 ()).

QDirdonne 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 façon consiste à utiliser la fonction GetEntryList () de QDir et à implémenter un parcours récursif.

Voici un exemple de code (tiré d' ici # Exemple 8-5) qui montre comment parcourir 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;
}

Doxygen utilise QT comme couche de compatibilité du système d'exploitation. Les outils de base n'utilisent pas du tout une interface graphique, juste le répertoire (et d'autres composants).
deft_code

7

Boost :: filesystem fournit recursive_directory_iterator, ce qui est assez 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;                                    
}

1
Qu'est-ce que "ça" s'il vous plaît? N'y a-t-il pas une erreur de syntaxe? Et comment nourrir la «fin»? (= comment savons-nous que nous avons analysé tout le
répertoire

1
@yO_ vous avez raison, il y avait une faute de frappe, le constructeur par défaut pour recursive_directory_iterator construira un itérateur "invalide", lorsque vous aurez fini d'itérer sur dir, il deviendra "it" deviendra invalide et sera égal à "end"
DikobrAz


4

Vous ne le faites pas. Le standard C ++ n'a pas de concept de répertoires. C'est à l'implémentation de transformer une chaîne en descripteur de fichier. Le contenu de cette chaîne et son mappage dépendent 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ù le fait de demander comment parcourir un répertoire n'est pas encore défini (car vous écrivez le code de gestion de répertoire).

Consultez la documentation de votre API OS pour savoir comment procéder. Si vous avez besoin d'être portable, vous devrez avoir un tas de #ifdef pour différents OS.


4

Vous seriez probablement mieux avec le système de fichiers expérimental de boost ou de c ++ 14. SI vous analysez un répertoire interne (c'est-à-dire utilisé par votre programme pour stocker des données après la fermeture du programme), créez un fichier d'index contenant un index du contenu du fichier. Au fait, vous devrez probablement utiliser boost à l'avenir, 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 pour chaque cas est tiré 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.

2

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.


Et le C ++? Existe-t-il de telles fonctions dans iostream?
Aaron Maenpaa le

2
Uniquement pour les fichiers. Il n'y a aucune sorte de fonction "montre-moi tous les fichiers d'un répertoire".
1800 INFORMATION

1
@ 1800: Les répertoires sont des fichiers.
Courses de légèreté en orbite le

2

Nous sommes en 2019. Nous avons une bibliothèque standard de système de fichiers dans C++. leFilesystem 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 fonctionnalités de la bibliothèque de système de fichiers peuvent ne pas être disponibles si un système de fichiers hiérarchique n'est pas accessible à l'implémentation ou s'il ne fournit pas les capacité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 (par exemple, le système de fichiers FAT manque de liens symboliques et interdit les liens physiques multiples). Dans ces cas, les erreurs doivent être signalées.

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

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

std :: filesystem :: recursive_directory_iterator est un LegacyInputIteratorqui 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 d'itération 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 itérer récursivement sur les entrées des sous-répertoires, alors directory_iterator doit être utilisé.

Les deux itérateurs retournent un objet de type directory_entry . directory_entrya diverses fonctions membres utiles comme is_regular_file, is_directory, is_socket, is_symlinketc. La path()fonction membre retourne un objet de std :: :: système de fichiers chemin et il peut être utilisé pour obtenir file extension, filename,root name .

Prenons l'exemple ci-dessous. Je l'ai utilisé Ubuntuet compilé sur le terminal en utilisant

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

1

Vous ne le faites pas. Le C ++ standard n'expose pas le concept de répertoire. Plus précisément, cela ne donne aucun moyen de lister tous les fichiers dans un répertoire.

Un horrible hack 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 multiplateforme telle que Qt ou même POSIX .


1

Vous pouvez utiliser std::filesystem::recursive_directory_iterator. Mais attention, cela inclut les liens symboliques (logiciels). Si vous voulez les éviter, vous pouvez utiliser is_symlink. Exemple d'utilisation:

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
Dernier point mais non le moindre, en fait mieux que les réponses précédentes.
Seyed Mehran Siadati le

0

Si vous êtes sous Windows, vous pouvez utiliser FindFirstFile avec l'API FindNextFile. Vous pouvez utiliser FindFileData.dwFileAttributes pour vérifier si un chemin donné est un fichier ou un répertoire. S'il s'agit d'un répertoire, vous pouvez répéter l'algorithme de manière récursive.

Ici, j'ai rassemblé un code qui répertorie tous les fichiers sur une machine Windows.

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


0

La marche dans l'arborescence des fichiers ftwest une manière récursive de murer toute l'arborescence de répertoires dans le chemin. Plus de détails ici .

REMARQUE: vous pouvez également utiliser ftsqui peut ignorer les fichiers cachés comme .ou ..ou.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;
}

la sortie ressemble à ceci:

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

Disons que si vous voulez faire correspondre un nom de fichier (exemple: rechercher tous les *.jpg, *.jpeg, *.pngfichiers.) Pour un besoin spécifique, utilisez 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;
 }
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.