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



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

因为路径来自用户,所以它们应该是非规范的和奇怪的。例如,它们可以是./dir1/subdir/filedir2/subdir/../subdir/file,其中dir2是指向dir1的符号链接,而subdir还不存在。预期结果仍然是true,它们是等价的。

std::filesystem::equivalent只适用于已经存在的文件。有没有类似的函数没有这个限制?

我将使用std::filesystem::absolute,然后std::filesystem::weakly_canonical的结果。

namespace fs = std::filesystem;
auto fullpath1 fs::weakly_canonical(fs::absolute(path1));
auto fullpath2 fs::weakly_canonical(fs::absolute(path2));
if(fullpath1 == fullpath2) {
//
}

演示注意:对于std::filesystem::absolute,鼓励实现不将不存在的path视为错误,但实现仍然可能会这样做。它可以在g++,clang++MSVC的最新版本中使用。

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

有几种情况你需要担心:

  • 具有初始./的相对路径
  • 没有初始./的裸相对路径
  • "不存在"中的符号链接路径的一部分
  • 不同文件系统的大小写敏感性
  • 几乎可以肯定的是,我没有想到

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

这里是一个canonicalize函数,它将考虑所有这些。它仍然有一些缺点,主要是非ascii字符(即大小写归一化不适用于'É'),但它应该在大多数情况下工作:

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

现场演示

我根据Ted Lyngmo的回答和Miles Budnek的评论汇编了这个答案。

你需要做的是标准化你的路径,以删除所有.,..,符号链接和类似的东西得到的方式。

std::filesystem::weakly_canonical可以完成大部分工作,但是,您可能需要多次调用它,以防它在某个不存在的目录上出错,从而掩盖了现有目录。(在您的示例中,dir2/subdir/../../dir2会这样做。)调用该函数,直到结果停止变化。

在规范化路径之前,您还需要确保路径是绝对的。std::filesystem::weakly_canonical通常会将路径转换为绝对路径,但前提是原始路径的第一部分存在。否则可能无法正常工作。

std::filesystem::path normalizePath(const std::filesystem::path &originalPath)
{
using namespace std::filesystem;
path currentPath;
if (originalPath.is_absolute())
currentPath = originalPath;
else
currentPath = std::filesystem::current_path() / originalPath;
while(true)
{
path newPath = weakly_canonical(currentPath);
if (newPath != currentPath)
currentPath = newPath;
else
break;
}
return currentPath;
}

完成后,您可以使用操作符==比较路径。

演示

相关内容

最新更新