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

时间:2022-10-16 07:20:46

I need to know which is the real path of a given path.

我需要知道哪条是给定路径的真正路径。

For example:

例如:

The real path is: d:\src\File.txt
And the user give me: D:\src\file.txt
I need as a result: d:\src\File.txt

真正的路径是:d:\ src \ File.txt并且用户给我:D:\ src \ file.txt我需要结果:d:\ src \ File.txt

7 个解决方案

#1


17  

You can use this function:

你可以使用这个功能:

[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;
}

#2


7  

As an old-timer, I always used FindFirstFile for this purpose. The .Net translation is:

作为老朋友,我总是为此目的使用FindFirstFile。 .Net翻译是:

Directory.GetFiles(Path.GetDirectoryName(userSuppliedName), Path.GetFileName(userSuppliedName)).FirstOrDefault();

This only gets you the correct casing for the filename portion of the path, not then entire path.

这只能获得路径文件名部分的正确大小,而不是整个路径。

JeffreyLWhitledge's comment provides a link to a recursive version that can work (though not always) to resolve the full path.

JeffreyLWhitledge的评论提供了一个递归版本的链接,该版本可以解决完整路径(尽管不总是)。

#3


3  

The way to get the actual path of a file (this won't work for folders) is to follow these steps:

获取文件的实际路径(这对文件夹不起作用)的方法是遵循以下步骤:

  1. Call CreateFileMapping to create a mapping for the file.
  2. 调用CreateFileMapping以创建文件的映射。
  3. Call GetMappedFileName to get the name of the file.
  4. 调用GetMappedFileName以获取文件的名称。
  5. Use QueryDosDevice to convert it to an MS-DOS-style path name.
  6. 使用QueryDosDevice将其转换为MS-DOS样式的路径名。

If you feel like writing a more robust program that also works with directories (but with more pain and a few undocumented features), follow these steps:

如果您想编写一个更强大的程序,该程序也适用于目录(但更多的痛苦和一些未记录的功能),请按照下列步骤操作:

  1. Get a handle to the file/folder with CreateFile or NtOpenFile.
  2. 使用CreateFile或NtOpenFile获取文件/文件夹的句柄。
  3. Call NtQueryObject to get the full path name.
  4. 调用NtQueryObject以获取完整路径名。
  5. Call NtQueryInformationFile with FileNameInformation to get the volume-relative path.
  6. 使用FileNameInformation调用NtQueryInformationFile以获取卷相对路径。
  7. Using the two paths above, get the component of the path that represents the volume itself. For example, if you get \Device\HarddiskVolume1\Hello.txt for the first path and \Hello.txt for the second, you now know the volume's path is \Device\HarddiskVolume1.
  8. 使用上面的两个路径,获取表示卷本身的路径的组件。例如,如果您获得第一个路径的\ Device \ HarddiskVolume1 \ Hello.txt和第二个路径的\ Hello.txt,您现在知道卷的路径是\ Device \ HarddiskVolume1。
  9. Use either the poorly-documented Mount Manager I/O Control Codes or QueryDosDevice to convert substitute the volume portion of the full NT-style path with the drive letter.
  10. 使用记录不良的Mount Manager I / O控制代码或QueryDosDevice转换替换完整NT样式路径的卷部分和驱动器号。

Now you have the real path of the file.

现在你有了文件的真实路径。

#4


1  

As Borja's answer does not work for volumes where 8.3 names are disabled, here the recursive implementation that Tergiver suggests (works for files and folders, as well as the files and folders of UNC shares but not on their machine names nor their share names).

由于Borja的答案不适用于禁用8.3名称的卷,这里是Tergiver建议的递归实现(适用于文件和文件夹,以及UNC共享的文件和文件夹,但不适用于其机器名称及其共享名称)。

Non-existing file or folders are no problem, what exists is verified and corrected, but you might run into folder-redirection issues, e.g when trying to get the correct path of "C:\WinDoWs\sYsteM32\driVErs\eTC\Hosts" you'll get "C:\Windows\System32\drivers\eTC\hosts" on a 64bit windows as there is no "etc" folder withing "C:\Windows\sysWOW64\drivers".

不存在的文件或文件夹没有问题,存在的内容经过验证和更正,但是您可能会遇到文件夹重定向问题,例如在尝试获取“C:\ WinDoWs \ sYsteM32 \ drivers \ eTC \ Hosts”的正确路径时你会在64位Windows上获得“C:\ Windows \ System32 \ drivers \ eTC \ hosts”,因为没有“etc”文件夹包含“C:\ Windows \ sysWOW64 \ drivers”。

Test Scenario:

测试场景:

        Directory.CreateDirectory(@"C:\Temp\SomeFolder");
        File.WriteAllLines(@"C:\Temp\SomeFolder\MyTextFile.txt", new String[] { "Line1", "Line2" });

Usage:

用法:

        FileInfo myInfo = new FileInfo(@"C:\TEMP\SOMEfolder\MyTeXtFiLe.TxT");
        String myResult = myInfo.GetFullNameWithCorrectCase(); //Returns "C:\Temp\SomeFolder\MyTextFile.txt"

Code:

码:

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;
    }

}

