NO_*_*AME 6 c++ c++17 std-filesystem
我的程序应该创建两个具有用户指定路径的文件。我需要知道路径是否通向同一位置,以便在开始更改文件系统之前以错误结束。
由于路径来自用户,因此它们应该是非规范且奇怪的。例如,它们可能是./dir1/subdir/file,并且符号链接dir2/subdir/../subdir/file在哪里并且还不存在。预期的结果仍然是,它们是等价的。dir2dir1subdirtrue
std::filesystem::equivalent仅适用于已存在的文件。有没有类似的功能,没有这个限制?
这是一个非常难以解决的问题,没有一个标准库函数可以做到这一点。
\n有几种情况您需要担心:
\n././std::filesystem::weakly_canonical会带你到达那里的一部分,但它本身不会完全到达那里。例如,它不会解决不存在裸相对路径的情况(即不会foo规范化为与 相同的东西./foo),并且它甚至不会尝试解决区分大小写的问题。
这是一个canonicalize将考虑所有这些的函数。它仍然有一些缺点,主要是围绕非 ASCII 字符(即大小写规范化不适用于 \'\xc3\x89\'),但它应该在大多数情况下工作:
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}\nRun Code Online (Sandbox Code Playgroud)\n\n