Standart C ++ ile her dosya / dizini yinelemeli olarak nasıl yinelersiniz?
Standart C ++ ile her dosya / dizini yinelemeli olarak nasıl yinelersiniz?
Yanıtlar:
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;
}
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::filesystem
itibariyle, standart kitaplığın bir parçasıdır ve <filesystem>
başlıkta bulunabilir (artık "deneysel" değildir).
using
, namespace
onun yerine kullanın.
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;
}
Yeni C ++ 11 aralığı tabanlı for
ve 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;
}
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;
}
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.
wxDir
dosyaları 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).
QDir
dizin 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;
}
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;
}
POSIX sistemlerinde C veya C ++ 'da bir dosya sistemi hiyerarşisini kullanabilir ftw(3)
veyanftw(3)
yürütebilirsiniz .
nftw()
kullanım için iyi bir öğretici görevi görür .
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 .
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.
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.
2019 yılındayız. İçinde dosya sistemi standart kitaplığımız var C++
. Filesystem library
Bö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.filesystem
ISO / 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 LegacyInputIterator
dizinin 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_entry
gibi çeşitli kullanışlı üye işlevi vardır is_regular_file
, is_directory
, is_socket
, is_symlink
vb 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 Ubuntu
ve 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;
}
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 .
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;
}
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.
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, *.png
Belirli 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;
}