繁体   English   中英

如何在 Windows 上获取区分大小写的路径?

[英]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 的评论提供了一个指向递归版本的链接,该版本可以(虽然不总是)解析完整路径。

获取文件实际路径的方法(这不适用于文件夹)是按照以下步骤操作:

  1. 调用CreateFileMapping为文件创建映射。
  2. 调用GetMappedFileName获取文件名。
  3. 使用QueryDosDevice将其转换为 MS-DOS 样式的路径名。

如果你想编写一个更健壮的程序,它也适用于目录(但有更多的痛苦和一些未记录的功能),请按照以下步骤操作:

  1. 使用CreateFileNtOpenFile获取文件/文件夹的NtOpenFile
  2. 调用NtQueryObject以获取完整路径名。
  3. 使用FileNameInformation调用NtQueryInformationFile以获取卷相对路径。
  4. 使用上面的两个路径,获取表示卷本身的路径组件。 例如,如果您获得第一个路径的\\Device\\HarddiskVolume1\\Hello.txt和第二个路径的\\Hello.txt ,您现在知道卷的路径是\\Device\\HarddiskVolume1
  5. 使用记录不完整的 Mount Manager I/O Control Codes 或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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM