microsoft - Obteniendo el nombre real del archivo(con la carcasa adecuada) en Windows con.NET
net framework windows 7 32 bits (8)
Quiero hacer exactamente lo mismo que en esta pregunta :
El sistema de archivos de Windows no distingue entre mayúsculas y minúsculas. ¿Cómo, dado el nombre de un archivo / carpeta (por ejemplo, "somefile"), obtengo el nombre real de ese archivo / carpeta (por ejemplo, debería devolver "SomeFile" si Explorer lo muestra así)?
Pero necesito hacerlo en .NET y quiero la ruta completa ( D:/Temp/Foobar.xml
y no solo Foobar.xml
).
Veo que FullName
en la clase FileInfo
no funciona.
¿Has probado la clase DirectoryInfo y Path, podrían hacer el truco. (No lo he probado yo mismo)
Creo que la única forma en que podrá hacerlo es mediante el uso de la misma API de Win32, concretamente el método SHGetFileInfo, mencionado en la respuesta aceptada para la pregunta que hace referencia. Para hacer esto, necesitará usar algunas llamadas interoperativas p / invoke. Eche un vistazo a pinvoke.net para ver un ejemplo de cómo hacer esto y qué estructuras adicionales necesitará.
Problema interesante.
Una forma de hacerlo es "buscar" un archivo basado en el nombre insensible a mayúsculas y minúsculas, y luego mirar la propiedad FileInfo.FullName. Lo he probado con la siguiente función y proporciona el resultado requerido.
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;
}
Debe tener un poco de cuidado aquí: si tiene dos archivos con nombres llamados file.xml y File.xml, solo devolverá el primero.
Parece que la mejor manera es recorrer todas las carpetas de la ruta y obtener los límites adecuados:
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
Mi segunda respuesta aquí con un método no recursivo. Acepta tanto archivos como directorios.
Esta vez traducido de VB a 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;
}
Inspirado por la respuesta de Ivan, este es un método que también maneja la carcasa de la letra de unidad:
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;
}
Me gustó la respuesta de Yona , pero quería que:
- Apoye las rutas UNC
- Dime si el camino no existía
- Use iteración en lugar de recursión (ya que solo usó recursividad de cola)
- Minimice el número de llamadas a Path.Combine (para minimizar las concatenaciones de cadenas).
/// <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;
}
Para las rutas UNC, este caso es la raíz (// Servidor / Compartir) en el caso del título en lugar del caso exacto porque sería mucho más trabajo intentar determinar el nombre del caso exacto del servidor remoto y el nombre del caso exacto de la acción. Si está interesado en agregar ese soporte, deberá utilizar métodos P / Invoke como NetServerEnum y NetShareEnum . Pero esos pueden ser lentos, y no admiten el filtrado inicial solo en el servidor y comparten nombres que le preocupan.
Aquí hay un método de prueba unitaria para TryGetExactPath (usando las extensiones de prueba de 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();
}
}
}
Parece que dado que NTFS no distingue entre mayúsculas y minúsculas, siempre aceptará su entrada correctamente, independientemente de si el nombre está encasillado correctamente.
La única forma de obtener el nombre de ruta correcto parece encontrar el archivo como sugirió John Sibly.
Creé un método que tomará una ruta (carpeta o archivo) y devolverá la versión correcta de la misma (para toda la ruta):
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();
}
}
Aquí hay algunos casos de prueba que funcionaron en mi máquina:
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();
}
El método devolverá el valor proporcionado si el archivo no existe.
Puede haber métodos más rápidos (esto usa recursividad) pero no estoy seguro de si hay alguna manera obvia de hacerlo.