Jak rekurencyjnie przeglądać każdy plik / katalog w standardowym C ++?

115

Jak rekurencyjnie przeglądać każdy plik / katalog w standardowym C ++?

robottobor
źródło
Możesz sprawdzić boost.filesystem boost.org/doc/libs/1_31_0/libs/filesystem/doc/index.htm
Doug T.
1
Niestandardowy C ++: pocoproject.org/docs/Poco.DirectoryIterator.html
Agnel Kurian
1
Wkrótce powinno to być w standardzie poprzez Filesystem TS , z recursive_directory_iterator
Adi Shavit
Jeśli użycie standardowej biblioteki C nie przeszkadza w wywołaniu programu w C ++ jako „standardowego”, nftw () . Oto praktyczny przykład
6-ty
2
Ktoś, kto wie, co robi, powinien zająć godzinę, aby to zaktualizować.
Josh C,

Odpowiedzi:

99

W standardowym C ++ technicznie nie da się tego zrobić, ponieważ standardowy C ++ nie ma koncepcji katalogów. Jeśli chcesz trochę rozszerzyć swoją sieć, możesz spojrzeć na użycie Boost.FileSystem . Zostało to zaakceptowane do włączenia do TR2, więc daje to największą szansę na utrzymanie implementacji jak najbliżej standardu.

Przykład zaczerpnięty prosto ze strony:

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 INFORMACJE
źródło
5
C ++ nie ma pojęcia o plikach? A co ze std :: fstream? Lub fopen?
Kevin
30
pliki, a nie katalogi
1800 INFORMACJA
22
Aktualizacja w odniesieniu do najnowszej wersji boost: na wypadek, gdyby ktoś natknął się na tę odpowiedź, najnowszy boost zawiera wygodną klasę boost :: recursive_directory_iterator, więc pisanie powyższej pętli z jawnym wywołaniem rekurencyjnym nie jest już konieczne. Link: boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/ ...
JasDev,
5
VC ++ 11 ma prawie taką samą funkcjonalność w nagłówku <filesystem> w przestrzeni nazw std :: tr2 :: sys.
mheyman
3
Kiedyś była to dobra odpowiedź, ale teraz, gdy <filesystem> jest standardem, lepiej jest po prostu użyć jest (zobacz przykład w innych odpowiedziach).
Gathar
54

Począwszy od C ++ 17, <filesystem>nagłówek i zakres- for, możesz po prostu zrobić to:

#include <filesystem>

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

Od C ++ 17 std::filesystemjest częścią biblioteki standardowej i można go znaleźć w <filesystem>nagłówku (nie jest już „eksperymentalny”).

Adi Shavit
źródło
Unikaj używania using, użyj namespacezamiast tego.
Roi Danton
2
A czemu to? Lepiej dokładniej niż wprowadzanie rzeczy, których nie używasz.
Adi Shavit
Przejrzyj moją edycję, dodałem również brakujący standard przestrzeni nazw.
Roi Danton
5
<filesystem> nie jest już TS. Jest częścią C ++ 17. Prawdopodobnie powinieneś odpowiednio zaktualizować tę odpowiedź.
Niespodziewane
Uwaga dla użytkowników komputerów Mac wymaga to co najmniej OSX 10.15 (Catalina).
Justin
45

W przypadku korzystania z interfejsu API Win32 można używać funkcji FindFirstFile i FindNextFile .

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

W przypadku cyklicznego przeglądania katalogów należy sprawdzić wszystkie atrybuty WIN32_FIND_DATA.dwFileAttributes, aby sprawdzić, czy bit FILE_ATTRIBUTE_DIRECTORY jest ustawiony. Jeśli bit jest ustawiony, możesz rekurencyjnie wywołać funkcję z tym katalogiem. Alternatywnie możesz użyć stosu, aby zapewnić ten sam efekt wywołania rekurencyjnego, ale uniknąć przepełnienia stosu dla bardzo długich drzew ścieżek.

#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
źródło
19
jak długo zajęło ci napisanie tego? Myślę, że przyklejenie C ++ do Pythona i zrobienie tego w jednej linii zajmie mniej czasu.
Dustin Getz
2
To ładne, nierekurencyjne rozwiązanie (co czasami jest przydatne!).
jm.
1
Przy okazji, jeśli ktoś chce nieco zmodyfikować program, aby zaakceptować parametr wiersza poleceń argv [1] dla ścieżki zamiast zakodowanego na stałe ("F: \\ cvsrepos"), podpis dla main (int, char) ulegnie zmianie to wmain (int, wchar_t) w ten sposób: int wmain (int argc, wchar_t * argv [])
JasDev
1
Dzięki, ale ta funkcja nie działa w przypadku cyrylicy. Czy jest jakiś sposób, aby to działało ze znakami cyrylicy, takimi jak - б, в, г itp.?
unresolved_external
31

