Windows和Linux之间的相对路径分辨率差异?
Posted
技术标签:
【中文标题】Windows和Linux之间的相对路径分辨率差异?【英文标题】:Relative path resolution differences between Windows & Linux? 【发布时间】:2020-06-11 15:20:37 【问题描述】:给定 CWD 中的文件text.txt
,我从fopen()
得到两个不同的答案,即“此文件是否存在?”这个问题。对于a/b/../../test.txt
(减少为test.txt
):
但是,如果我随后 mkdir -p a/b
Linux 改变其调子并说 a/b/../../test.txt
现在存在。
就好像 Linux 在处理相对路径时检查每个目录是否存在(如果其中任何一个不存在,则提前输出),而不是先折叠任何 ../
,然后像 Windows 一样进行文件存在检查似乎可以。
两个问题:
在这种情况下,如何让 Linux 像 Windows 一样运行?像某种环境变量来修改 glibc/syscall 行为。 应该fopen()
如何处理这样的相对路径?我可以为fopen()
找到的文档/规范只是表明它接受了相对路径(也许有几个例子),而不是在中间路径../
的情况下应该如何解决这些路径。
测试程序:
#include <filesystem>
#include <fstream>
#include <iostream>
#include <cstdio>
void file_exists( const char* filename )
FILE* file = fopen( filename, "r" );
std::cout << ( file ? "Y" : "N" ) << ": " << filename << std::endl;
if( file )
fclose( file );
int main( int, char** )
// initial tests
std::filesystem::remove( "test.txt" );
std::filesystem::remove( "doesnt-exist.txt" );
file_exists( "test.txt" );
file_exists( "doesnt-exist.txt" );
std::cout << std::endl;
// create file
std::cout << "creating text.txt..." << std::endl;
std::ofstream output( "test.txt", std::ios::trunc );
output << "test" << std::endl;
output.close();
// more tests
file_exists( "test.txt" );
file_exists( "doesnt-exist.txt" );
file_exists( "a/b/../../test.txt" );
file_exists( "a/b/../../doesnt-exist.txt" );
std::cout << std::endl;
std::cout << "creating directory 'a/b'..." << std::endl;
std::filesystem::create_directories( "a/b" );
file_exists( "a/b/../../test.txt" );
file_exists( "a/b/../../doesnt-exist.txt" );
std::cout << std::endl;
// cleanup
std::filesystem::remove( "test.txt" );
std::filesystem::remove_all( "a/b" );
return 0;
结果:
Windows 10、VS2017:
N: test.txt
N: doesnt-exist.txt
creating text.txt...
Y: test.txt
N: doesnt-exist.txt
Y: a/b/../../test.txt # file exists, expected behavior
N: a/b/../../doesnt-exist.txt
creating directory 'a/b'...
Y: a/b/../../test.txt
N: a/b/../../doesnt-exist.txt
Ubuntu 20.04,g++ (Ubuntu 9.3.0-10ubuntu2) 9.3.0
N: test.txt
N: doesnt-exist.txt
creating text.txt...
Y: test.txt
N: doesnt-exist.txt
N: a/b/../../test.txt # file *doesn't* exist, unexpected behavior
N: a/b/../../doesnt-exist.txt
creating directory 'a/b'...
Y: a/b/../../test.txt
N: a/b/../../doesnt-exist.txt
【问题讨论】:
在这种情况下,Windows 行为可能对您更方便,但我认为 Ubuntu 行为看起来更“正确”。如果以这种方式指定路径,也许它应该与指定 c/d/../../text.txt 不同。如果“a/b”或“c/d”(以指定者为准)不存在,则所写的路径是没有意义的。 Windows底层NT系统不支持“.”和路径中的“..”组件,而不是在某些文件系统中保留名称(例如 NTFS 保留它们;FAT32 不允许并允许创建名为“.”和“..”的文件)。因此,当将路径规范化为 NT 路径时,Windows API 必须将它们解析为纯字符串操作(例如,在调用NtCreateFile
之前规范化 CreateFileW
中的路径)。
@Basya:您当然希望操作系统会检查a
中的b
的权限。
@dxiv:The cppreference page for std::filesystem::path
表示路径规范化是在 Windows 样式中完成的,没有文件系统检查。
一个例外是 NT 中的文件系统相关符号链接,它在内核中相对于符号链接的路径进行重新解析,其语义类似于 Unix——当且仅当组件存在时(即非- 存在“a”和“b”)。这是正确的方法,因为天真地解析“..”以删除符号链接(例如“symlink/../spam”)可能会导致完全不同的目标路径。因此,“..”组件的处理在 Windows 中是自相矛盾的,与 Unix 相比,这使得手动解析 Windows 中的相关符号链接(例如通过 readlink 实现)极其困难。
【参考方案1】:
文件名和路径名是特定于实现的。当您意识到 C:X
在 Linux 和 Windows 中是一个完全有效的文件名时,这显然是有道理的,但含义却截然不同。
在 Linux(如 UNIX)上,..
是一个真正的目录条目。也就是说a/b/../../
涉及4个真实的目录项:a和b明显,但也有b中的..
和a中的..
。 fopen
不需要做任何特别的事情:操作系统只检查实际的目录条目。
在 Windows 上,必须首先将路径 a/b/../../
转换为标准格式。也就是说应该以\??\
开头。您以前可能没有见过这些路径,但它们是 Windows 内部使用的。所有这些路径都是绝对路径,并且这些 always 包含反斜杠。因此,从a/b/../../
的翻译涉及为当前工作驱动器和目录添加前缀,将\
替换为/
并删除..
如您所见,这些是解决..
的完全不同的方法。您的 fopen
调用仅遵循本地约定,通常只需将字符串传递给操作系统。 (但在 Windows 上,fopen
也可能提前进行 /\
转换,以确保。)
如果另一个操作系统选择^
来表示“父目录”,那么您会期望fopen("a/b/^/^")
也遵循该约定,所以它不像..
是一个特殊的字符串。在这样的平台上,..
甚至可能是子目录的有效名称。
【讨论】:
请注意,如果您在 Windows 中使用逐字“\\?\”路径,您实际上可以在 FAT32 文件系统中创建一个名为“..”的文件。它与非根目录列表中的普通“..”条目的区别在于自动生成的 8.3 短名称。【参考方案2】:std::filesystem::path::lexically_normal 可能有助于纯词法规范化路径(不遵循符号链接,检查路径是否存在,...)。
所以fopen(std::filesystem::path"a/b/../../test.txt".lexically_normal().c_str(), "r")
的行为相同。 (你确实必须包装你的电话才能有这种行为)。
【讨论】:
以上是关于Windows和Linux之间的相对路径分辨率差异?的主要内容,如果未能解决你的问题,请参考以下文章
使用 Intel 编译器的 Windows 和 Linux 之间的性能差异:查看程序集