Получение фактического имени файла (с соответствующим корпусом) в Windows с .NET.
Я хочу сделать то же самое, что и в этом вопросе:
Файловая система Windows нечувствительна к регистру. Как, учитывая имя файла/папки (например, "somefile" ), я получаю фактическое имя этого файла/папки (например, он должен возвращать "SomeFile", если Explorer показывает это так)?
Но мне нужно сделать это в .NET, и мне нужен полный путь (D:/Temp/Foobar.xml
, а не только Foobar.xml
).
Я вижу, что FullName
в классе FileInfo
не выполняет трюк.
Ответы
Ответ 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, они могли бы сделать трюк. (Не пробовал сам)