#5


0  

Here's an alternate solution, works on files and directories. Uses GetFinalPathNameByHandle, which is only supported for desktop apps on Vista/Server2008 or above according to docs.

这是一个替代解决方案,适用于文件和目录。使用GetFinalPathNameByHandle,根据文档仅支持Vista / Server2008或更高版本上的桌面应用程序。

Note that it will resolve a symlink if you give it one, which is part of finding the "final" path.

请注意,如果您给它一个,它将解析一个符号链接,这是查找“最终”路径的一部分。

// 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);
    }
}

#6


0  

Alternative Solution

替代方案

Here is a solution that worked for me to move files between Windows and a server using case sensitive paths. It walks down the directory tree and corrects each entry with GetFileSystemEntries(). If part of the path is invalid (UNC or folder name), then it corrects the path only up to that point and then uses the original path for what it can't find. Anyway, hopefully this will save others time when dealing with the same issue.

这是一个解决方案,使我可以使用区分大小写的路径在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;
}

#7


-3  

On Windows, paths are case-insensitive. So both paths are equally real.

在Windows上,路径不区分大小写。所以两条路径都是同样真实的。

If you want to get some kind of a path with canonical capitalization (i. e. how Windows thinks it should be capitalized), you can call FindFirstFile() with the path as a mask, then take the full name of the found file. If the path is invalid, then you won't get a canonical name, natually.

如果你想获得某种规范大写的路径(即Windows认为它应该大写),你可以使用路径作为掩码调用FindFirstFile(),然后获取找到的文件的全名。如果路径无效,那么您将无法获得规范名称。

#1


17  

You can use this function:

你可以使用这个功能:

[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;
}

#2


7  

As an old-timer, I always used FindFirstFile for this purpose. The .Net translation is:

作为老朋友,我总是为此目的使用FindFirstFile。 .Net翻译是:

Directory.GetFiles(Path.GetDirectoryName(userSuppliedName), Path.GetFileName(userSuppliedName)).FirstOrDefault();

This only gets you the correct casing for the filename portion of the path, not then entire path.

这只能获得路径文件名部分的正确大小,而不是整个路径。

JeffreyLWhitledge's comment provides a link to a recursive version that can work (though not always) to resolve the full path.

JeffreyLWhitledge的评论提供了一个递归版本的链接,该版本可以解决完整路径(尽管不总是)。

#3


3  

