Wie iteriert man in Standard-C ++ rekursiv durch jede Datei / jedes Verzeichnis?

115

Wie iteriert man in Standard-C ++ rekursiv durch jede Datei / jedes Verzeichnis?

Robottobor
quelle
Vielleicht möchten Sie boost.filesystem boost.org/doc/libs/1_31_0/libs/filesystem/doc/index.htm
Doug T.
1
Nicht Standard C ++: pocoproject.org/docs/Poco.DirectoryIterator.html
Agnel Kurian
1
Dies sollte bald im Standard über das Dateisystem TS sein , mit dem rekursiven_Directory_iterator
Adi Shavit
Wenn die Verwendung einer Standard-C-Bibliothek den Aufruf eines C ++ - Programms als 'Standard' nicht behindert , nftw () . Hier ist ein praktisches Beispiel
Sechs-k
2
Jemand, der weiß, was er tut, sollte sich eine Stunde Zeit nehmen, um dies zu aktualisieren.
Josh C

Antworten:

99

In Standard-C ++ gibt es technisch keine Möglichkeit, dies zu tun, da Standard-C ++ keine Konzeption von Verzeichnissen hat. Wenn Sie Ihr Netz ein wenig erweitern möchten, sollten Sie sich Boost.FileSystem ansehen . Dies wurde für die Aufnahme in TR2 akzeptiert, sodass Sie die beste Chance haben, Ihre Implementierung so nah wie möglich am Standard zu halten.

Ein Beispiel direkt von der Website:

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;
}
1800 INFORMATIONEN
quelle
5
C ++ hat kein Konzept von Dateien? Was ist mit std :: fstream? Oder offen?
Kevin
29
Dateien, keine Verzeichnisse
1800 INFORMATION
22
Update in Bezug auf die neueste Boost-Version: Falls jemand über diese Antwort stolpert, enthält der neueste Boost eine Convenience-Klasse boost :: recursive_directory_iterator, sodass das Schreiben der obigen Schleife mit einem expliziten rekursiven Aufruf nicht mehr erforderlich ist. Link: boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/…
JasDev
5
VC ++ 11 bietet im Header <filesystem> unter dem Namespace std :: tr2 :: sys fast die gleiche Funktionalität.
Mheyman
3
Früher war dies eine gute Antwort, aber jetzt, da <Dateisystem> Standard ist, ist es besser, es einfach zu verwenden (siehe andere Antworten für ein Beispiel).
Gathar
53

Ab C ++ 17, dem <filesystem>Header und dem Bereich forkönnen Sie dies einfach tun:

#include <filesystem>

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

Ab C ++ 17 std::filesystemist Teil der Standardbibliothek und befindet sich im <filesystem>Header (nicht mehr "experimentell").

Adi Shavit
quelle
Vermeiden Sie die Verwendung von using, verwenden Sie namespacestattdessen.
Roi Danton
2
Und warum ist das? Besser spezifischer als Dinge einzubringen, die Sie nicht benutzen.
Adi Shavit
Überprüfen Sie meine Bearbeitung bitte, ich habe auch fehlende Namespace std hinzugefügt.
Roi Danton
5
<Dateisystem> ist kein TS mehr. Es ist Teil von C ++ 17. Sie sollten diese Antwort wahrscheinlich entsprechend aktualisieren.
Unsichtbarer
Hinweis: Für Mac-Benutzer ist mindestens OSX 10.15 (Catalina) erforderlich.
Justin
45

Wenn Sie die Win32-API verwenden, können Sie die Funktionen FindFirstFile und FindNextFile verwenden.

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

Für das rekursive Durchlaufen von Verzeichnissen müssen Sie jedes WIN32_FIND_DATA.dwFileAttributes überprüfen, um zu überprüfen, ob das Bit FILE_ATTRIBUTE_DIRECTORY gesetzt ist. Wenn das Bit gesetzt ist, können Sie die Funktion mit diesem Verzeichnis rekursiv aufrufen. Alternativ können Sie einen Stapel verwenden, um den gleichen Effekt eines rekursiven Aufrufs zu erzielen, aber einen Stapelüberlauf für sehr lange Pfadbäume zu vermeiden.

#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;
}
Jorge Ferreira
quelle
19
Wie lange hast du gebraucht, um das zu schreiben? Ich denke, es würde weniger Zeit brauchen, um C ++ auf Python zu kleben und es in einer Zeile zu tun.
Dustin Getz
2
Dies ist eine schöne, nicht rekursive Lösung (was manchmal praktisch ist!).
jm.
1
Übrigens, wenn jemand das Programm leicht bearbeiten möchte, um einen Befehlszeilenparameter argv [1] für den Pfad anstelle eines fest codierten ("F: \\ cvsrepos") zu akzeptieren, würde sich die Signatur für main (int, char) ändern zu wmain (int, wchar_t) wie folgt: int wmain (int argc, wchar_t * argv [])
JasDev
1
Danke, aber diese Funktion funktioniert nicht mit Cyrilic. Gibt es eine Möglichkeit, es mit kyrilischen Zeichen wie - б, в, г usw. funktionieren zu lassen?
ungelöste_externe
31

