使用 .NET 在 Windows 上获取实际文件名(带有正确的大小写)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/325931/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 11:40:22  来源:igfitidea点击:

Getting actual file name (with proper casing) on Windows with .NET

.netwindows

提问by pauldoo

I want to do exactly the same as in this question:

我想做与这个问题完全相同的事情:

Windows file system is case insensitive. How, given a file/folder name (e.g. "somefile"), I get the actual name of that file/folder (e.g. it should return "SomeFile" if Explorer displays it so)?

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

But I need to do it in .NET and I want the full path (D:/Temp/Foobar.xmland not just Foobar.xml).

但我需要在 .NET 中完成,并且我想要完整路径(D:/Temp/Foobar.xml而不仅仅是Foobar.xml)。

I see that FullNameon the FileInfoclass doesn't do the trick.

我看到FullNameFileInfo课堂上并不能解决问题。

采纳答案by Yona

I seems that since NTFS is case insensitive it will always accept your input correctly regardless if the name is cased right.

我似乎因为 NTFS 不区分大小写,无论名称大小写是否正确,它都会始终正确接受您的输入。

The only way to get the correct path name seems to find the file like John Sibly suggested.

获得正确路径名的唯一方法似乎是找到像 John Sively 建议的文件。

I created a method that will take a path (folder or file) and return the correctly cased version of it (for the entire path):

我创建了一个方法,它将采用一个路径(文件夹或文件)并返回它的正确大小写版本(对于整个路径):

    public static string GetExactPathName(string pathName)
    {
        if (!(File.Exists(pathName) || Directory.Exists(pathName)))
            return pathName;

        var di = new DirectoryInfo(pathName);

        if (di.Parent != null) {
            return Path.Combine(
                GetExactPathName(di.Parent.FullName), 
                di.Parent.GetFileSystemInfos(di.Name)[0].Name);
        } else {
            return di.Name.ToUpper();
        }
    }

Here are some test cases that worked on my machine:

以下是在我的机器上运行的一些测试用例:

    static void Main(string[] args)
    {
        string file1 = @"c:\documents and settings\administrator\ntuser.dat";
        string file2 = @"c:\pagefile.sys";
        string file3 = @"c:\windows\system32\cmd.exe";
        string file4 = @"c:\program files\common files";
        string file5 = @"ddd";

        Console.WriteLine(GetExactPathName(file1));
        Console.WriteLine(GetExactPathName(file2));
        Console.WriteLine(GetExactPathName(file3));
        Console.WriteLine(GetExactPathName(file4));
        Console.WriteLine(GetExactPathName(file5));

        Console.ReadLine();
    }

The method will return the supplied value if the file does not exists.

如果文件不存在,该方法将返回提供的值。

There might be faster methods (this uses recursion) but I'm not sure if there are any obvious ways to do it.

可能有更快的方法(这使用递归),但我不确定是否有任何明显的方法可以做到这一点。

回答by Bill Menees

I liked Yona's answer, but I wanted it to:

我喜欢Yona 的回答,但我希望它:

  • Support UNC paths
  • Tell me if the path didn't exist
  • Use iteration instead of recursion (since it only used tail recursion)
  • Minimize the number of calls to Path.Combine (to minimize string concatenations).
  • 支持UNC路径
  • 告诉我路径是否不存在
  • 使用迭代而不是递归(因为它只使用尾递归)
  • 最小化对 Path.Combine 的调用次数(以最小化字符串连接)。
