在Windows上获取实际的文件名(正确的shell)

Windows文件系统不区分大小写。 如何,给定一个文件/文件夹名称(例如“somefile”),我得到该文件/文件夹的实际名称(例如,如果资源pipe理器显示它应该返回“SomeFile”)?

我知道的一些方式,所有这些看起来都很倒退:

  1. 给定完整path,searchpath上的每个文件夹(通过FindFirstFile)。 这给每个文件夹适当的结果。 在最后一步,search文件本身。
  2. 从句柄获取文件名(如在MSDN示例中 )。 这需要打开一个文件,创build文件映射,得到它的名称,parsing设备名称等。相当复杂。 它不适用于文件夹或零大小的文件。

我是否缺less一些明显的WinAPI调用? 最简单的,如GetActualPathName()或GetFullPathName()返回传入的名字(例如,如果传入的是程序文件,即使它应该是“Program Files”也会返回)。

我正在寻找一个本地解决scheme(不是.NET的)。

在此我根据cspirz的原始回答回答我自己的问题。

这是一个给定绝对,相对或网络路径的函数,将返回大小写的路径,就像在Windows上显示的一样。 如果路径的某个组件不存在,它将从该点返回传入的路径。

这是相当涉及,因为它试图处理网络路径和其他边缘情况。 它使用宽字符字符串并使用std :: wstring。 是的,从理论上讲,Unicode TCHAR可能与wchar_t不一样; 这是一个读者的练习:)