Możesz to jeszcze bardziej uprościć dzięki nowej gamie opartej na C ++ 11for i 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;
}
Matthieu G
źródło
5
Nie ma potrzeby doładowania. OP specjalnie poprosił o standardowe c ++.
Craig B
23

Szybkim rozwiązaniem jest użycie biblioteki Dirent.h języka C.

Działający fragment kodu z Wikipedii:

#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
źródło
5
Ta procedura nie jest rekurencyjna.
user501138
@TimCooper, oczywiście, że nie, dirent jest specyficzny dla POSIX.
Vorac
1
Właściwie to robi pracę na VC ++ jeśli masz port dirent.h dla C ++ visual Tony Rönkkö. To FOSS. Właśnie tego spróbowałem i działa.
user1741137
10

Oprócz wspomnianego powyżej boost :: filesystem możesz chcieć sprawdzić wxWidgets :: wxDir i Qt :: QDir .

Zarówno wxWidgets, jak i Qt są open source, wieloplatformowymi frameworkami C ++.

wxDirzapewnia elastyczny sposób przechodzenia między plikami rekurencyjnie przy użyciu Traverse()lub prostszej GetAllFiles()funkcji. Możesz również zaimplementować przechodzenie z funkcjami GetFirst()i GetNext()(zakładam, że Traverse () i GetAllFiles () są opakowaniami, które ostatecznie używają funkcji GetFirst () i GetNext ()).

QDirzapewnia dostęp do struktur katalogów i ich zawartości. Istnieje kilka sposobów przeglądania katalogów za pomocą QDir. Możesz iterować po zawartości katalogu (w tym podkatalogów) za pomocą QDirIterator, którego instancja została utworzona z flagą QDirIterator :: Subdirectories. Innym sposobem jest użycie funkcji GetEntryList () QDir i zaimplementowanie przechodzenia rekurencyjnego.

