Standart C ++ ile her dosya / dizini yinelemeli olarak nasıl yinelersiniz?


115

Standart C ++ ile her dosya / dizini yinelemeli olarak nasıl yinelersiniz?





Standart bir C kitaplığının kullanılması, bir C ++ programını 'standart', nftw () olarak çağırmanın önüne geçmez . İşte pratik örneği
altı k

2
Ne yaptığını bilen birinin bunu güncellemesi bir saat sürmeli.
Josh C

Yanıtlar:


99

Standart C ++ 'da, teknik olarak bunu yapmanın bir yolu yoktur, çünkü standart C ++' da dizin kavramları yoktur. Ağınızı biraz genişletmek istiyorsanız, Boost.FileSystem'i kullanmak isteyebilirsiniz . Bunun TR2'ye dahil edilmesi kabul edildi, bu nedenle bu, uygulamanızı standarda mümkün olduğunca yakın tutmak için size en iyi şansı verir.

Doğrudan web sitesinden alınan bir örnek:

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ın dosya kavramı yok mu? Peki ya std :: fstream? Veya fopen?
Kevin

30
dizinler değil dosyalar
1800 BİLGİ

22
En son yükseltme sürümü ile ilgili güncelleme: Herhangi birinin bu yanıta rastlaması durumunda, en son destek bir kolaylık sınıfı desteği içerir: recursive_directory_iterator, bu nedenle yukarıdaki döngüyü açık özyinelemeli çağrı ile yazmak artık gerekli değildir. Bağlantı: boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/…
JasDev

5
VC ++ 11, std :: tr2 :: sys ad alanı altındaki <filesystem> başlığında hemen hemen aynı işlevselliğe sahiptir.
mheyman

3
Bu eskiden iyi bir cevaptı, ancak artık <filesystem> standart olduğundan, basitçe is kullanmak daha iyidir (örnek için diğer cevaplara bakın).
Gathar

54

C ++ 17'den itibaren, <filesystem>başlık ve aralık- for, bunu kolayca yapabilirsiniz:

#include <filesystem>

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

C ++ 17 std::filesystemitibariyle, standart kitaplığın bir parçasıdır ve <filesystem>başlıkta bulunabilir (artık "deneysel" değildir).


Kullanımından kaçının using, namespaceonun yerine kullanın.
Roi Danton

2
Ve neden böyle? Kullanmadığınız şeyleri getirmekten daha spesifiktir.
Adi Shavit

Düzenlememi lütfen gözden geçirin, eksik ad alanını da ekledim std.
Roi Danton

5
<filesystem> artık bir TS değil. C ++ 17'nin bir parçasıdır. Muhtemelen bu yanıtı buna göre güncellemelisiniz.
2017

Mac kullanıcıları için bu, minimum OSX 10.15 (Catalina) gerektirir.
Justin

45

Win32 API kullanıyorsanız FindFirstFile ve FindNextFile işlevlerini kullanabilirsiniz.

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

Dizinlerin özyinelemeli geçişi için, FILE_ATTRIBUTE_DIRECTORY bitinin ayarlanıp ayarlanmadığını kontrol etmek için her WIN32_FIND_DATA.dwFileAttributes'ı incelemelisiniz . Bit ayarlanmışsa, işlevi o dizinle özyinelemeli olarak çağırabilirsiniz. Alternatif olarak, özyinelemeli bir çağrının aynı etkisini sağlamak, ancak çok uzun yol ağaçlarında yığın taşmasını önlemek için bir yığın kullanabilirsiniz.

#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
bunu yazman ne kadar sürdü? C ++ 'yı python'a yapıştırmanın ve bunu tek satırda yapmanın daha az zaman alacağını düşünüyorum.
Dustin Getz

2
Bu güzel, özyinelemeli olmayan bir çözümdür (bu bazen kullanışlıdır!).
jm.

1
Btw, herhangi biri programı sabit kodlu bir parametre yerine yol için argv [1] komut satırı parametresini ("F: \\ cvsrepos") kabul etmek üzere biraz düzenlemek isterse, main (int, char) imzası değişir wmain (int, wchar_t) şöyle: int wmain (int argc, wchar_t * argv [])
JasDev

1
Teşekkürler, ancak bu işlev Cyrilic ile çalışmıyor. - б, в, г gibi Kiril karakterleriyle çalışmasını sağlamanın bir yolu var mı?
unresolved_external

31

Yeni C ++ 11 aralığı tabanlı forve Boost ile bunu daha da basitleştirebilirsiniz :

#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
Güçlendirmeye gerek yok. OP özellikle standart c ++ istedi.
Craig B

