跳转至

C++ 文件与文件系统(std::filesystem)

C++17 把"操作文件"纳入标准库:路径拼接、目录遍历、属性查询、复制删除,一套 API 跨 Windows/Linux。本篇函数与 shell 命令一一对照,全部示例在 WSL g++ 11(-std=c++17)编译运行验证。命令行对照的 shell 用法见常用命令速查。

path:先认识路径类型

#include <filesystem>
#include <cstdio>
namespace fs = std::filesystem;

int main() {
    fs::path p = "/var/log/myapp";         // 路径对象,跨平台分隔符
    fs::path f = p / "app.log";            // operator/ 拼接:/var/log/myapp/app.log

    std::printf("%s\n", f.c_str());        // 完整路径
    std::printf("%s\n", f.parent_path().c_str());   // /var/log/myapp
    std::printf("%s\n", f.filename().c_str());      // app.log
    std::printf("%s\n", f.stem().c_str());          // app(不含扩展名)
    std::printf("%s\n", f.extension().c_str());     // .log
}

要点:

  • fs::path 惰性转换——它只是路径的表达,不要求文件存在;
  • operator/ 是拼接运算符,比手动补 / 安全(Windows/Linux 通吃);
  • Windows 下 path 内部使用 wchar_t,取字符串时 string() 会做转换;涉及非 ASCII 文件名时留意编码。

查询:exists 一族

fs::exists(p)          // 路径存在?(文件或目录都算)
fs::is_directory(p)    // 是目录?
fs::is_regular_file(p) // 是普通文件?
fs::file_size(f)       // 字节数(仅普通文件)
fs::last_write_time(f) // 最后修改时间(file_time_type)
fs::current_path()     // 当前工作目录(无参读 / 有参改,等价 getcwd/chdir)

操作:与 shell 命令对照

shell 命令 std::filesystem
mkdir -p a/b/c fs::create_directories("a/b/c")
touch f std::ofstream f("f");(用流创建)
cp src dst fs::copy_file(src, dst, fs::copy_options::overwrite_existing)
cp -r src dst fs::copy(src, dst, fs::copy_options::recursive)
mv a b fs::rename(a, b)
rm f fs::remove(f)
rm -rf dir fs::remove_all(dir)(返回删除的条数)
pwd fs::current_path()
#include <filesystem>
#include <fstream>
namespace fs = std::filesystem;

int main() {
    fs::create_directories("demo/data");              // 递归建目录
    { std::ofstream("demo/data/a.txt") << "hello"; }

    fs::copy_file("demo/data/a.txt", "demo/data/b.txt",
                  fs::copy_options::overwrite_existing);
    fs::rename("demo/data/b.txt", "demo/data/c.txt");

    std::printf("%lld bytes\n",                       // file_size 返回无符号,转 %lld
                static_cast<long long>(fs::file_size("demo/data/a.txt")));
    std::printf("%d\n", (int)fs::remove_all("demo")); // 递归删除
}

遍历:目录迭代器

#include <filesystem>
#include <cstdio>
namespace fs = std::filesystem;

int main() {
    // 单层遍历(等价 ls dir)
    for (const auto& entry : fs::directory_iterator("demo")) {
        std::printf("%s %s\n",
                    entry.is_directory() ? "dir " : "file",
                    entry.path().filename().c_str());
    }

    // 递归遍历(等价 find dir -type f)
    for (const auto& entry : fs::recursive_directory_iterator("demo")) {
        if (entry.is_regular_file() && entry.path().extension() == ".txt")
            std::printf("%s\n", entry.path().c_str());
    }
}

directory_entry 自带 is_directory()/file_size() 等成员(内部缓存,比每次对 path 重新查询更高效)。

错误处理:两套重载

文件系统函数几乎都有两个版本——抛异常的默认版与接收 std::error_code 的 noexcept 版(思路见错误处理):

// 版本一:失败抛 filesystem_error
try {
    fs::file_size("missing.txt");
} catch (const fs::filesystem_error& e) {
    std::printf("error: %s\n", e.what());   // 含两个 path 与系统错误信息
}

// 版本二:失败填 error_code,不抛
std::error_code ec;
auto size = fs::file_size("missing.txt", ec);
if (ec) std::printf("error: %s\n", ec.message().c_str());

常驻服务、库代码建议用 error_code 版(不希望异常穿越边界);工具脚本用异常版更省事。

实战:统计目录下最大的文件

#include <filesystem>
#include <cstdio>
#include <string>
namespace fs = std::filesystem;

int main(int argc, char** argv) {
    fs::path dir = argc > 1 ? argv[1] : ".";
    std::string biggest;
    long long biggest_size = -1;

    for (const auto& entry : fs::recursive_directory_iterator(dir)) {
        if (!entry.is_regular_file()) continue;
        auto size = static_cast<long long>(entry.file_size());
        if (size > biggest_size) {
            biggest_size = size;
            biggest = entry.path().string();
        }
    }
    if (biggest_size >= 0)
        std::printf("%s %lld bytes\n", biggest.c_str(), biggest_size);
}

(对应 shell:du -a dir | sort -n | tail -1,见排查工具箱。)

常见错误

错误 说明
相对路径依赖工作目录 服务由 systemd 启动时工作目录可能不同——配置里用绝对路径或以可执行文件位置为基准
remove 只删空目录/单文件 递归删除用 remove_all;误用 remove 删目录返回 false 并不报错
忘记 -std=c++17 GCC 9 之前还需显式链接 -lstdc++fs(g++ 11 不需要)
遍历中修改目录结构 迭代器可能失效;先收集路径列表再操作
Windows 与 Linux 分隔符 始终用 operator/ 拼接,不要手写字符串斜杠
权限与符号链接 copy 默认不跟随目录符号链接;exists(p) 默认跟随,fs::symlink_status(p) 查链接本身

评论