คุณทำซ้ำทุกไฟล์ / ไดเร็กทอรีแบบวนซ้ำใน C ++ มาตรฐานได้อย่างไร
คุณทำซ้ำทุกไฟล์ / ไดเร็กทอรีแบบวนซ้ำใน C ++ มาตรฐานได้อย่างไร
คำตอบ:
ใน C ++ มาตรฐานในทางเทคนิคไม่มีวิธีใดที่จะทำได้เนื่องจาก C ++ มาตรฐานไม่มีแนวคิดเกี่ยวกับไดเร็กทอรี หากคุณต้องการที่จะขยายสุทธินิด ๆ หน่อย ๆ ของคุณคุณอาจต้องการที่จะมองไปที่การใช้Boost.FileSystem สิ่งนี้ได้รับการยอมรับสำหรับการรวมไว้ใน TR2 ดังนั้นสิ่งนี้จะช่วยให้คุณมีโอกาสที่ดีที่สุดในการทำให้การใช้งานของคุณใกล้เคียงกับมาตรฐานมากที่สุด
ตัวอย่างที่นำมาจากเว็บไซต์โดยตรง:
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 เป็นต้นไป<filesystem>
ส่วนหัวและช่วงfor
- คุณสามารถทำได้ง่ายๆ:
#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
เป็นส่วนหนึ่งของไลบรารีมาตรฐานและสามารถพบได้ใน<filesystem>
ส่วนหัว (ไม่ใช่ "การทดลอง" อีกต่อไป)
using
ใช้ใช้namespace
แทน
หากใช้ Win32 API คุณสามารถใช้ฟังก์ชันFindFirstFileและFindNextFile
http://msdn.microsoft.com/en-us/library/aa365200(VS.85).aspx
สำหรับการส่งผ่านไดเร็กทอรีแบบเรียกซ้ำคุณต้องตรวจสอบWIN32_FIND_DATA.dwFileAttributesแต่ละรายการเพื่อตรวจสอบว่าบิตFILE_ATTRIBUTE_DIRECTORYถูกตั้งค่าหรือไม่ หากตั้งค่าบิตคุณสามารถเรียกใช้ฟังก์ชันซ้ำกับไดเร็กทอรีนั้นได้ หรือคุณสามารถใช้สแต็กเพื่อให้เอฟเฟกต์เดียวกันของการเรียกซ้ำ แต่หลีกเลี่ยงการล้นสแต็กสำหรับทรีพา ธ ที่ยาวมาก
#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;
}
คุณสามารถทำให้ง่ายขึ้นได้ด้วยC ++ 11 range ใหม่ที่ใช้for
และ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;
}
วิธีแก้ปัญหาที่รวดเร็วคือการใช้ไลบรารีDirent.hของ C
ส่วนรหัสการทำงานจาก 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;
}
นอกเหนือจาก boost :: filesystem ที่กล่าวมาข้างต้นแล้วคุณอาจต้องการตรวจสอบwxWidgets :: wxDirและQt :: QDir Qt
ทั้ง wxWidgets และ Qt เป็นโอเพ่นซอร์สเฟรมเวิร์ก C ++ ข้ามแพลตฟอร์ม
wxDir
ให้วิธีที่ยืดหยุ่นในการสำรวจไฟล์แบบวนซ้ำโดยใช้Traverse()
หรือGetAllFiles()
ฟังก์ชันที่ง่ายกว่า คุณสามารถใช้การข้ามผ่านด้วยGetFirst()
และGetNext()
ฟังก์ชันได้เช่นกัน (ฉันคิดว่า Traverse () และ GetAllFiles () เป็นเครื่องห่อที่ใช้ฟังก์ชัน GetFirst () และ GetNext () ในที่สุด)
QDir
ให้การเข้าถึงโครงสร้างไดเร็กทอรีและเนื้อหา มีหลายวิธีในการสำรวจไดเรกทอรีด้วย QDir คุณสามารถวนซ้ำเนื้อหาไดเร็กทอรี (รวมถึงไดเร็กทอรีย่อย) ด้วย QDirIterator ที่สร้างอินสแตนซ์ด้วยแฟล็ก QDirIterator :: Subdirectories อีกวิธีหนึ่งคือการใช้ฟังก์ชัน GetEntryList () ของ QDir และใช้การส่งผ่านแบบวนซ้ำ
นี่คือโค้ดตัวอย่าง (นำมาจากที่นี่ # ตัวอย่างที่ 8-5) ที่แสดงวิธีการวนซ้ำในไดเรกทอรีย่อยทั้งหมด
#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 จัดเตรียม recursive_directory_iterator ซึ่งค่อนข้างสะดวกสำหรับงานนี้:
#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;
}
คุณสามารถใช้ftw(3)
หรือnftw(3)
เดินตามลำดับชั้นของระบบไฟล์ใน C หรือ C ++ บนระบบPOSIX
nftw()
การใช้งาน
คุณไม่ทำ มาตรฐาน C ++ ไม่มีแนวคิดเกี่ยวกับไดเรกทอรี ขึ้นอยู่กับการนำไปใช้เพื่อเปลี่ยนสตริงให้เป็นที่จับไฟล์ เนื้อหาของสตริงนั้นและสิ่งที่แมปนั้นขึ้นอยู่กับ OS โปรดทราบว่า C ++ สามารถใช้เพื่อเขียน OS นั้นได้ดังนั้นจึงถูกใช้ในระดับที่ยังไม่ได้กำหนดวิธีการถามซ้ำผ่านไดเร็กทอรี (เนื่องจากคุณกำลังเขียนโค้ดการจัดการไดเร็กทอรี)
ดูเอกสาร OS API ของคุณสำหรับวิธีการดำเนินการนี้ หากคุณต้องการพกพาคุณจะต้องมี#ifdef จำนวนมากสำหรับระบบปฏิบัติการต่างๆ
คุณน่าจะดีที่สุดกับระบบไฟล์ทดลองของ boost หรือ c ++ 14 หากคุณกำลังแยกวิเคราะห์ไดเร็กทอรีภายใน (เช่นใช้สำหรับโปรแกรมของคุณเพื่อจัดเก็บข้อมูลหลังจากปิดโปรแกรมแล้ว) ให้สร้างไฟล์ดัชนีที่มีดัชนีของเนื้อหาไฟล์ อย่างไรก็ตามคุณอาจต้องใช้บูสต์ในอนาคตดังนั้นหากคุณไม่ได้ติดตั้งให้ติดตั้ง! ประการที่สองคุณสามารถใช้การรวบรวมแบบมีเงื่อนไขเช่น:
#ifdef WINDOWS //define WINDOWS in your code to compile for windows
#endif
รหัสสำหรับแต่ละกรณีนำมาจาก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.
คุณจำเป็นต้องเรียกใช้ฟังก์ชัน OS เฉพาะสำหรับระบบแฟ้มข้ามเช่นและopen()
readdir()
มาตรฐาน C ไม่ได้ระบุฟังก์ชันที่เกี่ยวข้องกับระบบไฟล์
เราอยู่ในปี 2019 เรามีไลบรารีมาตรฐานระบบไฟล์ในC++
. Filesystem library
ให้สิ่งอำนวยความสะดวกสำหรับการดำเนินการดำเนินการบนระบบไฟล์และส่วนประกอบของพวกเขาเช่นเส้นทางไฟล์ปกติและไดเรกทอรี
มีหมายเหตุสำคัญในลิงก์นี้หากคุณกำลังพิจารณาปัญหาการพกพา มันบอกว่า:
สิ่งอำนวยความสะดวกไลบรารีระบบไฟล์อาจไม่พร้อมใช้งานหากระบบไฟล์แบบลำดับชั้นไม่สามารถเข้าถึงการนำไปใช้งานได้หรือหากไม่มีความสามารถที่จำเป็น คุณสมบัติบางอย่างอาจไม่พร้อมใช้งานหากระบบไฟล์พื้นฐานไม่รองรับ (เช่นระบบไฟล์ FAT ไม่มีลิงก์สัญลักษณ์และห้ามฮาร์ดลิงก์หลายลิงก์) ในกรณีดังกล่าวจะต้องรายงานข้อผิดพลาด
ไลบรารีระบบไฟล์ได้รับการพัฒนาboost.filesystem
โดยเผยแพร่เป็นข้อกำหนดทางเทคนิค ISO / IEC TS 18822: 2015 และในที่สุดก็รวมเข้ากับ ISO C ++ ณ C ++ 17 ขณะนี้การใช้งานบูสต์มีให้บริการในคอมไพเลอร์และแพลตฟอร์มมากกว่าไลบรารี C ++ 17
@ adi-shavit ได้ตอบคำถามนี้เมื่อเป็นส่วนหนึ่งของ std :: trial และเขาได้อัปเดตคำตอบนี้ในปี 2017 ฉันต้องการให้รายละเอียดเพิ่มเติมเกี่ยวกับห้องสมุดและแสดงตัวอย่างโดยละเอียดเพิ่มเติม
std :: filesystem :: recursive_directory_iteratorคือการLegacyInputIterator
วนซ้ำบนองค์ประกอบ directory_entry ของไดเร็กทอรีและวนซ้ำบนรายการของไดเร็กทอรีย่อยทั้งหมด ลำดับการวนซ้ำไม่ได้ระบุไว้ยกเว้นว่าแต่ละรายการไดเร็กทอรีจะถูกเยี่ยมชมเพียงครั้งเดียว
หากคุณไม่ต้องการวนซ้ำในรายการของไดเรกทอรีย่อยควรใช้directory_iterator
ทั้งสอง iterators ส่งกลับวัตถุของdirectory_entry directory_entry
มีฟังก์ชั่นสมาชิกที่มีประโยชน์ต่างๆเช่นis_regular_file
, is_directory
, is_socket
, is_symlink
ฯลฯpath()
ฟังก์ชันสมาชิกส่งกลับวัตถุของมาตรฐาน :: ระบบแฟ้ม :: เส้นทางและมันสามารถนำมาใช้เพื่อให้ได้file extension
, filename
,root name
,
ลองพิจารณาตัวอย่างด้านล่าง ฉันใช้Ubuntu
และรวบรวมผ่านเทอร์มินัลโดยใช้ไฟล์
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;
}
คุณไม่ทำ C ++ มาตรฐานไม่เปิดเผยแนวคิดของไดเร็กทอรี โดยเฉพาะอย่างยิ่งมันไม่ได้ให้วิธีใด ๆ ในการแสดงรายการไฟล์ทั้งหมดในไดเรกทอรี
การแฮ็กที่น่ากลัวคือการใช้การโทรของระบบ () และเพื่อแยกวิเคราะห์ผลลัพธ์ วิธีการแก้ปัญหาที่เหมาะสมที่สุดที่จะใช้ชนิดของห้องสมุดข้ามแพลตฟอร์มเช่นบางQtหรือแม้กระทั่งPOSIX
คุณสามารถใช้std::filesystem::recursive_directory_iterator
. แต่ระวังสิ่งนี้รวมถึงลิงก์สัญลักษณ์ (อ่อน) is_symlink
หากคุณต้องการที่จะหลีกเลี่ยงพวกเขาคุณสามารถใช้ ตัวอย่างการใช้งาน:
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 คุณสามารถใช้ FindFirstFile ร่วมกับ FindNextFile API คุณสามารถใช้ FindFileData.dwFileAttributes เพื่อตรวจสอบว่าพา ธ ที่กำหนดเป็นไฟล์หรือไดเร็กทอรี หากเป็นไดเรกทอรีคุณสามารถทำซ้ำอัลกอริทึมซ้ำได้
ที่นี่ฉันได้รวบรวมรหัสที่แสดงรายการไฟล์ทั้งหมดบนเครื่อง Windows
File tree walk ftw
เป็นวิธีการวนซ้ำในการสร้างไดเร็กทอรีไดเร็กทอรีทั้งหมดในพา ธ รายละเอียดเพิ่มเติมที่นี่
หมายเหตุ: คุณยังสามารถใช้fts
ที่สามารถข้ามไฟล์ที่ซ่อนอยู่เช่น.
หรือ..
หรือ.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;
}
ผลลัพธ์มีลักษณะดังนี้:
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
. ไฟล์) 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;
}