使用.NET在Windows上获取实际文件名(使用适当的大小写)

时间:2008-11-28 14:47:02

标签: .net windows

我想和this question完全一样:

  

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

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

我发现FullName上的FileInfo不能解决问题。

7 个答案:

答案 0 :(得分:24)

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

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

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

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

以下是一些适用于我的计算机的测试用例:

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

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

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

答案 1 :(得分:8)

我喜欢Yona's answer,但我希望它:

  • 支持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;
}

对于UNC路径,这会出现标题情况下的根(\\ Server \ Share),而不是确切的情况,因为尝试确定远程服务器时,很多工作量会更多确切的案例名称和共享的确切案例名称。如果您有兴趣添加该支持,则必须使用P / Invoke方法,例如NetServerEnumNetShareEnum。但这些可能很慢,而且他们不支持对服务器进行预先过滤并共享您关注的名称。

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

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

答案 2 :(得分:5)

受Ivan的回答启发,这里也是一种处理驱动器字母外壳的方法:

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

答案 3 :(得分:4)

我的第二个答案是非递归方法。它接受文件和目录 这次从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;
}

答案 4 :(得分:1)

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

答案 5 :(得分:0)

有趣的问题。

一种方法是根据不区分大小写的名称“查找”文件,然后查看FileInfo.FullName属性。我使用以下函数测试了它,它给出了所需的结果。

static string GetCaseSensitiveFileName(string filePath)
{
    string caseSensitiveFilePath = null;

    DirectoryInfo dirInfo = new DirectoryInfo(Path.GetDirectoryName(filePath));
    FileInfo[] files = dirInfo.GetFiles(Path.GetFileName(filePath));
    if (files.Length > 0)
    {
        caseSensitiveFilePath = files[0].FullName;
    }

    return caseSensitiveFilePath;
}

你需要在这里小心一点 - 如果你有两个文件调用名称,如file.xml和File.xml,那么它只会返回第一个。

答案 6 :(得分:0)

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

 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