/// <summary>
/// Gets the exact case used on the file system for an existing file or directory.
/// </summary>
/// <param name="path">A relative or absolute path.</param>
/// <param name="exactPath">The full path using the correct case if the path exists.  Otherwise, null.</param>
/// <returns>True if the exact path was found.  False otherwise.</returns>
/// <remarks>
/// This supports drive-lettered paths and UNC paths, but a UNC root
/// will be returned in title case (e.g., \Server\Share).
/// </remarks>
public static bool TryGetExactPath(string path, out string exactPath)
{
    bool result = false;
    exactPath = null;

    // DirectoryInfo accepts either a file path or a directory path, and most of its properties work for either.
    // However, its Exists property only works for a directory path.
    DirectoryInfo directory = new DirectoryInfo(path);
    if (File.Exists(path) || directory.Exists)
    {
        List<string> parts = new List<string>();

        DirectoryInfo parentDirectory = directory.Parent;
        while (parentDirectory != null)
        {
            FileSystemInfo entry = parentDirectory.EnumerateFileSystemInfos(directory.Name).First();
            parts.Add(entry.Name);

            directory = parentDirectory;
            parentDirectory = directory.Parent;
        }

        // Handle the root part (i.e., drive letter or UNC \server\share).
        string root = directory.FullName;
        if (root.Contains(':'))
        {
            root = root.ToUpper();
        }
        else
        {
            string[] rootParts = root.Split('\');
            root = string.Join("\", rootParts.Select(part => CultureInfo.CurrentCulture.TextInfo.ToTitleCase(part)));
        }

        parts.Add(root);
        parts.Reverse();
        exactPath = Path.Combine(parts.ToArray());
        result = true;
    }

    return result;
}

For UNC paths, this cases the root (\\Server\Share) in title case rather than exact case because it would be a lotmore work to try determine the remote server's exact case name and the share's exact case name. If you're interested in adding that support you'll have to P/Invoke methods like NetServerEnumand NetShareEnum. But those can be slow, and they don't support up-front filtering to just the server and share names you're concerned with.

对于 UNC 路径,这种情况下根 (\\Server\Share) 的标题大小写而不是精确大小写,因为尝试确定远程服务器的精确大小写名称和共享的精确大小写名称需要更多的工作。如果您有兴趣添加该支持,则必须 P/Invoke 方法,如NetServerEnumNetShareEnum。但是这些可能会很慢,而且它们不支持仅对服务器进行预先过滤并共享您关心的名称。

Here's a unit test method for TryGetExactPath (using Visual Studio Testing Extensions):

这是 TryGetExactPath 的单元测试方法(使用Visual Studio 测试扩展):

[TestMethod]
public void TryGetExactPathNameTest()
{
    string machineName = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(Environment.MachineName.ToLower());
    string[] testPaths = new[]
        {
            @"C:\Users\Public\desktop.ini",
            @"C:\pagefile.sys",
            @"C:\Windows\System32\cmd.exe",
            @"C:\Users\Default\NTUSER.DAT",
            @"C:\Program Files (x86)\Microsoft.NET\Primary Interop Assemblies",
            @"C:\Program Files (x86)",
            @"Does not exist",
            @"\Nas\Main\Setups",
            @"\Nas\Main\Setups\Microsoft\Visual Studio\VS 2015\vssdk_full.exe",
            @"\" + machineName + @"\C$\Windows\System32\ActionCenter.dll",
            @"..",
        };
    Dictionary<string, string> expectedExactPaths = new Dictionary<string, string>()
        {
            { @"..", Path.GetDirectoryName(Environment.CurrentDirectory) },
        };

    foreach (string testPath in testPaths)
    {
        string lowercasePath = testPath.ToLower();
        bool expected = File.Exists(lowercasePath) || Directory.Exists(lowercasePath);
        string exactPath;
        bool actual = FileUtility.TryGetExactPath(lowercasePath, out exactPath);
        actual.ShouldEqual(expected);
        if (actual)
        {
            string expectedExactPath;
            if (expectedExactPaths.TryGetValue(testPath, out expectedExactPath))
            {
                exactPath.ShouldEqual(expectedExactPath);
            }
            else
            {
                exactPath.ShouldEqual(testPath);
            }
        }
        else
        {
            exactPath.ShouldBeNull();
        }
    }
}

回答by bingles

Inspired by Ivan's answer, here is a method that also handles drive letter casing as well:

受伊万回答的启发,这里有一种方法也可以处理驱动器字母大小写:

public string FixFilePathCasing(string filePath)
{
    string fullFilePath = Path.GetFullPath(filePath);

    string fixedPath = "";
    foreach(string token in fullFilePath.Split('\'))
    {
        //first token should be drive token
        if(fixedPath == "")
        {
            //fix drive casing
            string drive = string.Concat(token, "\");
            drive = DriveInfo.GetDrives()
                .First(driveInfo => driveInfo.Name.Equals(drive, StringComparison.OrdinalIgnoreCase)).Name;

            fixedPath = drive;
        }
        else
        {
            fixedPath = Directory.GetFileSystemEntries(fixedPath, token).First();
        }
    }

    return fixedPath;
}

回答by Ivan Ferrer Villa

My second answer here with a non recursive method. It accepts both files and dirs.
This time translated from VB to C#:

我的第二个答案是非递归方法。它接受文件和目录。
这次从VB翻译成C#:

private string fnRealCAPS(string sDirOrFile)
{
    string sTmp = "";
    foreach (string sPth in sDirOrFile.Split("\")) {
        if (string.IsNullOrEmpty(sTmp)) {
            sTmp = sPth + "\";
            continue;
        }
        sTmp = System.IO.Directory.GetFileSystemEntries(sTmp, sPth)[0];
    }
    return sTmp;
}

回答by Scott Dorman

I think the only way you are going to be able to do this is by using the same Win32 API, namely the SHGetFileInfo method, mentioned in the accepted answer for the question you reference. In order to do this, you will need to use some interop p/invoke calls. Take a look at pinvoke.netfor an example of how to do this and what additional structs you will need.

我认为您能够做到这一点的唯一方法是使用相同的 Win32 API,即 SHGetFileInfo 方法,在您参考的问题的已接受答案中提到。为此,您需要使用一些互操作 p/invoke 调用。查看pinvoke.net的示例,了解如何执行此操作以及您需要哪些其他结构。

回答by Ivan Ferrer Villa

It looks like the best way is to iterate through all folders in the path and get their proper caps:

看起来最好的方法是遍历路径中的所有文件夹并获得正确的大写:

 Public Function gfnProperPath(ByVal sPath As String) As String
    If Not IO.File.Exists(sPath) AndAlso Not IO.Directory.Exists(sPath) Then Return sPath
    Dim sarSplitPath() As String = sPath.Split("\")
    Dim sAddPath As String = sarSplitPath(0).ToUpper & "\"
    For i = 1 To sarSplitPath.Length - 1
        sPath = sAddPath & "\" & sarSplitPath(i)
        If IO.File.Exists(sPath) Then
            Return IO.Directory.GetFiles(sAddPath, sarSplitPath(i), IO.SearchOption.TopDirectoryOnly)(0)
        ElseIf IO.Directory.Exists(sPath) Then
            sAddPath = IO.Directory.GetDirectories(sAddPath, sarSplitPath(i), IO.SearchOption.TopDirectoryOnly)(0)
        End If
    Next
    Return sPath
End Function