Oto przykładowy kod (pobrany stąd # Przykład 8-5), który pokazuje, jak iterować po wszystkich podkatalogach.

#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
źródło
Doxygen używa QT jako warstwy kompatybilności z systemem operacyjnym. Podstawowe narzędzia w ogóle nie używają GUI tylko z katalogu (i innych komponentów).
deft_code,
7

Boost :: filesystem zapewnia recursive_directory_iterator, co jest dość wygodne w przypadku tego zadania:

#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
źródło
1
Co to jest „to”, proszę? Czy nie ma błędu składni? A jak karmisz „koniec”? (= skąd wiemy, że przeanalizowaliśmy cały
katalog
1
@yO_ masz rację, wystąpiła literówka, domyślny konstruktor dla recursive_directory_iterator skonstruuje „nieprawidłowy” iterator, po zakończeniu iteracji po
katalogu
4

Ty nie. W standardzie C ++ nie ma koncepcji katalogów. Od implementacji zależy, czy ciąg znaków zostanie przekształcony w uchwyt pliku. Zawartość tego ciągu i to, na co jest mapowany, zależy od systemu operacyjnego. Pamiętaj, że C ++ może być użyty do napisania tego systemu operacyjnego, więc jest używany na poziomie, na którym pytanie, jak iterować przez katalog, nie jest jeszcze zdefiniowane (ponieważ piszesz kod zarządzania katalogami).

Zajrzyj do dokumentacji interfejsu API systemu operacyjnego, aby dowiedzieć się, jak to zrobić. Jeśli chcesz być przenośny, będziesz musiał mieć kilka #ifdef dla różnych systemów operacyjnych.

Matthew Scouten
źródło
4

Prawdopodobnie byłbyś najlepszy z eksperymentalnym systemem plików boost lub c ++ 14. JEŚLI analizujesz katalog wewnętrzny (tj. Używany przez program do przechowywania danych po zamknięciu programu), utwórz plik indeksowy, który będzie zawierał indeks zawartości pliku. Nawiasem mówiąc, prawdopodobnie będziesz musiał użyć boost w przyszłości, więc jeśli go nie masz, zainstaluj go! Po drugie, możesz użyć kompilacji warunkowej, np .:

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

Kod dla każdego przypadku pochodzi z 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
źródło
2

Musisz wywołać funkcje specyficzne dla systemu operacyjnego do przechodzenia przez system plików, takie jak open()i readdir(). Standard C nie określa żadnych funkcji związanych z systemem plików.

John Millikin
źródło
A co z C ++? Czy są takie funkcje w iostream?
Aaron Maenpaa
2
Tylko dla plików. Nie ma żadnych funkcji „pokaż mi wszystkie pliki w katalogu”.
1800 INFORMACJA
1
@ 1800: Katalogi to pliki.
Lightness Races in Orbit
2

Jesteśmy w 2019 roku. Mamy bibliotekę standardowych systemów plików w C++. PlikFilesystem libraryZapewnia zaplecze do przeprowadzania operacji na systemie plików i ich składników, takich jak ścieżki, zwykłych plików i katalogów.

Pod tym linkiem znajduje się ważna uwaga, jeśli rozważasz problemy z przenoszeniem. To mówi:

Funkcje biblioteki systemu plików mogą być niedostępne, jeśli hierarchiczny system plików nie jest dostępny dla implementacji lub jeśli nie zapewnia niezbędnych możliwości. Niektóre funkcje mogą nie być dostępne, jeśli nie są obsługiwane przez podstawowy system plików (np. System plików FAT nie ma dowiązań symbolicznych i zabrania wielu dowiązań twardych). W takich przypadkach należy zgłosić błędy.

Biblioteka systemu plików została pierwotnie opracowana jako boost.filesystem, została opublikowana jako specyfikacja techniczna ISO / IEC TS 18822: 2015, a ostatecznie połączona z ISO C ++ od C ++ 17. Implementacja przyspieszenia jest obecnie dostępna na większej liczbie kompilatorów i platform niż biblioteka C ++ 17.

@ adi-shavit odpowiedział na to pytanie, gdy było ono częścią std :: experimental i zaktualizował tę odpowiedź w 2017 roku. Chcę podać więcej szczegółów na temat biblioteki i pokazać bardziej szczegółowy przykład.

std :: filesystem :: recursive_directory_iterator to LegacyInputIteratoriteracja po elementach directory_entry katalogu i rekurencyjnie po wpisach wszystkich podkatalogów. Kolejność iteracji jest nieokreślona, ​​z wyjątkiem tego, że każda pozycja katalogu jest odwiedzana tylko raz.

Jeśli nie chcesz rekurencyjnie iterować po wpisach podkatalogów, powinieneś użyć directory_iterator .

Oba iteratory zwracają obiekt directory_entry . directory_entryma różne funkcje przydatne członków jak is_regular_file, is_directory, is_socket, is_symlinkitd path()funkcja członek zwraca obiekt std :: :: ścieżki systemu plików i może być używana do pobierania file extension, filename,root name .

Rozważ poniższy przykład. Używam Ubuntui skompilowane go na terminal za pomocą

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
źródło
1

Ty nie. Standardowy C ++ nie ujawnia koncepcji katalogu. W szczególności nie daje możliwości wyświetlenia wszystkich plików w katalogu.

Okropnym hackiem byłoby użycie wywołań system () i przeanalizowanie wyników. Najbardziej rozsądnym rozwiązaniem byłoby użycie jakiejś wieloplatformowej biblioteki, takiej jak Qt lub nawet POSIX .

shoosh
źródło
1

Możesz użyć std::filesystem::recursive_directory_iterator. Ale uwaga, obejmuje to symboliczne (miękkie) linki. Jeśli chcesz ich uniknąć, możesz użyć is_symlink. Przykładowe użycie:

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
źródło
1
Last but not least, właściwie lepsze niż poprzednie odpowiedzi.
Seyed Mehran Siadati
0

Jeśli korzystasz z systemu Windows, możesz używać FindFirstFile razem z interfejsem API FindNextFile. Możesz użyć FindFileData.dwFileAttributes, aby sprawdzić, czy podana ścieżka jest plikiem lub katalogiem. Jeśli jest to katalog, możesz rekurencyjnie powtórzyć algorytm.

Tutaj utworzyłem kod, który zawiera listę wszystkich plików na komputerze z systemem Windows.

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

Ibrahim
źródło
0

Spacer po drzewie plików ftwto rekurencyjny sposób umieszczenia ściany w całym drzewie katalogów w ścieżce. Więcej szczegółów tutaj .

UWAGA: Możesz również użyć ftstego, który może pomijać ukryte pliki, takie jak .lub ..lub.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;
}

wyjście wygląda następująco:

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

Powiedzmy, że jeśli chcesz dopasować nazwę pliku (na przykład: wyszukiwanie wszystkich *.jpg, *.jpeg, *.pngplików.) Do określonych potrzeb, użyj 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
źródło