Mit der neuen C ++ 11- Reihe forund Boost können Sie es noch einfacher machen :

#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;
}
Matthieu G.
quelle
5
Kein Boost nötig. Das OP fragte speziell nach Standard-C ++.
Craig B
23

Eine schnelle Lösung ist die Verwendung der Dirent.h- Bibliothek von C.

Arbeitscodefragment aus 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;
}
Alex
quelle
5
Diese Routine ist nicht rekursiv.
user501138
@ TimCooper, natürlich nicht, dirent ist posix-spezifisch.
Vorac
1
Eigentlich ist es tut Arbeit auf VC ++ , wenn Sie einen Port von dirent.h für Visual C ++ von Tony Rönkkö bekommen. Es ist FOSS. Ich habe es gerade versucht und es funktioniert.
user1741137
10

Zusätzlich zu dem oben erwähnten Boost :: -Dateisystem möchten Sie möglicherweise wxWidgets :: wxDir und Qt :: QDir untersuchen .

Sowohl wxWidgets als auch Qt sind plattformübergreifende Open Source-C ++ - Frameworks.

wxDirbietet eine flexible Möglichkeit, Dateien rekursiv zu durchlaufen, Traverse()oder eine einfachere GetAllFiles()Funktion. Sie können auch das Traversal mit GetFirst()und GetNext()Funktionen implementieren (ich gehe davon aus, dass Traverse () und GetAllFiles () Wrapper sind, die schließlich die Funktionen GetFirst () und GetNext () verwenden).

QDirbietet Zugriff auf Verzeichnisstrukturen und deren Inhalte. Es gibt verschiedene Möglichkeiten, Verzeichnisse mit QDir zu durchlaufen. Sie können den Verzeichnisinhalt (einschließlich der Unterverzeichnisse) mit QDirIterator durchlaufen, der mit dem Flag QDirIterator :: Subdirectories instanziiert wurde. Eine andere Möglichkeit besteht darin, die GetEntryList () -Funktion von QDir zu verwenden und eine rekursive Durchquerung zu implementieren.

Hier ist Beispielcode (aus hier # Beispiel 8-5) , die zeigt , wie alle Unterverzeichnisse iterieren.

#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;
}
mrvincenzo
quelle
Doxygen verwendet QT als Betriebssystemkompatibilitätsschicht. Die Kerntools verwenden überhaupt keine GUI, sondern nur das Verzeichnismaterial (und andere Komponenten).
Deft_code
7

Boost :: filesystem bietet recursive_directory_iterator, was für diese Aufgabe sehr praktisch ist:

