std::filesystem:: 与不存在文件等效的版本

NO_*_*AME 6 c++ c++17 std-filesystem

我的程序应该创建两个具有用户指定路径的文件。我需要知道路径是否通向同一位置,以便在开始更改文件系统之前以错误结束。

由于路径来自用户,因此它们应该是非规范且奇怪的。例如,它们可能是./dir1/subdir/file,并且符号链接dir2/subdir/../subdir/file在哪里并且还不存在。预期的结果仍然是,它们是等价的。dir2dir1subdirtrue

std::filesystem::equivalent仅适用于已存在的文件。有没有类似的功能,没有这个限制?

Mil*_*nek 1

这是一个非常难以解决的问题,没有一个标准库函数可以做到这一点。

\n

有几种情况您需要担心:

\n
    \n
  • 带有初始值的相对路径./
  • \n
  • 没有初始的裸相对路径./
  • \n
  • 路径“不存在”部分中的符号链接
  • \n
  • 不同文件系统区分大小写
  • \n
  • 几乎肯定有更多我没有想到的
  • \n
\n

std::filesystem::weakly_canonical会带你到达那里的一部分,但它本身不会完全到达那里。例如,它不会解决不存在裸相对路径的情况(即不会foo规范化为与 相同的东西./foo),并且它甚至不会尝试解决区分大小写的问题。

\n

这是一个canonicalize将考虑所有这些的函数。它仍然有一些缺点,主要是围绕非 ASCII 字符(即大小写规范化不适用于 \'\xc3\x89\'),但它应该在大多数情况下工作:

\n
namespace fs = std::filesystem;\n\nstd::pair<fs::path, fs::path> splitExistingNonExistingParts(const fs::path& path)\n{\n    fs::path existingPart = path;\n    while (!existingPart.empty() && !fs::exists(existingPart)) {\n        existingPart = existingPart.parent_path();\n    }\n    return {existingPart, fs::relative(path, existingPart)};\n}\n\nfs::path toUpper(const fs::path& path)\n{\n    const fs::path::string_type& native = path.native();\n    fs::path::string_type lower;\n    lower.reserve(native.length());\n    std::transform(\n        native.begin(),\n        native.end(),\n        std::back_inserter(lower),\n        [](auto c) { return std::toupper(c, std::locale()); }\n    );\n    return lower;\n}\n\nfs::path toLower(const fs::path& path)\n{\n    const fs::path::string_type& native = path.native();\n    fs::path::string_type lower;\n    lower.reserve(native.length());\n    std::transform(\n        native.begin(),\n        native.end(),\n        std::back_inserter(lower),\n        [](auto c) { return std::tolower(c, std::locale()); }\n    );\n    return lower;\n}\n\nbool isCaseSensitive(const fs::path& path)\n{\n    // NOTE: This function assumes the path exists.\n    //       fs::equivalent will throw if that isn\'t the case\n\n    fs::path upper = path.parent_path() / toUpper(*(--path.end()));\n    fs::path lower = path.parent_path() / toLower(*(--path.end()));\n\n    bool exists = fs::exists(upper);\n    if (exists != fs::exists(lower)) {\n        // If one exists and the other doesn\'t, then they\n        // must reference different files and therefore be\n        // case-sensitive\n        return true;\n    }\n\n    // If the two paths don\'t reference the same file, then\n    // the filesystem must be case-sensitive\n    return !fs::equivalent(upper, lower);\n}\n\nfs::path normalizeCase(const fs::path& path)\n{\n    // Normalize the case of a path to lower-case if it is on a\n    // non-case-sensitive filesystem\n\n    fs::path ret;\n    for (const fs::path& component : path) {\n        if (!isCaseSensitive(ret / component)) {\n            ret /= toLower(component);\n        } else {\n            ret /= component;\n        }\n    }\n    return ret;\n}\n\nfs::path canonicalize(fs::path path)\n{\n    if (path.empty()) {\n        return path;\n    }\n\n    // Initial pass to deal with .., ., and symlinks in the existing part\n    path = fs::weakly_canonical(path);\n\n    // Figure out if this is absolute or relative by assuming that there\n    // is a base path component that will always exist (i.e. / on POSIX or\n    // the drive letter on Windows)\n    auto [existing, nonExisting] = splitExistingNonExistingParts(path);\n    if (!existing.empty()) {\n        existing = fs::canonical(fs::absolute(existing));\n    } else {\n        existing = fs::current_path();\n    }\n\n    // Normalize the case of the existing part of the path\n    existing = normalizeCase(existing);\n\n    // Need to deal with case-sensitivity of the part of the path\n    // that doesn\'t exist.  Assume that part will have the same\n    // case-sensitivity as the last component of the existing path\n    if (!isCaseSensitive(existing)) {\n        path = existing / toLower(nonExisting);\n    } else {\n        path = existing / nonExisting;\n    }\n\n    // Call weakly_canonical again to deal with any existing symlinks that were\n    // hidden by .. components after non-existing path components\n    fs::path temp;\n    while ((temp = fs::weakly_canonical(path)) != path) {\n        path = temp;\n    }\n    return path;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

现场演示

\n