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?


115

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?


Bạn có thể muốn kiểm tra boost.filesystem boost.org/doc/libs/1_31_0/libs/filesystem/doc/index.htm
Doug T.


1
Điều này cần sớm được trong các tiêu chuẩn thông qua hệ thống tập tin TS , với recursive_directory_iterator
Adi Shavit

Nếu việc sử dụng thư viện C chuẩn không cản trở việc gọi một chương trình C ++ là 'chuẩn', thì nftw () . Đây là một ví dụ
sáu giờ

2
Ai đó, những người biết họ đang làm gì, sẽ mất một giờ để cập nhật điều này.
Josh C

Câu trả lời:


99

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;
}

5
C ++ không có khái niệm về tệp? Còn std :: fstream thì sao? Hay fopen?
Kevin

30
tác phẩm, không phải thư mục
1800 INFORMATION

22
Cập nhật liên quan đến phiên bản tăng mới nhất: Trong trường hợp bất kỳ ai tình cờ gặp câu trả lời này, bản tăng mới nhất bao gồm một tăng cường lớp tiện lợi :: recursive_directory_iterator, vì vậy việc viết vòng lặp ở trên với lệnh gọi đệ quy rõ ràng không còn cần thiết. Liên kết: boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/…
JasDev

5
VC ++ 11 đi kèm với nhiều chức năng tương tự trong tiêu đề <filesystem> trong không gian tên std :: tr2 :: sys.
mheyman

3
Đây từng là một câu trả lời hay, nhưng bây giờ <filesystem> là tiêu chuẩn, tốt hơn hết bạn nên sử dụng is (xem các câu trả lời khác để làm ví dụ).
Gathar

54

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").


Tránh sử dụng using, sử dụng namespacethay thế.
Roi Danton

2
Và tại sao vậy? Tốt hơn cụ thể hơn là mang những thứ bạn không dùng đến.
Adi Shavit

Vui lòng xem lại chỉnh sửa của tôi, tôi cũng đã thêm std không gian tên bị thiếu.
Roi Danton

5
<filesystem> không còn là TS. Nó là một phần của C ++ 17. Bạn có thể nên cập nhật câu trả lời này cho phù hợp.
IInspectable

Lưu ý đối với người dùng mac, điều này yêu cầu tối thiểu OSX 10.15 (Catalina).
Justin

45

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

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;
}

19
bạn đã mất bao lâu để viết nó? Tôi nghĩ rằng sẽ mất ít thời gian hơn để gắn C ++ vào python và thực hiện nó trong một dòng.
Dustin Getz

2
Đây là một giải pháp tốt, không đệ quy (đôi khi rất tiện dụng!).
jm.

1
Btw, nếu ai đó muốn chỉnh sửa chương trình một chút để chấp nhận một tham số dòng lệnh argv [1] cho đường dẫn thay vì một đường dẫn được mã hóa cứng ("F: \\ cvsrepos"), thì chữ ký cho main (int, char) sẽ thay đổi thành wmain (int, wchar_t) như thế này: int wmain (int argc, wchar_t * argv [])
JasDev

1
Cảm ơn, nhưng chức năng này không hoạt động với Cyrilic. Có cách nào để làm cho nó hoạt động với các ký tự Cyrilic như - б, в, г, v.v. không?
unresolved_external

31

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 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;
}

5
Không cần tăng cường. OP đã yêu cầu cụ thể về c ++ chuẩn.
Craig B

23

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;
}

5
Quy trình này không đệ quy.
user501138

@TimCooper, tất nhiên là không, chỉ thị là cụ thể.
Vorac

1
Trên thực tế nó không làm việc trên VC ++ nếu bạn nhận được một cảng dirent.h cho visual C ++ bởi Tony Ronkko. Nó là phần mềm nguồn. Tôi chỉ thử điều này và nó hoạt động.
user1741137

10

Ngoài hệ thống tập tin boost :: được đề cập ở trên, bạn có thể muốn kiểm tra wxWidgets :: wxDirQt :: QDir .

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

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

QDircung 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;
}

Doxygen sử dụng QT làm lớp tương thích hệ điều hành của nó. Các công cụ cốt lõi hoàn toàn không sử dụng GUI mà chỉ sử dụng nội dung thư mục (và các trình so sánh khác).
deft_code

7

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;                                    
}

1
Làm ơn cho "nó" là gì? Không có lỗi cú pháp? Và làm thế nào để bạn nuôi "kết thúc"? (= làm sao biết chúng tôi đã phân tích cú pháp tất cả các dir?)
yO_

1
@yO_ bạn nói đúng là có một lỗi đánh máy, constructor mặc định cho recursive_directory_iterator sẽ xây dựng một "không hợp lệ" iterator, khi bạn đã hoàn thành iterating qua dir nó sẽ biến "nó" sẽ trở thành không hợp lệ và sẽ bằng "kết thúc"
DikobrAz


4

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.


4

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.

2

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.


Còn C ++ thì sao? Có bất kỳ chức năng nào như vậy trong iostream không?
Aaron Maenpaa

2
Chỉ dành cho tệp. Không có bất kỳ loại chức năng "hiển thị cho tôi tất cả các tệp trong một thư mục".
1800 INFORMATION,

1
@ 1800: Thư mục là tệp.
Lightness Races in Orbit

2

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 librarynà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.filesystemlà 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 LegacyInputIteratorlặ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_entrycó chức năng thành viên hữu ích khác nhau như is_regular_file, is_directory, is_socket, is_symlinkvv 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 Ubuntuvà 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;
}

1

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 .


1

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;
}

1
Cuối cùng nhưng không kém phần quan trọng, thực sự tốt hơn các câu trả lời trước đó.
Seyed Mehran Siadati

0

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.

http://dreams-soft.com/projects/traverse-directory


0

File tree walk ftwlà 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 ftscó 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, *.pngtệ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;
 }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.