[英]How can I obtain the case-sensitive path on Windows?
我需要知道哪个是给定路径的真实路径。
例如:
真正的路径是:d:\\src\\File.txt
用户给我:D:\\src\\file.txt
结果我需要:d:\\src\\File.txt
您可以使用此功能:
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
static extern uint GetLongPathName(string ShortPath, StringBuilder sb, int buffer);
[DllImport("kernel32.dll")]
static extern uint GetShortPathName(string longpath, StringBuilder sb, int buffer);
protected static string GetWindowsPhysicalPath(string path)
{
StringBuilder builder = new StringBuilder(255);
// names with long extension can cause the short name to be actually larger than
// the long name.
GetShortPathName(path, builder, builder.Capacity);
path = builder.ToString();
uint result = GetLongPathName(path, builder, builder.Capacity);
if (result > 0 && result < builder.Capacity)
{
//Success retrieved long file name
builder[0] = char.ToLower(builder[0]);
return builder.ToString(0, (int)result);
}
if (result > 0)
{
//Need more capacity in the buffer
//specified in the result variable
builder = new StringBuilder((int)result);
result = GetLongPathName(path, builder, builder.Capacity);
builder[0] = char.ToLower(builder[0]);
return builder.ToString(0, (int)result);
}
return null;
}
作为一个老前辈,我总是为此使用 FindFirstFile。 .Net 翻译是:
Directory.GetFiles(Path.GetDirectoryName(userSuppliedName), Path.GetFileName(userSuppliedName)).FirstOrDefault();
这只会为您提供路径的文件名部分的正确大小写,而不是整个路径。
JeffreyLWhitledge 的评论提供了一个指向递归版本的链接,该版本可以(虽然不总是)解析完整路径。
获取文件实际路径的方法(这不适用于文件夹)是按照以下步骤操作:
CreateFileMapping
为文件创建映射。GetMappedFileName
获取文件名。QueryDosDevice
将其转换为 MS-DOS 样式的路径名。如果你想编写一个更健壮的程序,它也适用于目录(但有更多的痛苦和一些未记录的功能),请按照以下步骤操作:
CreateFile
或NtOpenFile
获取文件/文件夹的NtOpenFile
。NtQueryObject
以获取完整路径名。FileNameInformation
调用NtQueryInformationFile
以获取卷相对路径。\\Device\\HarddiskVolume1\\Hello.txt
和第二个路径的\\Hello.txt
,您现在知道卷的路径是\\Device\\HarddiskVolume1
。QueryDosDevice
来转换用驱动器号替换完整 NT 样式路径的卷部分。现在您有了文件的真实路径。
替代方案
这是一个解决方案,适用于我使用区分大小写的路径在 Windows 和服务器之间移动文件。 它沿着目录树向下走,并使用GetFileSystemEntries()
更正每个条目。 如果路径的一部分无效(UNC 或文件夹名称),则它仅在该点之前更正路径,然后将原始路径用于无法找到的内容。 无论如何,希望这将在处理相同问题时节省其他人的时间。
private string GetCaseSensitivePath(string path)
{
var root = Path.GetPathRoot(path);
try
{
foreach (var name in path.Substring(root.Length).Split(Path.DirectorySeparatorChar))
root = Directory.GetFileSystemEntries(root, name).First();
}
catch (Exception e)
{
// Log("Path not found: " + path);
root += path.Substring(root.Length);
}
return root;
}
由于 Borja 的回答不适用于禁用 8.3 名称的卷,因此这里采用 Tergiver 建议的递归实现(适用于文件和文件夹,以及 UNC 共享的文件和文件夹,但不适用于它们的机器名称或共享名称)。
不存在的文件或文件夹没有问题,存在的内容会得到验证和更正,但您可能会遇到文件夹重定向问题,例如在尝试获取“C:\\WinDoWs\\sYsteM32\\driVERs\\eTC\\Hosts”的正确路径时您将在 64 位 Windows 上获得“C:\\Windows\\System32\\drivers\\eTC\\hosts”,因为没有包含“C:\\Windows\\sysWOW64\\drivers”的“etc”文件夹。
测试场景:
Directory.CreateDirectory(@"C:\Temp\SomeFolder");
File.WriteAllLines(@"C:\Temp\SomeFolder\MyTextFile.txt", new String[] { "Line1", "Line2" });
用法:
FileInfo myInfo = new FileInfo(@"C:\TEMP\SOMEfolder\MyTeXtFiLe.TxT");
String myResult = myInfo.GetFullNameWithCorrectCase(); //Returns "C:\Temp\SomeFolder\MyTextFile.txt"
代码:
public static class FileSystemInfoExt {
public static String GetFullNameWithCorrectCase(this FileSystemInfo fileOrFolder) {
//Check whether null to simulate instance method behavior
if (Object.ReferenceEquals(fileOrFolder, null)) throw new NullReferenceException();
//Initialize common variables
String myResult = GetCorrectCaseOfParentFolder(fileOrFolder.FullName);
return myResult;
}
private static String GetCorrectCaseOfParentFolder(String fileOrFolder) {
String myParentFolder = Path.GetDirectoryName(fileOrFolder);
String myChildName = Path.GetFileName(fileOrFolder);
if (Object.ReferenceEquals(myParentFolder, null)) return fileOrFolder.TrimEnd(new char[]{Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar });
if (Directory.Exists(myParentFolder)) {
//myParentFolder = GetLongPathName.Invoke(myFullName);
String myFileOrFolder = Directory.GetFileSystemEntries(myParentFolder, myChildName).FirstOrDefault();
if (!Object.ReferenceEquals(myFileOrFolder, null)) {
myChildName = Path.GetFileName(myFileOrFolder);
}
}
return GetCorrectCaseOfParentFolder(myParentFolder) + Path.DirectorySeparatorChar + myChildName;
}
}
这是一个替代解决方案,适用于文件和目录。 使用 GetFinalPathNameByHandle,根据文档,它仅支持 Vista/Server2008 或更高版本的桌面应用程序。
请注意,如果您给它一个符号链接,它将解析一个符号链接,这是找到“最终”路径的一部分。
// http://www.pinvoke.net/default.aspx/shell32/GetFinalPathNameByHandle.html
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern uint GetFinalPathNameByHandle(SafeFileHandle hFile, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder lpszFilePath, uint cchFilePath, uint dwFlags);
private const uint FILE_NAME_NORMALIZED = 0x0;
static string GetFinalPathNameByHandle(SafeFileHandle fileHandle)
{
StringBuilder outPath = new StringBuilder(1024);
var size = GetFinalPathNameByHandle(fileHandle, outPath, (uint)outPath.Capacity, FILE_NAME_NORMALIZED);
if (size == 0 || size > outPath.Capacity)
throw new Win32Exception(Marshal.GetLastWin32Error());
// may be prefixed with \\?\, which we don't want
if (outPath[0] == '\\' && outPath[1] == '\\' && outPath[2] == '?' && outPath[3] == '\\')
return outPath.ToString(4, outPath.Length - 4);
return outPath.ToString();
}
// http://www.pinvoke.net/default.aspx/kernel32.createfile
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern SafeFileHandle CreateFile(
[MarshalAs(UnmanagedType.LPTStr)] string filename,
[MarshalAs(UnmanagedType.U4)] FileAccess access,
[MarshalAs(UnmanagedType.U4)] FileShare share,
IntPtr securityAttributes, // optional SECURITY_ATTRIBUTES struct or IntPtr.Zero
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
[MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes,
IntPtr templateFile);
private const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
public static string GetFinalPathName(string dirtyPath)
{
// use 0 for access so we can avoid error on our metadata-only query (see dwDesiredAccess docs on CreateFile)
// use FILE_FLAG_BACKUP_SEMANTICS for attributes so we can operate on directories (see Directories in remarks section for CreateFile docs)
using (var directoryHandle = CreateFile(
dirtyPath, 0, FileShare.ReadWrite | FileShare.Delete, IntPtr.Zero, FileMode.Open,
(FileAttributes)FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero))
{
if (directoryHandle.IsInvalid)
throw new Win32Exception(Marshal.GetLastWin32Error());
return GetFinalPathNameByHandle(directoryHandle);
}
}
我试图避免 dll 导入,所以对我来说最好的方法是使用 System.Linq 和 System.IO.Directory 类。
对于您的示例真实路径是:d:\\src\\File.txt 用户给我:D:\\src\\file.txt
代码:
使用 System.Linq;
public static class PathUtils
{
public static string RealPath(string inputPath)
{
return Directory.GetFiles(Path.GetDirectoryName(inputPath))
.FirstOrDefault(p => String.Equals(Path.GetFileName(p),
Path.GetFileName(inputPath), StringComparison.OrdinalIgnoreCase));
}
}
var p = PathUtils.RealPath(@"D:\\src\\file.txt");
方法应该返回路径“d:\\src\\File.txt”或“D:\\src\\File.txt”。
这是我如何做到的。 本来我是依赖GetFinalPathNameByHandle
,这个很好用,可惜有些自定义文件系统不支持(当然NTFS是支持的)。 我也用ObjectNameInformation
尝试过NtQueryObject
但同样,它们不一定报告原始文件名。
所以这是另一种“手动”方式:
public static string GetRealPath(string fullPath)
{
if (fullPath == null)
return null; // invalid
var pos = fullPath.LastIndexOf(Path.DirectorySeparatorChar);
if (pos < 0 || pos == (fullPath.Length - 1))
return fullPath.ToUpperInvariant(); // drive letter
var dirPath = fullPath.Substring(0, pos);
var realPath = GetRealPath(dirPath); // go recursive, we want the final full path
if (realPath == null)
return null; // doesn't exist
var dir = new DirectoryInfo(realPath);
if (!dir.Exists)
return null; // doesn't exist
var fileName = fullPath.Substring(pos + 1);
if (fileName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) // avoid wildcard calls
return null;
return dir.EnumerateFileSystemInfos(fileName).FirstOrDefault()?.FullName; // may return null
}
在 Windows 上,路径不区分大小写。 所以两条路径都是同样真实的。
如果您想获得某种具有规范大写的路径(即 Windows 认为它应该如何大写),您可以使用路径作为掩码调用 FindFirstFile(),然后获取找到的文件的全名。 如果路径无效,那么您自然不会得到规范名称。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.