确定两个路径是不是引用 Windows 中同一文件的最佳方法?

Posted

技术标签:

【中文标题】确定两个路径是不是引用 Windows 中同一文件的最佳方法?【英文标题】:Best way to determine if two path reference to same file in Windows?确定两个路径是否引用 Windows 中同一文件的最佳方法? 【发布时间】:2010-10-08 10:36:23 【问题描述】:

如何比较两个字符串以确定它们是否使用 C/C++ 在 Win32 中引用相同的路径?

虽然这会处理很多情况,但它会遗漏一些事情:

_tcsicmp(szPath1, szPath2) == 0

例如:

正斜杠/反斜杠

相对/绝对路径。

[编辑] 标题已更改以匹配现有 C# 问题。

【问题讨论】:

这能回答你的问题吗? What is the best way of determining that two file paths are referring to the same file object? 【参考方案1】:

使用CreateFile 打开两个文件,调用GetFileInformationByHandle 并比较dwVolumeSerialNumbernFileIndexLownFileIndexHigh。如果三个都相等,则它们都指向同一个文件:

GetFileInformationByHandle function

BY_HANDLE_FILE_INFORMATION Structure

【讨论】:

注意文件应该保持打开,否则可能相同的数字将对应不同的文件。 BY_HANDLE_FILE_INFORMATION 结构的 2016 年文档说“此结构中的 64 位标识符不能保证在 ReFS 上是唯一的。”和“要检索 128 位文件标识符,请使用带有 FileIdInfo 的 GetFileInformationByHandleEx 函数来检索 FILE_ID_INFO 结构。”此方法适用于 Windows 8 及更高版本。它需要编译选项 -D_WIN32_WINNT=_WIN32_WINNT_WIN8。 索引号(inode)即使在本地文件系统上的句柄关闭后也将保持不变。网络驱动器只有一个问题。 Samba 有一个选项提供跨会话工作的 inode(服务器重新启动)。【参考方案2】:

文件系统库

从 C++17 开始,您可以使用 standard filesystem library。使用#include <filesystem> 包含它。您甚至可以在旧版本的 C++ 中访问它,请参见脚注。

你要找的函数是equivalent,在命名空间std::filesystem下:

bool std::filesystem::equivalent(const std::filesystem::path& p1, const filesystem::path& p2 );

总结documentation:此函数将两个路径作为参数,如果它们引用相同的文件或目录,则返回true,否则返回false。还有一个noexcept 重载,它采用第三个参数:一个std::error_code,用于保存任何可能的错误。

示例

#include <filesystem>
#include <iostream>
//...

int main() 
    std::filesystem::path p1 = ".";
    std::filesystem::path p2 = fs::current_path();
    std::cout << std::filesystem::equivalent(p1, p2);
    //...

输出:

1

使用 C++17 之前的文件系统

要在 C++17 之前的版本中使用此库,您必须在编译器中启用实验性语言功能并以这种方式包含该库:#include &lt;experimental/filesystem&gt;。然后,您可以在命名空间std::experimental::filesystem 下使用它的功能。请注意,实验文件系统库可能与 C++17 不同。请参阅文档here。 例如:

#include <experimental/filesystem>
//...
std::experimental::filesystem::equivalent(p1, p2);

【讨论】:

【参考方案3】:

看到这个问题:Best way to determine if two path reference to same file in C#

问题是关于 C#,但答案只是 Win32 API 调用GetFileInformationByHandle

【讨论】:

GetFileInformationByHandle 的文档说:“nFileIndexLow:与文件关联的唯一标识符的低位部分。此值仅在文件被至少一个进程打开时才有用。如果没有进程打开它,下次打开文件时索引可能会改变。”【参考方案4】:

使用 kernel32.dll 中的 GetFullPathName,这将为您提供文件的绝对路径。然后将其与您使用简单字符串比较的其他路径进行比较

编辑:代码

TCHAR buffer1[1000];
TCHAR buffer2[1000];
TCHAR buffer3[1000];
TCHAR buffer4[1000];

GetFullPathName(TEXT("C:\\Temp\\..\\autoexec.bat"),1000,buffer1,NULL);
GetFullPathName(TEXT("C:\\autoexec.bat"),1000,buffer2,NULL);
GetFullPathName(TEXT("\\autoexec.bat"),1000,buffer3,NULL);
GetFullPathName(TEXT("C:/autoexec.bat"),1000,buffer4,NULL);
_tprintf(TEXT("Path1: %s\n"), buffer1);
_tprintf(TEXT("Path2: %s\n"), buffer2);
_tprintf(TEXT("Path3: %s\n"), buffer3);
_tprintf(TEXT("Path4: %s\n"), buffer4);

上面的代码将为所有三个路径表示打印相同的路径.. 之后您可能希望进行不区分大小写的搜索

【讨论】:

这不起作用,因为同一个文件可以有多个路径 是的,但是 GetFullPathName 会给你同一个文件的绝对路径..所以它应该是一样的.. 硬链接允许单个文件有两个不同的名称。是的,那些只知道对自己和他人构成危险的人,Windows 确实支持硬链接。 @IntegerPoet 就是这么说的。硬链接是魔鬼的游乐场。您需要的不仅仅是一条路径来确定相同性。 这似乎也没有考虑带有斜杠的目录(即 c:\blah 与 c:\blah)。【参考方案5】:

简单的字符串比较不足以比较路径是否相等。在 Windows 中,c:\foo\bar.txt 和 c:\temp\bar.txt 很可能通过文件系统中的符号链接和硬链接指向完全相同的文件。

正确比较路径本质上会强制您打开两个文件并比较低级句柄信息。任何其他方法都会产生不稳定的结果。