23

Hızlı bir çözüm, C'nin Dirent.h kitaplığını kullanmaktır.

Wikipedia'dan çalışan kod parçası:

#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
Bu rutin yinelemeli değildir.
user501138

@TimCooper, tabii ki öyle değil, dirent posix'e özel.
Vorac

1
Aslında yapar Tony Rönkkö tarafından ++ görsel C dirent.h bir port alırsanız ++ VC üzerinde çalışmaya. Bu FOSS. Bunu sadece denedim ve işe yarıyor.
user1741137

10

Yukarıda belirtilen boost :: dosya sistemine ek olarak, wxWidgets :: wxDir ve Qt :: QDir'i incelemek isteyebilirsiniz .

Hem wxWidgets hem de Qt, açık kaynaklı, çapraz platform C ++ çerçeveleridir.

wxDirdosyaları yinelemeli olarak Traverse()veya daha basit bir GetAllFiles()işlev kullanarak geçmek için esnek bir yol sağlar . Ayrıca çapraz geçişi GetFirst()ve GetNext()işlevleriyle uygulayabilirsiniz (Traverse () ve GetAllFiles () 'ın sonunda GetFirst () ve GetNext () işlevlerini kullanan sarmalayıcılar olduğunu varsayıyorum).

QDirdizin yapılarına ve içeriklerine erişim sağlar. QDir ile dizinlerde gezinmenin birkaç yolu vardır. QDirIterator :: Subdirectories bayrağı ile başlatılan QDirIterator ile dizin içeriğini (alt dizinler dahil) yineleyebilirsiniz. Başka bir yol, QDir'in GetEntryList () işlevini kullanmak ve özyinelemeli bir geçiş uygulamaktır.

İşte tüm alt dizinlerde nasıl yineleme yapılacağını gösteren örnek kod ( burada # Örnek 8-5'ten alınmıştır).

#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, işletim sistemi uyumluluk katmanı olarak QT'yi kullanır. Çekirdek araçlar bir GUI'yi yalnızca dizin malzemelerinde (ve diğer bileşenlerde) kullanmaz.
deft_code

7

Boost :: filesystem, bu görev için oldukça uygun olan recursive_directory_iterator'ı sağlar:

#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
"O" nedir lütfen? Sözdizimi hatası yok mu? Ve "sonu" nasıl besliyorsunuz? (= tüm dizini ayrıştırdığımızı nasıl anlarsınız?)
yO_

1
@yO_ doğru bir yazım hatası, recursive_directory_iterator için varsayılan yapıcı inşa edecek vardı ediyoruz "geçersiz" yineleyici, sen ilerlerken üzerinde o "o" geçersiz olur dönecek ve "son" eşit olacaktır Dir bittiğinde
DikobrAz


4

Yapmıyorsun. C ++ standardında dizin kavramı yoktur. Bir dizeyi bir dosya tanıtıcısına dönüştürmek uygulamaya bağlıdır. Bu dizenin içeriği ve ne ile eşleştiği işletim sistemine bağlıdır. C ++ 'nın bu işletim sistemini yazmak için kullanılabileceğini unutmayın; bu nedenle, bir dizinde nasıl yineleme yapılacağını sormanın henüz tanımlanmadığı bir düzeyde kullanılır (çünkü dizin yönetim kodunu yazıyorsunuz).

Bunun nasıl yapılacağını öğrenmek için OS API belgelerinize bakın. Taşınabilir olmanız gerekiyorsa, çeşitli işletim sistemleri için bir grup # ifdef'e sahip olmanız gerekir .


4

Yükseltme veya c ++ 14'ün deneysel dosya sistemi öğeleri ile muhtemelen en iyisi olursunuz. Eğer dahili bir dizini ayrıştırıyorsanız (yani programınızın program kapatıldıktan sonra verileri depolaması için kullanılırsa), dosya içeriğinin bir dizinine sahip bir dizin dosyası oluşturun. Bu arada, ileride muhtemelen boost kullanmanız gerekecek, bu yüzden eğer kurulu değilse kurun! İkincisi, koşullu bir derleme kullanabilirsiniz, örneğin:

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

Her durum için kod https://stackoverflow.com/a/67336/7077165 adresinden alınır.

#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

Dosya sistemi geçişi için işletim sistemine özgü işlevleri çağırmanız gerekir, örneğin open()ve readdir(). C standardı, dosya sistemiyle ilgili herhangi bir işlev belirtmez.


C ++ ne olacak? İostream'de bu tür işlevler var mı?
Aaron Maenpaa

2
Yalnızca dosyalar için. Herhangi bir "bana bir dizindeki tüm dosyaları göster" işlevi yoktur.
1800 BİLGİ

1
@ 1800: Dizinler dosyalardır.
Orbit'te Hafiflik Yarışları

2

2019 yılındayız. İçinde dosya sistemi standart kitaplığımız var C++. Filesystem libraryBöyle yolları, düzenli dosyaları ve dizinleri gibi dosya sistemleri ve bileşenleri, üzerinde işlem gerçekleştirmek için olanaklar sağlar.

Taşınabilirlik sorunlarını düşünüyorsanız , bu bağlantıda önemli bir not var . Diyor ki:

Hiyerarşik bir dosya sistemi uygulama tarafından erişilebilir değilse veya gerekli yetenekleri sağlamıyorsa, dosya sistemi kitaplığı olanakları kullanılamayabilir. Bazı özellikler, temel alınan dosya sistemi tarafından desteklenmiyorsa kullanılamayabilir (örneğin, FAT dosya sistemi sembolik bağlardan yoksundur ve birden çok sabit bağlantıyı yasaklar). Bu durumlarda hatalar rapor edilmelidir.

Dosya sistemi kitaplığı başlangıçta boost.filesystemISO / IEC TS 18822: 2015 teknik şartname olarak yayınlandı ve sonunda C ++ 17 itibariyle ISO C ++ ile birleştirildi. Güçlendirme uygulaması şu anda C ++ 17 kitaplığından daha fazla derleyici ve platformda mevcuttur.

@ adi-shavit bu soruyu std :: experimental kapsamındayken cevapladı ve bu cevabı 2017'de güncelledi. Kütüphane hakkında daha fazla detay vermek ve daha detaylı örnek göstermek istiyorum.

std :: filesystem :: recursive_directory_iterator , bir LegacyInputIteratordizinin directory_entry öğeleri üzerinde ve tüm alt dizinlerin girişleri üzerinde yinelemeli olarak yinelemeli bir işlemdir. Yineleme sırası, her bir rehber girişinin yalnızca bir kez ziyaret edilmesi dışında belirtilmez.

Alt dizinlerin girdileri üzerinde yinelemeli olarak yinelemeli yineleme yapmak istemiyorsanız, directory_iterator kullanılmalıdır.

Her iki yineleyici de bir directory_entry nesnesi döndürür . directory_entrygibi çeşitli kullanışlı üye işlevi vardır is_regular_file, is_directory, is_socket, is_symlinkvb path()üye işlevini verir bir amacı std :: dosya sistemi :: yolu ve almak için kullanılabilir file extension, filename, root name.

Aşağıdaki örneği düşünün. Ben kullanarak olmuştur Ubuntuve kullanma terminali üzerine derlenmiş

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

Yapmıyorsun. Standart C ++, bir dizin kavramına maruz kalmaz. Özellikle, bir dizindeki tüm dosyaları listelemenin herhangi bir yolunu vermez.

Sistem () çağrılarını kullanmak ve sonuçları ayrıştırmak korkunç bir hack olacaktır. En makul çözüm, Qt veya hatta POSIX gibi bir tür çapraz platform kitaplığı kullanmak olacaktır .


1

Kullanabilirsiniz std::filesystem::recursive_directory_iterator. Ancak bunun sembolik (yumuşak) bağlantılar içerdiğine dikkat edin. Onlardan kaçınmak istiyorsanız kullanabilirsiniz is_symlink. Örnek kullanım:

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
Son fakat en az değil, aslında önceki cevaplardan daha iyi.
Seyed Mehran Siadati

0

Windows kullanıyorsanız, FindFirstFile'ı FindNextFile API ile birlikte kullanabilirsiniz. Belirli bir yolun dosya mı yoksa dizin mi olduğunu kontrol etmek için FindFileData.dwFileAttributes'ı kullanabilirsiniz. Bir dizin ise, algoritmayı özyinelemeli olarak tekrarlayabilirsiniz.

Burada, bir Windows makinesindeki tüm dosyaları listeleyen bazı kodları bir araya getirdim.

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


0

Dosya ağacı yürüyüşü ftw, yoldaki tüm dizin ağacını örtmenin yinelemeli bir yoludur. Daha fazla ayrıntı burada .

NOT: Ayrıca fts, .veya ..veya gibi gizli dosyaları atlayabilirsiniz..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;
}

çıktı aşağıdaki gibi görünür:

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

*.jpg, *.jpeg, *.pngBelirli bir ihtiyaç için bir dosya adıyla eşleştirmek istiyorsanız (örneğin: tüm dosyaları aramak), diyelim 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;
 }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.