Bagaimana Anda melakukan iterasi melalui setiap file / direktori secara rekursif dalam C ++ standar?
Bagaimana Anda melakukan iterasi melalui setiap file / direktori secara rekursif dalam C ++ standar?
Jawaban:
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;
}
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::filesystem
merupakan bagian dari pustaka standar dan dapat ditemukan di <filesystem>
header (tidak lagi "eksperimental").
using
, gunakan namespace
sebagai gantinya.
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;
}
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;
}
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;
}
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.
wxDir
menyediakan 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 ()).
QDir
menyediakan 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;
}
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;
}
Anda dapat menggunakan ftw(3)
ataunftw(3)
menjalankan hierarki sistem file di C atau C ++ pada sistem POSIX .
nftw()
penggunaan.
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.
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.
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.
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 LegacyInputIterator
yang 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_entry
memiliki berbagai fungsi anggota yang berguna seperti is_regular_file
, is_directory
, is_socket
, is_symlink
dll 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 Ubuntu
dan 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;
}
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 .
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;
}
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.
File tree walk ftw
adalah cara rekursif untuk memagari seluruh pohon direktori di jalur. Detail selengkapnya ada di sini .
CATATAN: Anda juga dapat menggunakan fts
yang 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, *.png
file.) 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;
}