查看 Lucian 就该主题发表的这篇出色的帖子。该代码在 VB 中,但由于他 PInvoke 了大多数方法,因此可以很好地转换为 C/C++。

http://blogs.msdn.com/vbteam/archive/2008/09/22/to-compare-two-filenames-lucian-wischik.aspx

【讨论】:

【参考方案6】:

根据关于GetFileInformationByHandle() 的回答,这里是代码。

注意:这仅在文件已存在时才有效...

//Determine if 2 paths point ot the same file...
//Note: This only works if the file exists
static bool IsSameFile(LPCWSTR szPath1, LPCWSTR szPath2)

    //Validate the input
    _ASSERT(szPath1 != NULL);
    _ASSERT(szPath2 != NULL);

    //Get file handles
    HANDLE handle1 = ::CreateFileW(szPath1, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 
    HANDLE handle2 = ::CreateFileW(szPath2, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 

    bool bResult = false;

    //if we could open both paths...
    if (handle1 != INVALID_HANDLE_VALUE && handle2 != INVALID_HANDLE_VALUE)
    
        BY_HANDLE_FILE_INFORMATION fileInfo1;
        BY_HANDLE_FILE_INFORMATION fileInfo2;
        if (::GetFileInformationByHandle(handle1, &fileInfo1) && ::GetFileInformationByHandle(handle2, &fileInfo2))
        
            //the paths are the same if they refer to the same file (fileindex) on the same volume (volume serial number)
            bResult = fileInfo1.dwVolumeSerialNumber == fileInfo2.dwVolumeSerialNumber &&
                      fileInfo1.nFileIndexHigh == fileInfo2.nFileIndexHigh &&
                      fileInfo1.nFileIndexLow == fileInfo2.nFileIndexLow;
        
    

    //free the handles
    if (handle1 != INVALID_HANDLE_VALUE )
    
        ::CloseHandle(handle1);
    

    if (handle2 != INVALID_HANDLE_VALUE )
    
        ::CloseHandle(handle2);
    

    //return the result
    return bResult;

【讨论】:

GetFileInformationByHandle 的文档说:“nFileIndexLow:与文件关联的唯一标识符的低位部分。此值仅在文件被至少一个进程打开时才有用。如果没有进程将其打开,索引可能会在下次打开文件时更改。” @IntegerPoet:你的意思是?除非文件已打开,否则您甚至不能调用 GetFileInformationByHandle,并且此代码不会在调用之间关闭任何句柄。 好问题。我很久以前写了那条评论,以至于我不再记得我的想法了。 我的意思是 IsSameFile (bResult) 的返回值在 IsSameFile 返回之前就已经过时了。 BY_HANDLE_FILE_INFORMATION 结构的 2016 年文档说“此结构中的 64 位标识符不能保证在 ReFS 上是唯一的。”和“要检索 128 位文件标识符,请使用带有 FileIdInfo 的 GetFileInformationByHandleEx 函数来检索 FILE_ID_INFO 结构。”此方法适用于 Windows 8 及更高版本。它需要编译选项 -D_WIN32_WINNT=_WIN32_WINNT_WIN8。【参考方案7】:

如果您可以访问 Boost 库,请尝试

bool boost::filesystem::path::equivalent( const path& p1, const path& p2 )

http://www.boost.org/doc/libs/1_53_0/libs/filesystem/doc/reference.html#equivalent

从文档中总结:如果给定的 path 对象解析为相同的文件系统实体,则返回 true,否则返回 false

【讨论】:

上面链接后面的文档说明了他们在 boost 中所做的事情。他们比较了stat() 的结果,这很容易自己做。与大多数其他解决方案相比,它独立于平台。 是的。您可以查看源代码并将其用作您自己实现这一小块的指南。【参考方案8】:

您需要做的是获取规范路径。

对于您要求文件系统转换为规范路径或为您提供唯一标识文件的标识符(例如 iNode)的每个路径。

然后比较规范路径或唯一标识符。

注意: 不要尝试自己找出锥形路径,文件系统可以使用符号链接等做一些不容易处理的事情,除非您非常熟悉文件系统。

【讨论】:

【参考方案9】:

如果您引用 UNC 或规范路径(即本地路径以外的任何路径),比较实际路径字符串将不会产生准确的结果。

shlwapi.h 有一些 Path Functions 可能对您确定您的路径是否相同有用。

它包含像PathIsRoot 这样的函数,可以在更大范围的函数中使用。

【讨论】:

【参考方案10】:

如果文件存在并且您可以处理打开文件可能导致的竞争条件和性能损失,那么适用于任何平台的不完美解决方案是打开一个文件自行写入,关闭它,然后再打开它打开另一个文件进行写入后再次写入。由于只允许独占写入权限,因此如果您能够打开第一个文件以进行第一次写入但第二次无法打开,那么当您尝试打开这两个文件时,您可能会阻止您自己的请求。

(当然,也有可能是系统的其他部分打开了您的文件之一)

【讨论】:

【参考方案11】:

打开这两个文件并使用 GetFinalPathNameByHandle() 反对HANDLEs。然后比较路径。

【讨论】:

以上是关于确定两个路径是不是引用 Windows 中同一文件的最佳方法?的主要内容,如果未能解决你的问题,请参考以下文章

并查集入门

检查字符串是不是是有效的 Windows 目录(文件夹)路径

全网首发:VS编译出错:两个输出文件名解析为同一个输出路径

全网首发:VS编译出错:两个输出文件名解析为同一个输出路径

在 pyInstaller 生成的 Python EXE 中确定应用程序路径

您如何确定两个 HashSet 是不是相等(按值,而不是按引用)?