Подтвердить что ты не робот

Получение фактического имени файла (с соответствующим корпусом) в Windows с .NET.

Я хочу сделать то же самое, что и в этом вопросе:

Файловая система Windows нечувствительна к регистру. Как, учитывая имя файла/папки (например, "somefile" ), я получаю фактическое имя этого файла/папки (например, он должен возвращать "SomeFile", если Explorer показывает это так)?

Но мне нужно сделать это в .NET, и мне нужен полный путь (D:/Temp/Foobar.xml, а не только Foobar.xml).

Я вижу, что FullName в классе FileInfo не выполняет трюк.

4b9b3361

Ответ 1

Мне кажется, что, поскольку NTFS нечувствительна к регистру, он всегда будет принимать ваш вход правильно, независимо от того, правильно ли оно указано.

Единственный способ получить правильное имя пути, похоже, найти файл, например, предложенный Джоном Сайблистом.

Я создал метод, который возьмет путь (папку или файл) и вернет корректную версию его (для всего пути):

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

Метод возвращает возвращаемое значение, если файл не существует.

Бывают более быстрые методы (это использует рекурсию), но я не уверен, есть ли какие-либо очевидные способы сделать это.

Ответ 2

Вдохновленный Иваном ответом, вот метод, который также обрабатывает корпус буклета:

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

Мне понравился ответ Yona, но я хотел:

  • Поддержка путей 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 это приводит к тому, что root (\\ Server\Share) в заголовке, а не в точном случае, потому что это будет много больше, чтобы попытаться определить имя удаленного имени удаленного сервера и доля точное имя случая. Если вы заинтересованы в добавлении этой поддержки, вам придется использовать методы P/Invoke, такие как NetServerEnum и NetShareEnum. Но они могут быть медленными, и они не поддерживают предварительную фильтрацию только на сервере и обмениваются именами, которые вы заинтересованы.

Здесь unit test метод 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();
        }
    }
}

Ответ 4

Мой второй ответ здесь с нерекурсивным методом. Он принимает как файлы, так и файлы.
На этот раз перевод с VB на С#:

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

Ответ 5

Я думаю, что единственный способ, которым вы собираетесь это сделать, - использовать тот же API Win32, а именно метод SHGetFileInfo, упомянутый в принятом ответе на заданный вами вопрос. Для этого вам нужно будет использовать несколько вызовов pop/invoke. Посмотрите pinvoke.net для примера того, как это сделать и какие дополнительные структуры вам понадобятся.

Ответ 6

Интересная проблема.

Один из способов сделать это - найти файл на основе нечувствительного к регистру имени, а затем посмотреть свойство 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, тогда он вернет только первый.

Ответ 7

Похоже, лучший способ - перебрать все папки в пути и получить их правильные колпачки:

 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

Ответ 8

Вы пробовали класс DirectoryInfo и Path, они могли бы сделать трюк. (Не пробовал сам)