Bagaimana Anda melakukan iterasi melalui setiap file / direktori secara rekursif dalam C ++ standar?


115

Bagaimana Anda melakukan iterasi melalui setiap file / direktori secara rekursif dalam C ++ standar?


Anda mungkin ingin memeriksa boost.filesystem boost.org/doc/libs/1_31_0/libs/filesystem/doc/index.htm
Doug T.


1
Ini akan segera menjadi standar melalui TS Filesystem , dengan recursive_directory_iterator
Adi Shavit

Jika penggunaan pustaka C standar tidak menghalangi pemanggilan program C ++ sebagai 'standar', nftw () . Berikut adalah contoh
enam-k

2
Seseorang, yang tahu apa yang mereka lakukan, harus meluangkan waktu satu jam untuk memperbaruinya.
Josh C

Jawaban:


99

Dalam C ++ standar, secara teknis tidak ada cara untuk melakukan ini karena C ++ standar tidak memiliki konsep direktori. Jika Anda ingin memperluas jaringan Anda sedikit, Anda mungkin ingin menggunakan Boost.FileSystem . Ini telah diterima untuk disertakan dalam TR2, jadi ini memberi Anda peluang terbaik untuk menjaga penerapan Anda sedekat mungkin dengan standar.

Contoh, diambil langsung dari situs 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 ++ tidak memiliki konsep file? Bagaimana dengan std :: fstream? Atau fopen?
Kevin

30
file, bukan direktori
1800 INFORMATION

22
Pembaruan terkait dengan versi peningkatan terbaru: Jika ada yang menemukan jawaban ini, peningkatan terbaru menyertakan peningkatan kenyamanan kelas :: recursive_directory_iterator sehingga menulis loop di atas dengan panggilan rekursif eksplisit tidak lagi diperlukan. Tautan: boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/…
JasDev

5
VC ++ 11 hadir dengan fungsionalitas yang hampir sama di header <filesystem> di bawah namespace std :: tr2 :: sys.
mheyman

3
Ini dulunya adalah jawaban yang bagus, tapi sekarang <filesystem> adalah standar, lebih baik gunakan saja (lihat jawaban lain untuk contoh).
Gathar

54

Dari C ++ 17 dan seterusnya, <filesystem>header, dan range- for, Anda cukup melakukan ini:

#include <filesystem>

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

Mulai C ++ 17, std::filesystemmerupakan bagian dari pustaka standar dan dapat ditemukan di <filesystem>header (tidak lagi "eksperimental").


Hindari penggunaan using, gunakan namespacesebagai gantinya.
Roi Danton

2
Dan mengapa demikian? Lebih baik lebih spesifik daripada membawa barang-barang yang tidak Anda gunakan.
Adi Shavit

Tinjau suntingan saya, saya juga menambahkan namespace std yang hilang.
Roi Danton

5
<filesystem> bukan lagi TS. Itu bagian dari C ++ 17. Anda mungkin harus memperbarui jawaban ini sesuai.
IInspectable

Catatan untuk pengguna mac, ini membutuhkan OSX 10.15 (Catalina) minimal.
Justin

45

Jika menggunakan Win32 API Anda dapat menggunakan fungsi FindFirstFile dan FindNextFile .

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

Untuk traversal rekursif direktori Anda harus memeriksa setiap WIN32_FIND_DATA.dwFileAttributes untuk memeriksa apakah bit FILE_ATTRIBUTE_DIRECTORY disetel. Jika bit disetel maka Anda dapat memanggil fungsi secara rekursif dengan direktori itu. Alternatifnya, Anda dapat menggunakan tumpukan untuk memberikan efek yang sama dari panggilan rekursif tetapi menghindari tumpukan berlebih untuk pohon jalur yang sangat panjang.

#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
Berapa lama waktu yang Anda butuhkan untuk menulis itu? Saya pikir akan membutuhkan lebih sedikit waktu untuk merekatkan C ++ ke python dan melakukannya dalam satu baris.
Dustin Getz

2
Ini adalah solusi non-rekursif yang bagus (yang terkadang berguna!).
jm.

1
Btw, jika ada yang ingin mengedit program sedikit untuk menerima parameter baris perintah argv [1] untuk jalur daripada yang di-hardcode ("F: \\ cvsrepos"), tanda tangan untuk main (int, char) akan berubah ke wmain (int, wchar_t) seperti ini: int wmain (int argc, wchar_t * argv [])
JasDev