std::wstring GetActualPathName( const wchar_t* path ) { // This is quite involved, but the meat is SHGetFileInfo const wchar_t kSeparator = L'\\'; // copy input string because we'll be temporary modifying it in place size_t length = wcslen(path); wchar_t buffer[MAX_PATH]; memcpy( buffer, path, (length+1) * sizeof(path[0]) ); size_t i = 0; std::wstring result; // for network paths (\\server\share\RestOfPath), getting the display // name mangles it into unusable form (eg "\\server\share" turns // into "share on server (server)"). So detect this case and just skip // up to two path components if( length >= 2 && buffer[0] == kSeparator && buffer[1] == kSeparator ) { int skippedCount = 0; i = 2; // start after '\\' while( i < length && skippedCount < 2 ) { if( buffer[i] == kSeparator ) ++skippedCount; ++i; } result.append( buffer, i ); } // for drive names, just add it uppercased else if( length >= 2 && buffer[1] == L':' ) { result += towupper(buffer[0]); result += L':'; if( length >= 3 && buffer[2] == kSeparator ) { result += kSeparator; i = 3; // start after drive, colon and separator } else { i = 2; // start after drive and colon } } size_t lastComponentStart = i; bool addSeparator = false; while( i < length ) { // skip until path separator while( i < length && buffer[i] != kSeparator ) ++i; if( addSeparator ) result += kSeparator; // if we found path separator, get real filename of this // last path name component bool foundSeparator = (i < length); buffer[i] = 0; SHFILEINFOW info; // nuke the path separator so that we get real name of current path component info.szDisplayName[0] = 0; if( SHGetFileInfoW( buffer, 0, &info, sizeof(info), SHGFI_DISPLAYNAME ) ) { result += info.szDisplayName; } else { // most likely file does not exist. // So just append original path name component. result.append( buffer + lastComponentStart, i - lastComponentStart ); } // restore path separator that we might have nuked before if( foundSeparator ) buffer[i] = kSeparator; ++i; lastComponentStart = i; addSeparator = true; } return result; } 

再次感谢cspirz将我指向SHGetFileInfo。

还有另一个解决方案。 首先调用GetShortPathName(),然后GetLongPathName()。 猜猜会使用什么字符的情况呢? 😉

你有没有尝试过使用SHGetFileInfo?

好吧,这是VBScript,但即使如此,我建议使用Scripting.FileSystemObject对象

 Dim fso Set fso = CreateObject("Scripting.FileSystemObject") Dim f Set f = fso.GetFile("C:\testfile.dat") 'actually named "testFILE.dAt" wscript.echo f.Name 

我得到的答复是从这个片段是

 testFILE.dAt 

希望至少能让你朝正确的方向发展。

编辑:现在我重读这个问题,看来OP是知道这个解决方案,并寻求其他解决方案。

我发现FindFirstFile()将返回fd.cFileName正确的套管文件名(路径的最后部分)。 如果我们将c:\winDOWs\exPLORER.exe作为第一个参数传递给FindFirstFile() ,则fd.cFileName将是explorer.exe如下所示:

证明

如果我们用fd.cFileName替换路径的最后一部分,我们将得到最后的部分; 路径将变成c:\winDOWs\explorer.exe

假设路径总是绝对路径(文本长度没有变化),我们可以将这个“算法”应用到路径的每个部分(驱动器字母部分除外)。

说话很便宜,这里是代码:

 #include <windows.h> #include <stdio.h> /* c:\windows\windowsupdate.log --> c:\windows\WindowsUpdate.log */ static HRESULT MyProcessLastPart(LPTSTR szPath) { HRESULT hr = 0; HANDLE hFind = NULL; WIN32_FIND_DATA fd = {0}; TCHAR *p = NULL, *q = NULL; /* thePart = GetCorrectCasingFileName(thePath); */ hFind = FindFirstFile(szPath, &fd); if (hFind == INVALID_HANDLE_VALUE) { hr = HRESULT_FROM_WIN32(GetLastError()); hFind = NULL; goto eof; } /* thePath = thePath.ReplaceLast(thePart); */ for (p = szPath; *p; ++p); for (q = fd.cFileName; *q; ++q, --p); for (q = fd.cFileName; *p = *q; ++p, ++q); eof: if (hFind) { FindClose(hFind); } return hr; } /* Important! 'szPath' should be absolute path only. MUST NOT SPECIFY relative path or UNC or short file name. */ EXTERN_C HRESULT __stdcall CorrectPathCasing( LPTSTR szPath) { HRESULT hr = 0; TCHAR *p = NULL; if (GetFileAttributes(szPath) == -1) { hr = HRESULT_FROM_WIN32(GetLastError()); goto eof; } for (p = szPath; *p; ++p) { if (*p == '\\' || *p == '/') { TCHAR slashChar = *p; if (p[-1] == ':') /* p[-2] is drive letter */ { p[-2] = toupper(p[-2]); continue; } *p = '\0'; hr = MyProcessLastPart(szPath); *p = slashChar; if (FAILED(hr)) goto eof; } } hr = MyProcessLastPart(szPath); eof: return hr; } int main() { TCHAR szPath[] = TEXT("c:\\windows\\EXPLORER.exe"); HRESULT hr = CorrectPathCasing(szPath); if (SUCCEEDED(hr)) { MessageBox(NULL, szPath, TEXT("Test"), MB_ICONINFORMATION); } return 0; } 

证明2

优点:

  • 该代码适用于Windows 95以后的所有Windows版本。
  • 基本的错误处理。
  • 尽可能高的性能。 FindFirstFile()非常快,直接的缓冲区操作使其更快。
  • 只是C和纯WinAPI。 小可执行文件大小

缺点:

  • 只支持绝对路径,其他都是未定义的行为。
  • 不确定是否依赖无证行为。
  • 对于一些人来说,这个代码可能太过于DIY了。 可能让你发火。

代码风格背后的原因:

我使用goto进行错误处理,因为我已经习惯了(在C中goto对于错误处理非常方便)。 我使用for循环来执行像strcpystrchr功能,因为我想确定实际执行的是什么。

FindFirstFileNameW将有一些缺点:

  • 它不适用于UNC路径
  • 它剥离驱动器号,所以你需要把它添加回来
  • 如果有多个硬链接到您的文件,您需要确定正确的一个

据我所知,System.IO.FileInfo类Name属性将从Windows返回你的实际名称。

经过一个快速的测试, GetLongPathName()做你想要的。

这是诀窍:

 win32file.FindFilesW('somefile')[0][-2] 

返回“SomeFile”。

编辑:愚蠢的我,我在Python中寻找相同的。 所以忽略这个C / C ++ …