我需要知道给定路径的真实路径。
例如:
真实路径是:d:\src\File.txt
用户提供的是:D:\src\file.txt
我需要的结果是:d:\src\File.txt
我需要知道给定路径的真实路径。
例如:
真实路径是:d:\src\File.txt
用户提供的是:D:\src\file.txt
我需要的结果是:d:\src\File.txt
[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;
}
D:\Test\test.txt
,该函数返回d:\Test\test.txt
。 - A-Sharabiani作为一名老手,我通常使用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;
}
CreateFileMapping
创建一个文件映射。GetMappedFileName
获取文件名。QueryDosDevice
将文件名转换为 MS-DOS 风格的路径名。CreateFile
或 NtOpenFile
获取文件/文件夹句柄。NtQueryObject
获取完整路径名。NtQueryInformationFile
和 FileNameInformation
获取卷相对路径。\Device\HarddiskVolume1\Hello.txt
,第二个路径为 \Hello.txt
,则现在您知道卷的路径为 \Device\HarddiskVolume1
。QueryDosDevice
,将完整的 NT 风格路径中的卷部分替换为驱动器号。// 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);
}
}
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;
}
}
这是我如何做到的。最初,我依赖于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
}
我尝试避免使用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"。