it-swarm-vi.tech

Làm thế nào để bạn lặp lại qua mỗi tệp / thư mục đệ quy trong C ++ tiêu chuẩn?

Làm thế nào để bạn lặp lại qua mỗi tệp/thư mục đệ quy trong C++ tiêu chuẩn?

102
robottobor

Trong C++ tiêu chuẩn, về mặt kỹ thuật không có cách nào để làm điều này vì C++ tiêu chuẩn không có khái niệm về các 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 xem xét bằng cách 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 đến cho bạn cơ hội tốt nhất để giữ cho việc triển khai của bạn càng gần với tiêu chuẩn.

Một ví dụ, lấy thẳng 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;
}
94
1800 INFORMATION

Nếu sử dụng API Win32, bạn có thể sử dụng các hàm FindFirstFile FindNextFile .

http://msdn.Microsoft.com/en-us/l Library/aa365200 (VS, 85) .aspx

Để duyệt qua các thư mục đệ quy, bạn phải kiểm tra từng WIN32_FIND_DATA.dwFileAttribut để kiểm tra xem FILE_ATTRIBUTE_DIRECTORY bit được đặt. Nếu bit được đặt thì bạn có thể gọi hàm đệ quy với thư mục đó. Ngoài ra, bạn có thể sử dụng một ngăn xếp để cung cấp hiệu ứng tương tự của một cuộc gọi đệ quy nhưng tránh tràn ngăn xếp cho 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;
}
42
Jorge Ferreira

Với C++ 17, tiêu đề <filesystem> và phạm vi -for, bạn chỉ cần làm đ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 tiêu đề _<filesystem>_ (không còn là "thử nghiệm").

36
Adi Shavit

Bạn có thể làm cho nó đơn giản hơn nữa với C++ 11 phạm vi dựa trên forBoost :

#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;
}
31
Matthieu G

Một giải pháp nhanh là sử dụng thư viện C's Dirent.h .

Đ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;
}
23
Alex

Ngoài boost :: đề cập ở trên, bạn có thể muốn kiểm tra wxWidgets :: wxDirQt :: QĐir .

Cả wxWidgets và Qt đều là các khung C++ nền tảng mở.

wxDir cung cấp một cách linh hoạt để duyệt qua các tệp đệ quy bằng cách sử dụng Traverse() hoặc một hàm GetAllFiles() đơn giản hơn. Cũng như bạn có thể triển khai truyền tải với các hàm GetFirst()GetNext() (Tôi giả sử rằng Traverse () và GetAllFiles () là các hàm bao 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 QĐir. 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 QĐirIterator đã được khởi tạo bằng cờ QĐirIterator :: Subdirectories. Một cách khác là sử dụng hàm GetEntryList () của QĐir và thực hiện một giao dịch đệ quy.

Đây là mã mẫu (lấy từ tại đây # Ví dụ 8-5) cho biết 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;
}
10
mrvincenzo

Boost :: filesystem cung cấp recursive_directory_iterator, khá thuận tiện cho nhiệm 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;                                    
}
6
DikobrAz

Bạn có thể sử dụng ftw(3) HOẶC nftw(3) để đi theo cấu trúc phân cấp hệ thống tệp trong C hoặc C++ trên POSIX hệ thống.

4
leif

Bạn có thể là tốt nhất với công cụ hệ thống tập tin thử nghiệm của boost hoặc c ++ 14. IF bạn đang phân tích một thư mục nội bộ (nghĩa 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), sau đó tạo tệp chỉ mục có một chỉ mục của nội dung tập tin. Nhân tiện, có lẽ 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ó! Thứ hai, bạn có thể sử dụng một phần tổng hợp 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.
4
ndrewxie

Bạn không. Tiêu chuẩn C++ không có khái niệm về thư mục. Tùy thuộc vào việc thực hiện để biến một chuỗi thành một tập tin xử lý. Nội dung của chuỗi đó và những gì nó ánh xạ 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ĐH đó, vì vậy nó được sử dụng ở mức độ yêu cầu cách lặp qua thư mục 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 di động, bạn sẽ phải có một loạt # ifdef s cho các hệ điều hành khác nhau.

3
Matthew Scouten

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()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 tin.

2
John Millikin

Bạn không. Tiêu chuẩn C++ không tiếp xúc với 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 hack khủng khiếp sẽ là sử dụng các cuộc gọi system () và phân tích kết quả. Giải pháp hợp lý nhất là sử dụng một số loại thư viện đa nền tảng như Qt hoặc thậm chí POSIX .

1
shoosh

Chúng tôi đang ở trong năm 2019. Chúng tôi có hệ thống tập tin thư viện chuẩn trong C++. Filesystem library 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 về 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ác phương tiện thư viện hệ thống tập tin 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 khi triển khai hoặc nếu nó 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 các 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 tin ban đầu được phát triển là boost.filesystem, được xuất bản dưới dạng thông số kỹ thuật ISO/IEC TS 18822: 2015, và cuối cùng được sáp nhập vào 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 :: thử nghiệm 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 LegacyInputIterator lặp đi lặp lại qua các phần tử thư mục_entry của một thư mục và, đệ quy, trên các mục 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 trên các mục của thư mục con, thì nên sử dụng library_iterator .

Cả hai trình vòng lặp trả về một đối tượng library_entry . directory_entry có nhiều hàm thành viên hữu ích khác nhau như is_regular_file, is_directory, is_socket, is_symlink v.v ... Hàm path() trả về một đối tượng của - std :: filesystem :: path và nó có thể được sử dụng để lấy 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;
}
0
abhiarora