The way to get the actual path of a file (this won't work for folders) is to follow these steps:

获取文件的实际路径(这对文件夹不起作用)的方法是遵循以下步骤:

  1. Call CreateFileMapping to create a mapping for the file.
  2. 调用CreateFileMapping以创建文件的映射。
  3. Call GetMappedFileName to get the name of the file.
  4. 调用GetMappedFileName以获取文件的名称。
  5. Use QueryDosDevice to convert it to an MS-DOS-style path name.
  6. 使用QueryDosDevice将其转换为MS-DOS样式的路径名。

If you feel like writing a more robust program that also works with directories (but with more pain and a few undocumented features), follow these steps:

如果您想编写一个更强大的程序,该程序也适用于目录(但更多的痛苦和一些未记录的功能),请按照下列步骤操作:

  1. Get a handle to the file/folder with CreateFile or NtOpenFile.
  2. 使用CreateFile或NtOpenFile获取文件/文件夹的句柄。
  3. Call NtQueryObject to get the full path name.
  4. 调用NtQueryObject以获取完整路径名。
  5. Call NtQueryInformationFile with FileNameInformation to get the volume-relative path.
  6. 使用FileNameInformation调用NtQueryInformationFile以获取卷相对路径。
  7. Using the two paths above, get the component of the path that represents the volume itself. For example, if you get \Device\HarddiskVolume1\Hello.txt for the first path and \Hello.txt for the second, you now know the volume's path is \Device\HarddiskVolume1.
  8. 使用上面的两个路径,获取表示卷本身的路径的组件。例如,如果您获得第一个路径的\ Device \ HarddiskVolume1 \ Hello.txt和第二个路径的\ Hello.txt,您现在知道卷的路径是\ Device \ HarddiskVolume1。
  9. Use either the poorly-documented Mount Manager I/O Control Codes or QueryDosDevice to convert substitute the volume portion of the full NT-style path with the drive letter.
  10. 使用记录不良的Mount Manager I / O控制代码或QueryDosDevice转换替换完整NT样式路径的卷部分和驱动器号。

Now you have the real path of the file.

现在你有了文件的真实路径。

#4


1  

As Borja's answer does not work for volumes where 8.3 names are disabled, here the recursive implementation that Tergiver suggests (works for files and folders, as well as the files and folders of UNC shares but not on their machine names nor their share names).

由于Borja的答案不适用于禁用8.3名称的卷,这里是Tergiver建议的递归实现(适用于文件和文件夹,以及UNC共享的文件和文件夹,但不适用于其机器名称及其共享名称)。

Non-existing file or folders are no problem, what exists is verified and corrected, but you might run into folder-redirection issues, e.g when trying to get the correct path of "C:\WinDoWs\sYsteM32\driVErs\eTC\Hosts" you'll get "C:\Windows\System32\drivers\eTC\hosts" on a 64bit windows as there is no "etc" folder withing "C:\Windows\sysWOW64\drivers".

不存在的文件或文件夹没有问题,存在的内容经过验证和更正,但是您可能会遇到文件夹重定向问题,例如在尝试获取“C:\ WinDoWs \ sYsteM32 \ drivers \ eTC \ Hosts”的正确路径时你会在64位Windows上获得“C:\ Windows \ System32 \ drivers \ eTC \ hosts”,因为没有“etc”文件夹包含“C:\ Windows \ sysWOW64 \ drivers”。

Test Scenario:

测试场景:

        Directory.CreateDirectory(@"C:\Temp\SomeFolder");
        File.WriteAllLines(@"C:\Temp\SomeFolder\MyTextFile.txt", new String[] { "Line1", "Line2" });

Usage:

用法:

        FileInfo myInfo = new FileInfo(@"C:\TEMP\SOMEfolder\MyTeXtFiLe.TxT");
        String myResult = myInfo.GetFullNameWithCorrectCase(); //Returns "C:\Temp\SomeFolder\MyTextFile.txt"

Code:

码:

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;
    }

}

#5


0  

Here's an alternate solution, works on files and directories. Uses GetFinalPathNameByHandle, which is only supported for desktop apps on Vista/Server2008 or above according to docs.

这是一个替代解决方案,适用于文件和目录。使用GetFinalPathNameByHandle,根据文档仅支持Vista / Server2008或更高版本上的桌面应用程序。

Note that it will resolve a symlink if you give it one, which is part of finding the "final" path.

请注意,如果您给它一个,它将解析一个符号链接,这是查找“最终”路径的一部分。

// 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);
    }
}

#6


0  

Alternative Solution

替代方案

Here is a solution that worked for me to move files between Windows and a server using case sensitive paths. It walks down the directory tree and corrects each entry with GetFileSystemEntries(). If part of the path is invalid (UNC or folder name), then it corrects the path only up to that point and then uses the original path for what it can't find. Anyway, hopefully this will save others time when dealing with the same issue.

这是一个解决方案,使我可以使用区分大小写的路径在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;
}

#7


-3  

On Windows, paths are case-insensitive. So both paths are equally real.

在Windows上,路径不区分大小写。所以两条路径都是同样真实的。

If you want to get some kind of a path with canonical capitalization (i. e. how Windows thinks it should be capitalized), you can call FindFirstFile() with the path as a mask, then take the full name of the found file. If the path is invalid, then you won't get a canonical name, natually.

如果你想获得某种规范大写的路径(即Windows认为它应该大写),你可以使用路径作为掩码调用FindFirstFile(),然后获取找到的文件的全名。如果路径无效,那么您将无法获得规范名称。