Làm thế nào để bạn lặp lại mọi tệp / thư mục một cách đệ quy trong C ++ chuẩn?
Làm thế nào để bạn lặp lại mọi tệp / thư mục một cách đệ quy trong C ++ chuẩn?
Câu trả lời:
Trong C ++ chuẩn, về mặt kỹ thuật không có cách nào để làm điều này vì C ++ chuẩn không có khái niệm về thư mục. Nếu bạn muốn mở rộng mạng lưới của mình một chút, bạn có thể muốn sử dụng Boost.FileSystem . Điều này đã được chấp nhận để đưa vào TR2, vì vậy, điều này mang lại cho bạn cơ hội tốt nhất để giữ cho việc triển khai của mình gần với tiêu chuẩn nhất có thể.
Một ví dụ, lấy trực tiếp từ trang 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;
}
Từ C ++ 17 trở đi, <filesystem>
tiêu đề và dải ô- for
, bạn có thể chỉ cần thực hiện điều này:
#include <filesystem>
using recursive_directory_iterator = std::filesystem::recursive_directory_iterator;
...
for (const auto& dirEntry : recursive_directory_iterator(myPath))
std::cout << dirEntry << std::endl;
Kể từ C ++ 17, std::filesystem
là một phần của thư viện chuẩn và có thể được tìm thấy trong <filesystem>
tiêu đề (không còn là "thử nghiệm").
using
, sử dụng namespace
thay thế.
Nếu sử dụng Win32 API, bạn có thể sử dụng các hàm FindFirstFile và FindNextFile .
http://msdn.microsoft.com/en-us/library/aa365200(VS.85).aspx
Để duyệt đệ quy các thư mục, bạn phải kiểm tra từng WIN32_FIND_DATA.dwFileAttributes để kiểm tra xem bit FILE_ATTRIBUTE_DIRECTORY có được đặt hay không. Nếu bit được đặt thì bạn có thể gọi một cách đệ quy hàm với thư mục đó. Ngoài ra, bạn có thể sử dụng ngăn xếp để cung cấp tác dụng tương tự của lệnh gọi đệ quy nhưng tránh tràn ngăn xếp đối với các cây đường dẫn rất dài.
#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;
}
Bạn có thể làm cho nó đơn giản hơn với phạm vi C ++ 11 mới dựa trên for
và 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;
}
Một giải pháp nhanh chóng là sử dụng C's Dirent.h thư viện
Đoạn mã làm việc từ 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;
}
Ngoài hệ thống tập tin boost :: được đề cập ở trên, bạn có thể muốn kiểm tra wxWidgets :: wxDir và Qt :: QDir .
Cả wxWidgets và Qt đều là các khung C ++ mã nguồn mở, đa nền tảng.
wxDir
cung cấp một cách linh hoạt để duyệt qua các tệp một cách đệ quy bằng cách sử dụng Traverse()
hoặc một GetAllFiles()
hàm đơn giản hơn . Đồng thời, bạn có thể triển khai truyền tải với GetFirst()
vàGetNext()
hàm (tôi giả sử rằng Traverse () và GetAllFiles () là các trình bao bọc cuối cùng sử dụng các hàm GetFirst () và GetNext ()).
QDir
cung cấp quyền truy cập vào cấu trúc thư mục và nội dung của chúng. Có một số cách để duyệt qua các thư mục với QDir. Bạn có thể lặp lại nội dung thư mục (bao gồm cả các thư mục con) với QDirIterator đã được khởi tạo bằng cờ QDirIterator :: Subdirectories. Một cách khác là sử dụng hàm GetEntryList () của QDir và thực hiện duyệt đệ quy.
Đây là mã mẫu (lấy từ đây # Ví dụ 8-5) cho thấy cách lặp lại trên tất cả các thư mục con.
#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 cung cấp recursive_directory_iterator, khá thuận tiện cho tác vụ này:
#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;
}
Bạn có thể sử dụng ftw(3)
hoặcnftw(3)
đi bộ phân cấp hệ thống tệp trong C hoặc C ++ trên hệ thống POSIX .
nftw()
sử dụng.
Bạn không. Tiêu chuẩn C ++ không có khái niệm về thư mục. Việc biến một chuỗi thành một tệp xử lý tùy thuộc vào việc triển khai. Nội dung của chuỗi đó và những gì nó ánh xạ đến phụ thuộc vào hệ điều hành. Hãy nhớ rằng C ++ có thể được sử dụng để viết hệ điều hành đó, vì vậy nó được sử dụng ở mức độ yêu cầu cách lặp qua một thư mục vẫn chưa được xác định (vì bạn đang viết mã quản lý thư mục).
Xem tài liệu API hệ điều hành của bạn để biết cách thực hiện việc này. Nếu bạn cần phải di động, bạn sẽ phải có một loạt các #ifdef cho các hệ điều hành khác nhau.
Bạn có thể sẽ tốt nhất với công cụ hệ thống tệp thử nghiệm của boost hoặc c ++ 14. NẾU bạn đang phân tích cú pháp một thư mục nội bộ (tức là được sử dụng cho chương trình của bạn để lưu trữ dữ liệu sau khi chương trình bị đóng), thì hãy tạo một tệp chỉ mục có chỉ mục nội dung tệp. Nhân tiện, có thể bạn sẽ cần sử dụng boost trong tương lai, vì vậy nếu bạn chưa cài đặt nó, hãy cài đặt nó! Trước hết, bạn có thể sử dụng biên dịch có điều kiện, ví dụ:
#ifdef WINDOWS //define WINDOWS in your code to compile for windows
#endif
Mã cho mỗi trường hợp được lấy từ 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.
Bạn cần gọi các chức năng dành riêng cho hệ điều hành để truyền tải hệ thống tệp, như open()
và readdir()
. Tiêu chuẩn C không chỉ định bất kỳ chức năng nào liên quan đến hệ thống tệp.
Chúng tôi đang ở trong năm 2019. Chúng tôi có thư viện tiêu chuẩn hệ thống tệp trong C++
. Phần mềm Filesystem library
này cung cấp các phương tiện để thực hiện các hoạt động trên hệ thống tệp và các thành phần của chúng, chẳng hạn như đường dẫn, tệp thông thường và thư mục.
Có một lưu ý quan trọng trên liên kết này nếu bạn đang xem xét các vấn đề về tính di động. Nó nói rằng:
Cơ sở thư viện hệ thống tệp có thể không khả dụng nếu hệ thống tệp phân cấp không thể truy cập được để triển khai hoặc nếu hệ thống tệp không cung cấp các khả năng cần thiết. Một số tính năng có thể không khả dụng nếu chúng không được hệ thống tệp bên dưới hỗ trợ (ví dụ: hệ thống tệp FAT thiếu liên kết tượng trưng và cấm nhiều liên kết cứng). Trong những trường hợp đó, lỗi phải được báo cáo.
Thư viện hệ thống tệp ban đầu được phát triển với tên gọi boost.filesystem
là tiêu chuẩn kỹ thuật ISO / IEC TS 18822: 2015 và cuối cùng được hợp nhất thành ISO C ++ kể từ C ++ 17. Việc triển khai boost hiện có sẵn trên nhiều trình biên dịch và nền tảng hơn thư viện C ++ 17.
@ adi-shavit đã trả lời câu hỏi này khi nó là một phần của std :: Experiment và anh ấy đã cập nhật câu trả lời này vào năm 2017. Tôi muốn cung cấp thêm chi tiết về thư viện và hiển thị ví dụ chi tiết hơn.
std :: filesystem :: recursive_directory_iterator là một trình LegacyInputIterator
lặp lại trên các phần tử directory_entry của một thư mục và đệ quy qua các mục nhập của tất cả các thư mục con. Thứ tự lặp là không xác định, ngoại trừ mỗi mục nhập thư mục chỉ được truy cập một lần.
Nếu bạn không muốn lặp lại đệ quy qua các mục nhập của thư mục con, thì nên sử dụng directory_iterator .
Cả hai trình vòng lặp đều trả về một đối tượng của directory_entry . directory_entry
có chức năng thành viên hữu ích khác nhau như is_regular_file
, is_directory
, is_socket
, is_symlink
vv Các path()
hàm thành viên trở về một đối tượng của std :: hệ thống tập tin :: con đường và nó có thể được sử dụng để có được file extension
, filename
, root name
.
Hãy xem xét ví dụ dưới đây. Tôi đã sử dụng Ubuntu
và biên dịch nó qua thiết bị đầu cuối bằng cách sử dụng
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;
}
Bạn không. C ++ chuẩn không thể hiện khái niệm thư mục. Cụ thể, nó không cung cấp bất kỳ cách nào để liệt kê tất cả các tệp trong một thư mục.
Một vụ hack khủng khiếp sẽ là sử dụng lệnh gọi hệ thống () và phân tích kết quả. Giải pháp hợp lý nhất sẽ là sử dụng một số loại thư viện đa nền tảng như Qt hoặc thậm chí là POSIX .
Bạn có thể sử dụng std::filesystem::recursive_directory_iterator
. Nhưng hãy cẩn thận điều này bao gồm các liên kết tượng trưng (mềm). Nếu bạn muốn tránh chúng, bạn có thể sử dụng is_symlink
. Ví dụ sử dụng:
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;
}
Nếu bạn đang sử dụng Windows, bạn có thể sử dụng FindFirstFile cùng với API FindNextFile. Bạn có thể sử dụng FindFileData.dwFileAttributes để kiểm tra xem đường dẫn đã cho là tệp hay thư mục. Nếu đó là một thư mục, bạn có thể lặp lại một cách đệ quy thuật toán.
Ở đây, tôi đã tập hợp một số mã liệt kê tất cả các tệp trên máy Windows.
File tree walk ftw
là một cách đệ quy để đưa toàn bộ cây thư mục vào đường dẫn. Thêm chi tiết ở đây .
LƯU Ý: Bạn cũng có thể sử dụng fts
có thể bỏ qua các tệp ẩn như .
hoặc ..
hoặc.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;
}
đầu ra trông giống như sau:
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
Hãy để chúng tôi nói nếu bạn muốn khớp một tên tệp (ví dụ: tìm kiếm tất cả các *.jpg, *.jpeg, *.png
tệp.) Cho một nhu cầu cụ thể, hãy sử dụng 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;
}