Как получить путь с учетом регистра в Windows?
Мне нужно знать, каков реальный путь данного пути.
Например:
Реальный путь: d:\src\File.txt
И пользователь дал мне: D:\src\file.txt
Мне нужно в результате: d:\src\File.txt
9 ответов
Вы можете использовать эту функцию:
[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;
}
Как старый таймер, я всегда использовал FindFirstFile для этой цели. Перевод.Net:
Directory.GetFiles(Path.GetDirectoryName(userSuppliedName), Path.GetFileName(userSuppliedName)).FirstOrDefault();
Это только дает вам правильный регистр для части пути в имени файла, а не для всего пути.
Комментарий JeffreyLWhitledge предоставляет ссылку на рекурсивную версию, которая может работать (хотя и не всегда) для определения полного пути.
Альтернативное решение
Вот решение, которое помогло мне переместить файлы между 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;
}
Вот альтернативное решение, работает с файлами и каталогами. Использует GetFinalPathNameByHandle, который поддерживается только для настольных приложений на Vista/Server2008 или выше в соответствии с документами.
Обратите внимание, что он разрешит символическую ссылку, если вы дадите ей ссылку, которая является частью поиска "окончательного" пути.
// 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);
}
}
Чтобы получить фактический путь к файлу (это не будет работать для папок), выполните следующие действия:
- Вызов
CreateFileMapping
создать сопоставление для файла. - Вызов
GetMappedFileName
чтобы получить имя файла. - использование
QueryDosDevice
преобразовать его в имя пути в стиле MS-DOS.
Если вы хотите написать более надежную программу, которая также работает с каталогами (но с большей трудностью и несколькими недокументированными функциями), выполните следующие действия:
- Получить дескриптор файла / папки с
CreateFile
или жеNtOpenFile
, - Вызов
NtQueryObject
чтобы получить полное имя пути. - Вызов
NtQueryInformationFile
сFileNameInformation
чтобы получить объемно-относительный путь. - Используя два пути выше, получите компонент пути, который представляет сам том. Например, если вы получаете
\Device\HarddiskVolume1\Hello.txt
для первого пути и\Hello.txt
во-вторых, теперь вы знаете, путь тома\Device\HarddiskVolume1
, - Используйте плохо документированные контрольные коды ввода-вывода Mount Manager или
QueryDosDevice
для преобразования замените часть тома полного пути в стиле NT буквой диска.
Теперь у вас есть реальный путь к файлу.
Поскольку ответ Борха не работает для томов, в которых имена 8.3 отключены, здесь предлагается рекурсивная реализация, которую предлагает Tergiver (работает с файлами и папками, а также с файлами и папками общих папок UNC, но не на именах их компьютеров или именах общих папок).
Несуществующий файл или папки не являются проблемой, то, что существует, проверяется и исправляется, но вы можете столкнуться с проблемами перенаправления папок, например, при попытке получить правильный путь к "C:\WinDoWs\sYsteM32\driVErs\eTC\Hosts" вы получите "C:\Windows\System32\drivers\eTC\hosts" в 64-битных окнах, так как нет папки "etc" с "C:\Windows\sysWOW64\drivers".
Тестовый сценарий:
Directory.CreateDirectory(@"C:\Temp\SomeFolder");
File.WriteAllLines(@"C:\Temp\SomeFolder\MyTextFile.txt", new String[] { "Line1", "Line2" });
Использование:
FileInfo myInfo = new FileInfo(@"C:\TEMP\SOMEfolder\MyTeXtFiLe.TxT");
String myResult = myInfo.GetFullNameWithCorrectCase(); //Returns "C:\Temp\SomeFolder\MyTextFile.txt"
Код:
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;
}
}
Я пытался избежать импорта dll, поэтому лучшим способом для меня было использование System.Linq и класса System.IO.Directory.
Для вашего примера Реальный путь: d: \ src \ File.txt Пользователь дал мне: D: \ src \ file.txt
Код для этого:
using System.Linq;
public static class PathUtils
{
public static string RealPath(string inputPath)
{
return Directory.GetFiles(Path.GetDirectoryName(inputPath))
.FirstOrDefault(p => String.Equals(Path.GetFileName(p),
Path.GetFileName(inputPath), StringComparison.OrdinalIgnoreCase));
}
}
var p = PathUtils.RealPath(@"D: \ src \ file.txt");
Метод должен возвращать путь "d: \ src \ File.txt" или "D: \ src \ File.txt".
Вот как я это делаю. Изначально я зависел от
GetFinalPathNameByHandle
что очень хорошо, но, к сожалению, некоторые пользовательские файловые системы не поддерживают его (конечно, NTFS поддерживает). Я тоже пробовал
NtQueryObject
с участием
ObjectNameInformation
но опять же, они не обязательно сообщают исходное имя файла.
Итак, вот еще один «ручной» способ:
public static string GetRealPath(string fullPath)
{
if (fullPath == null)
return null; // invalid
var pos = fullPath.LastIndexOf(Path.DirectorySeparatorChar);
if (pos < 0 || pos == (fullPath.Length - 1))
return fullPath.ToUpperInvariant(); // drive letter
var dirPath = fullPath.Substring(0, pos);
var realPath = GetRealPath(dirPath); // go recursive, we want the final full path
if (realPath == null)
return null; // doesn't exist
var dir = new DirectoryInfo(realPath);
if (!dir.Exists)
return null; // doesn't exist
var fileName = fullPath.Substring(pos + 1);
if (fileName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) // avoid wildcard calls
return null;
return dir.EnumerateFileSystemInfos(fileName).FirstOrDefault()?.FullName; // may return null
}
В Windows пути нечувствительны к регистру. Так что оба пути одинаково реальны.
Если вы хотите получить какой-либо путь с канонической заглавной буквой (то есть, как Windows считает, что он должен быть заглавной), вы можете вызвать FindFirstFile() с путем в качестве маски, а затем взять полное имя найденного файла. Если путь неверен, то вы не получите каноническое имя, естественно.