1
Terima kasih, tetapi fungsi ini tidak berfungsi dengan Cyrilic. Apakah ada cara untuk membuatnya berfungsi dengan karakter Sirilik seperti - б, в, г dll?
unresolved_external

31

Anda dapat membuatnya lebih sederhana dengan berbasis C ++ 11for dan Boost baru :

#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
Tidak perlu dorongan. OP secara khusus meminta standar c ++.
Craig B

23

Solusi cepat adalah menggunakan perpustakaan Dirent.h C.

Bagian kode yang berfungsi dari 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
Rutinitas ini tidak berulang.
pengguna501138

@TimCooper, tentu saja tidak, dirent spesifik posix.
Vorac

1
Sebenarnya itu tidak bekerja pada VC ++ jika Anda mendapatkan pelabuhan dirent.h untuk visual yang C ++ oleh Tony Ronkko. Ini FOSS. Saya baru saja mencoba ini dan berhasil.
pengguna1741137

10

Selain boost :: filesystem yang disebutkan di atas, Anda mungkin ingin memeriksa wxWidgets :: wxDir dan Qt :: QDir .

Baik wxWidgets dan Qt adalah framework C ++ sumber terbuka dan lintas platform.

wxDirmenyediakan cara yang fleksibel untuk melintasi file secara rekursif menggunakan Traverse()atau GetAllFiles()fungsi yang lebih sederhana . Anda juga dapat mengimplementasikan traversal dengan GetFirst()dan GetNext()fungsi (saya berasumsi bahwa Traverse () dan GetAllFiles () adalah pembungkus yang akhirnya menggunakan fungsi GetFirst () dan GetNext ()).

QDirmenyediakan akses ke struktur direktori dan isinya. Ada beberapa cara untuk menjelajahi direktori dengan QDir. Anda dapat mengulang isi direktori (termasuk sub-direktori) dengan QDirIterator yang dibuat dengan flag QDirIterator :: Subdirectories. Cara lain adalah dengan menggunakan fungsi GetEntryList () QDir dan mengimplementasikan traversal rekursif.