#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;                                    
}
DikobrAz
quelle
1
Was ist "es" bitte? Gibt es keinen Syntaxfehler? Und wie füttert man das "Ende"? (= woher wissen wir, dass wir das ganze
Verzeichnis
1
@yO_ Sie haben Recht, es gab einen Tippfehler. Der Standardkonstruktor für recursive_directory_iterator erstellt einen "ungültigen" Iterator. Wenn Sie die Iteration über dir beendet haben, wird "es" ungültig und entspricht "end"
DikobrAz
5

Sie können eine Dateisystemhierarchie in C oder C ++ auf POSIX- Systemen verwenden ftw(3)odernftw(3) durchlaufen .

leif
quelle
github.com/six-k/dtreetrawl/blob/… hat ein Beispiel dafür. Der Code macht noch ein paar Dinge, aber er ist ein gutes Tutorial für die nftw()Verwendung.
Sechs-k
4

Sie sind wahrscheinlich am besten mit Boost oder dem experimentellen Dateisystem von c ++ 14 vertraut. Wenn Sie ein internes Verzeichnis analysieren (dh für Ihr Programm zum Speichern von Daten nach dem Schließen des Programms verwendet werden), erstellen Sie eine Indexdatei mit einem Index des Dateiinhalts. Übrigens müssten Sie wahrscheinlich in Zukunft Boost verwenden. Wenn Sie es also nicht installiert haben, installieren Sie es! Zweitens könnten Sie eine bedingte Kompilierung verwenden, z.

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

Der Code für jeden Fall stammt von 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.
ndrewxie
quelle
3

Das tust du nicht. Der C ++ - Standard hat kein Konzept für Verzeichnisse. Es liegt an der Implementierung, eine Zeichenfolge in ein Dateihandle umzuwandeln. Der Inhalt dieser Zeichenfolge und die Zuordnung ist vom Betriebssystem abhängig. Beachten Sie, dass C ++ zum Schreiben dieses Betriebssystems verwendet werden kann, sodass es auf einer Ebene verwendet wird, auf der die Frage, wie ein Verzeichnis durchlaufen werden soll, noch nicht definiert ist (da Sie den Verzeichnisverwaltungscode schreiben).

Informationen dazu finden Sie in der Dokumentation zur Betriebssystem-API. Wenn Sie portabel sein müssen, müssen Sie eine Reihe von #ifdefs für verschiedene Betriebssysteme haben.

Matthew Scouten
quelle
2

Sie müssen betriebssystemspezifische Funktionen für das Durchlaufen des Dateisystems aufrufen, z. B. open()und readdir(). Der C-Standard spezifiziert keine dateisystembezogenen Funktionen.

John Millikin
quelle
Was ist mit C ++? Gibt es solche Funktionen in iostream?
Aaron Maenpaa
2
Nur für Dateien. Es gibt keine Funktionen zum Anzeigen aller Dateien in einem Verzeichnis.
1800 INFORMATION
1
@ 1800: Verzeichnisse sind Dateien.
Leichtigkeitsrennen im Orbit
2

Wir sind im Jahr 2019. Wir haben Dateisystem Standardbibliothek in C++. Das Filesystem librarybietet Funktionen zum Ausführen von Vorgängen auf Dateisystemen und deren Komponenten, z. B. Pfaden, regulären Dateien und Verzeichnissen.

Dieser Link enthält einen wichtigen Hinweis , wenn Sie Probleme mit der Portabilität in Betracht ziehen. Es sagt:

Die Funktionen der Dateisystembibliothek sind möglicherweise nicht verfügbar, wenn ein hierarchisches Dateisystem für die Implementierung nicht zugänglich ist oder wenn es nicht die erforderlichen Funktionen bietet. Einige Funktionen sind möglicherweise nicht verfügbar, wenn sie vom zugrunde liegenden Dateisystem nicht unterstützt werden (z. B. fehlen dem FAT-Dateisystem symbolische Links und es sind mehrere Hardlinks verboten). In diesen Fällen müssen Fehler gemeldet werden.

Die Dateisystembibliothek wurde ursprünglich als entwickelt boost.filesystem, als technische Spezifikation ISO / IEC TS 18822: 2015 veröffentlicht und schließlich ab C ++ 17 mit ISO C ++ zusammengeführt. Die Boost-Implementierung ist derzeit auf mehr Compilern und Plattformen als in der C ++ 17-Bibliothek verfügbar.

@ adi-shavit hat diese Frage beantwortet, als sie Teil von std :: experimentell war, und er hat diese Antwort 2017 aktualisiert. Ich möchte mehr Details über die Bibliothek geben und ein detaillierteres Beispiel zeigen.

std :: filesystem :: recursive_directory_iterator ist ein LegacyInputIteratorBefehl, der über die directory_entry-Elemente eines Verzeichnisses und rekursiv über die Einträge aller Unterverzeichnisse iteriert. Die Iterationsreihenfolge ist nicht angegeben, außer dass jeder Verzeichniseintrag nur einmal besucht wird.

Wenn Sie die Einträge von Unterverzeichnissen nicht rekursiv durchlaufen möchten, sollte directory_iterator verwendet werden.

Beide Iteratoren geben ein Objekt von directory_entry zurück . directory_entryhat verschiedene nützliche Member - Funktionen wie is_regular_file, is_directory, is_socket, is_symlinketc. Die path()Member - Funktion gibt ein Objekt von std :: filesystem :: path und es kann zu bekommen verwendet werden file extension, filename, root name.

Betrachten Sie das folgende Beispiel. Ich habe es verwendet Ubuntuund über das Terminal mit kompiliert

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;
}
abhiarora
quelle
1

Das tust du nicht. Standard C ++ ist nicht dem Konzept eines Verzeichnisses ausgesetzt. Insbesondere gibt es keine Möglichkeit, alle Dateien in einem Verzeichnis aufzulisten.

Ein schrecklicher Hack wäre, system () -Aufrufe zu verwenden und die Ergebnisse zu analysieren. Die vernünftigste Lösung wäre die Verwendung einer plattformübergreifenden Bibliothek wie Qt oder sogar POSIX .

shoosh
quelle
1

Sie können verwenden std::filesystem::recursive_directory_iterator. Beachten Sie jedoch, dass dies auch symbolische (weiche) Links umfasst. Wenn Sie sie vermeiden möchten, können Sie verwenden is_symlink. Anwendungsbeispiel:

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;
}
pooya13
quelle
1
Last but not least, eigentlich besser als bisherige Antworten.
Seyed Mehran Siadati
0

Wenn Sie unter Windows arbeiten, können Sie FindFirstFile zusammen mit der FindNextFile-API verwenden. Mit FindFileData.dwFileAttributes können Sie überprüfen, ob ein bestimmter Pfad eine Datei oder ein Verzeichnis ist. Wenn es sich um ein Verzeichnis handelt, können Sie den Algorithmus rekursiv wiederholen.

Hier habe ich einen Code zusammengestellt, der alle Dateien auf einem Windows-Computer auflistet.

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

Ibrahim
quelle
0

Der Dateibaumspaziergang ftwist eine rekursive Methode, um den gesamten Verzeichnisbaum im Pfad zu umwandeln. Weitere Details finden Sie hier .

HINWEIS: Sie können auch ftsversteckte Dateien wie .oder ..oder überspringen.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;
}

Die Ausgabe sieht folgendermaßen aus:

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

Angenommen, Sie möchten einen Dateinamen (Beispiel: Suche nach allen *.jpg, *.jpeg, *.pngDateien) für bestimmte Anforderungen verwenden 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;
 }
Milind Deore
quelle