C++檔案系統操作5 - 跨平臺列出指定目錄下的所有檔案和資料夾

陌尘(MoChen)發表於2024-07-25
  • 1. 關鍵詞
  • 2. fileutil.h
  • 3. fileutil.cpp
  • 4. filesystem_win.h
  • 5. filesystem_win.cpp
  • 6. filesystem_unix.cpp
  • 7. 原始碼地址

1. 關鍵詞

C++ 檔案系統操作 列出指定目錄下的所有檔案 列出指定目錄下的所有資料夾 跨平臺

2. fileutil.h


#pragma once

#include <string>
#include <cstdio>
#include <cstdint>
#include "filetype.h"
#include "filepath.h"

namespace cutl
{

    /**
     * @brief The file guard class to manage the FILE pointer automatically.
     * file_guard object can close the FILE pointer automatically when his scope is exit.
     */
    class file_guard
    {
    public:
        /**
         * @brief Construct a new file guard object
         *
         * @param file the pointer of the FILE object
         */
        explicit file_guard(FILE *file);

        /**
         * @brief Destroy the file guard object
         *
         */
        ~file_guard();

        /**
         * @brief Get the FILE pointer.
         *
         * @return FILE*
         */
        FILE *getfd() const;

    private:
        FILE *file_;
    };

    /**
     * @brief List all files in a directory.
     *
     * @param dirpath the filepath of the directory to be listed
     * @param type file type to be listed, default is all types.
     * @param recursive whether to list the whole directory recursively, default is false.
     * If true, means list the whole directory recursively, like the 'ls -R' command.
     * @return filevec the vector of file_entity, filepaths in the directory.
     */
    filevec list_files(const filepath &dirpath, filetype type = filetype::all, bool recursive = false);

} // namespace cutl

3. fileutil.cpp

#include <cstdio>
#include <map>
#include <iostream>
#include <cstring>
#include <sys/stat.h>
#include "fileutil.h"
#include "inner/logger.h"
#include "inner/filesystem.h"
#include "strutil.h"

namespace cutl
{
    file_guard::file_guard(FILE *file)
        : file_(file)
    {
    }

    file_guard::~file_guard()
    {
        if (file_)
        {
            // CUTL_DEBUG("close file");
            int ret = fclose(file_);
            if (ret != 0)
            {
                CUTL_ERROR("fail to close file, ret" + std::to_string(ret));
            }
            file_ = nullptr;
        }
        // ROBOLOG_DCHECK(file_ == nullptr);
    }

    FILE *file_guard::getfd() const
    {
        return file_;
    }

    filevec list_files(const filepath &dirpath, filetype type, bool recursive)
    {
        return list_sub_files(dirpath.str(), type, recursive);
    }

4. filesystem_win.h


#include <vector>
#include <string>

#pragma once

namespace cutl
{
    filetype get_file_type(const std::string &filepath);
    filevec list_sub_files(const std::string &dirpath, filetype type = filetype::all, bool recursive = false);
} // namespace cutl

5. filesystem_win.cpp

#if defined(_WIN32) || defined(__WIN32__)

#include <io.h>
#include <direct.h>
#include <Windows.h>
#include <stdlib.h>
#include "strutil.h"
#include "filesystem.h"
#include "logger.h"

namespace cutl
{
    filetype get_file_type(DWORD attributes, const std::string &extension)
    {
        filetype type = filetype::unknown;

        if (attributes == INVALID_FILE_ATTRIBUTES)
        {
            CUTL_WARN("Failed to get file attributes, error code: " + std::to_string(GetLastError()));
            if (extension == ".lnk")
            {
                // 注意:測試時發現,有些快捷方式訪問會失敗,用字尾名判斷進行兜底
                type = filetype::symlink;
            }
            return type;
        }
        else
        {
            if (attributes & FILE_ATTRIBUTE_DIRECTORY)
            {
                type = filetype::directory; // directory
            }
            else if (attributes & FILE_ATTRIBUTE_NORMAL || attributes & FILE_ATTRIBUTE_READONLY)
            {
                // 普通檔案|只讀檔案
                type = filetype::file; // regular file
            }
            else if ((attributes & FILE_ATTRIBUTE_ARCHIVE) && extension == ".lnk")
            {
                // windows的快捷方式
                type = filetype::symlink; // symbolic link
            }
        }

        return type;
    }

    std::string get_file_extension(const std::string &filepath)
    {
        auto pos = filepath.find_last_of('.');
        std::string extension = "";
        if (pos != std::string::npos)
        {
            extension = filepath.substr(pos);
            extension = cutl::to_lower(extension);
        }
        return extension;
    }

    // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileattributesa
    // https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
    filetype get_file_type(const std::string &filepath)
    {
        auto attributes = GetFileAttributesA(filepath.c_str());
        auto extension = get_file_extension(filepath);
        // CUTL_DEBUG(filepath + ", extension: " + extension + ", attributes: " + std::to_string(attributes));
        return get_file_type(attributes, extension);
    }

    filevec list_sub_files(const std::string &dirpath, filetype type, bool recursive)
    {
        filevec file_list;

        // 使用FindFirstFileA查詢第一個檔案
        auto findpath = dirpath + win_separator + "*.*";
        WIN32_FIND_DATAA findData = {0};
        HANDLE hFind = FindFirstFileA(findpath.c_str(), &findData);
        if (hFind == INVALID_HANDLE_VALUE || hFind == NULL)
        {
            CUTL_ERROR("FindFirstFileA failed for " + findpath + ", errCode: " + std::to_string(GetLastError()));
            return file_list;
        }

        do
        {
            auto dwAttrs = findData.dwFileAttributes;
            auto filename = std::string(findData.cFileName);
            CUTL_DEBUG(filename + ", attributes: " + std::to_string(dwAttrs));
            if (is_special_dir(filename))
            {
                // “..”和“.”不做處理
                continue;
            }
            std::string filepath = dirpath + win_separator + filename;
            auto extension = get_file_extension(filename);
            auto ftype = get_file_type(dwAttrs, extension);
            if (ftype & type)
            {
                file_entity entity;
                entity.type = ftype;
                entity.filepath = filepath;
                file_list.emplace_back(entity);
            }
            if ((dwAttrs & FILE_ATTRIBUTE_DIRECTORY) && recursive)
            {
                // directory
                auto sub_files = list_sub_files(filepath, type, recursive);
                if (!sub_files.empty())
                {
                    file_list.insert(file_list.end(), sub_files.begin(), sub_files.end());
                }
            }
        } while (FindNextFileA(hFind, &findData));
        // 關閉控制代碼
        FindClose(hFind);

        return file_list;
    }
} // namespace cutl

#endif // defined(_WIN32) || defined(__WIN32__)

6. filesystem_unix.cpp

#if defined(_WIN32) || defined(__WIN32__)
// do nothing
#else

#include <unistd.h>
#include <sys/stat.h>
#include <dirent.h>
#include <stack>
#include <cstring>
#include <utime.h>
#include <stdlib.h>
#include <sys/time.h>
#include "filesystem.h"
#include "inner/logger.h"

namespace cutl
{
    filetype get_file_type(const std::string &filepath)
    {
        struct stat file_stat;
        int ret = lstat(filepath.c_str(), &file_stat);
        if (0 != ret)
        {
            CUTL_ERROR("stat error. filepath:" + filepath + ", error:" + strerror(errno));
            return filetype::unknown;
        }

        return get_file_type(file_stat.st_mode);
    }
    filevec list_sub_files(const std::string &dirpath, filetype type, bool recursive)
    {
        filevec file_list;

        DIR *dir = opendir(dirpath.c_str()); // 開啟這個目錄
        if (dir == NULL)
        {
            CUTL_ERROR("opendir error. dirpath:" + dirpath + ", error:" + strerror(errno));
            return file_list;
        }
        struct dirent *file_info = NULL;
        // 逐個讀取目錄中的檔案到file_info
        while ((file_info = readdir(dir)) != NULL)
        {
            // 系統有個系統檔案,名為“..”和“.”,對它不做處理
            std::string filename(file_info->d_name);
            if (is_special_dir(filename))
            {
                continue;
            }
            struct stat file_stat; // 檔案的資訊
            std::string filepath = dirpath + unix_separator + filename;
            int ret = lstat(filepath.c_str(), &file_stat);
            if (0 != ret)
            {
                CUTL_ERROR("stat error. filepath:" + filepath + ", error:" + strerror(errno));
                closedir(dir);
                return file_list;
            }
            auto ftype = get_file_type(file_stat.st_mode);
            if (ftype & type)
            {
                file_entity entity;
                entity.type = ftype;
                entity.filepath = filepath;
                file_list.emplace_back(entity);
            }

            if (S_ISDIR(file_stat.st_mode) && recursive)
            {
                auto sub_files = list_sub_files(filepath, type, recursive);
                if (!sub_files.empty())
                {
                    file_list.insert(file_list.end(), sub_files.begin(), sub_files.end());
                }
            }
        }
        closedir(dir);

        return file_list;
    }
} // namespace cutl

#endif // defined(_WIN32) || defined(__WIN32__)

7. 原始碼地址

更多詳細程式碼,請檢視本人寫的C++ 通用工具庫: common_util, 本專案已開源,程式碼簡潔,且有詳細的文件和Demo。


【SunLogging】
C++檔案系統操作5 - 跨平臺列出指定目錄下的所有檔案和資料夾
掃碼二維碼,關注微信公眾號,閱讀更多精彩內容

相關文章