Berikut adalah contoh kode (diambil dari sini # Contoh 8-5) yang menunjukkan bagaimana melakukan iterasi pada semua sub direktori.

#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 menggunakan QT sebagai lapisan kompatibilitas OS-nya. Alat inti tidak menggunakan GUI sama sekali hanya barang direktori (dan komponen lain).
deft_code

7

Boost :: filesystem menyediakan recursive_directory_iterator, yang cukup nyaman untuk tugas ini:

#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
Tolong, apa itu "itu"? Apakah tidak ada kesalahan sintaks? Dan bagaimana Anda memberi makan "akhir"? (= seberapa tahu kita mengurai semua dir?)
yO_

1
@yO_ Anda benar ada kesalahan ketik, konstruktor default untuk recursive_directory_iterator akan membuat iterator "tidak valid", ketika Anda selesai mengulang dir itu akan mengubah "itu" menjadi tidak valid dan akan sama dengan "akhir"
DikobrAz


4

Kamu tidak. Standar C ++ tidak memiliki konsep direktori. Terserah pada implementasi untuk mengubah string menjadi pegangan file. Isi dari string itu dan apa yang dipetakannya bergantung pada OS. Perlu diingat bahwa C ++ dapat digunakan untuk menulis OS tersebut, sehingga digunakan pada level di mana menanyakan cara melakukan iterasi melalui direktori belum ditentukan (karena Anda sedang menulis kode manajemen direktori).

Lihat dokumentasi OS API Anda untuk mengetahui cara melakukannya. Jika Anda perlu portabel, Anda harus memiliki banyak #ifdef untuk berbagai OS.


4

Anda mungkin akan lebih baik jika menggunakan boost atau eksperimental c ++ 14 filesystem. JIKA Anda mengurai direktori internal (mis. Digunakan untuk program Anda untuk menyimpan data setelah program ditutup), maka buatlah file indeks yang memiliki indeks dari isi file. Ngomong-ngomong, Anda mungkin perlu menggunakan boost di masa mendatang, jadi jika Anda belum menginstalnya, instal! Kedua, Anda dapat menggunakan kompilasi bersyarat, misalnya:

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

Kode untuk setiap kasus diambil dari 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

Anda perlu memanggil fungsi khusus OS untuk traversal sistem file, seperti open()dan readdir(). Standar C tidak menentukan fungsi apa pun yang terkait dengan sistem file.


Bagaimana dengan C ++? Apakah ada fungsi seperti itu di iostream?
Aaron Maenpaa

2
Hanya untuk file. Tidak ada fungsi "tunjukkan semua file dalam direktori" apa pun.
1800 INFORMASI

1
@ 1800: Direktori adalah file.
Balapan Ringan di Orbit

2

Kami berada di tahun 2019. Kami memiliki perpustakaan standar sistem file di C++. ItuFilesystem library menyediakan fasilitas untuk melakukan operasi pada sistem file dan komponen mereka, seperti jalur, file biasa, dan direktori.

Ada catatan penting di tautan ini jika Anda mempertimbangkan masalah portabilitas. Ia mengatakan:

Fasilitas perpustakaan sistem file mungkin tidak tersedia jika sistem file hierarki tidak dapat diakses untuk implementasi, atau jika tidak menyediakan kemampuan yang diperlukan. Beberapa fitur mungkin tidak tersedia jika tidak didukung oleh sistem file yang mendasarinya (misalnya, sistem file FAT tidak memiliki tautan simbolik dan melarang banyak tautan keras). Dalam kasus tersebut, kesalahan harus dilaporkan.

Perpustakaan sistem file awalnya dikembangkan sebagai boost.filesystem , diterbitkan sebagai spesifikasi teknis ISO / IEC TS 18822: 2015, dan akhirnya digabungkan ke ISO C ++ pada C ++ 17. Implementasi boost saat ini tersedia di lebih banyak kompiler dan platform daripada library C ++ 17.

@ adi-shavit telah menjawab pertanyaan ini ketika itu adalah bagian dari std :: eksperimental dan dia telah memperbarui jawaban ini pada tahun 2017. Saya ingin memberikan detail lebih lanjut tentang perpustakaan dan menampilkan contoh yang lebih detail.

std :: filesystem :: recursive_directory_iterator adalah LegacyInputIteratoryang mengulangi elemen directory_entry dari sebuah direktori, dan, secara rekursif, atas entri dari semua subdirektori. Urutan iterasi tidak ditentukan, kecuali bahwa setiap entri direktori hanya dikunjungi sekali.

Jika Anda tidak ingin mengulang entri subdirektori secara rekursif, maka directory_iterator harus digunakan.

Kedua iterator mengembalikan objek directory_entry . directory_entrymemiliki berbagai fungsi anggota yang berguna seperti is_regular_file, is_directory, is_socket, is_symlinkdll path()fungsi anggota kembali sebuah objek dari std :: filesystem :: jalan dan dapat digunakan untuk mendapatkan file extension, filename, root name.

Perhatikan contoh di bawah ini. Saya telah menggunakan Ubuntudan menyusunnya melalui terminal menggunakan

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

Kamu tidak. Standar C ++ tidak mengekspos konsep direktori. Secara khusus itu tidak memberikan cara apa pun untuk mencantumkan semua file dalam sebuah direktori.

Peretasan yang mengerikan adalah menggunakan pemanggilan system () dan mengurai hasilnya. Solusi yang paling masuk akal adalah dengan menggunakan semacam pustaka lintas platform seperti Qt atau bahkan POSIX .


1

Anda bisa menggunakan std::filesystem::recursive_directory_iterator. Namun berhati-hatilah karena ini termasuk tautan simbolis (lunak). Jika Anda ingin menghindarinya, Anda dapat menggunakan is_symlink. Contoh penggunaan:

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
Last but not least, sebenarnya lebih baik dari jawaban sebelumnya.
Seyed Mehran Siadati

0

Jika Anda menggunakan Windows, Anda dapat menggunakan FindFirstFile bersama dengan FindNextFile API. Anda dapat menggunakan FindFileData.dwFileAttributes untuk memeriksa apakah jalur yang diberikan adalah file atau direktori. Jika ini adalah direktori, Anda dapat mengulangi algoritme secara rekursif.

Di sini, saya telah mengumpulkan beberapa kode yang mencantumkan semua file di mesin Windows.

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


0

File tree walk ftwadalah cara rekursif untuk memagari seluruh pohon direktori di jalur. Detail selengkapnya ada di sini .

CATATAN: Anda juga dapat menggunakan ftsyang dapat melewati file tersembunyi seperti .atau ..atau.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;
}

keluarannya terlihat seperti berikut:

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

Katakanlah jika Anda ingin mencocokkan nama file (contoh: mencari semua *.jpg, *.jpeg, *.pngfile.) Untuk kebutuhan tertentu, gunakan 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;
 }
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.