2011-01-21 12 views
24

Necesito saber cuál es la ruta real de una ruta determinada.¿Cómo puedo obtener la ruta sensible a mayúsculas y minúsculas en Windows?

Por ejemplo:

El camino real es: d: \ src \ Archivo.txt
Y el usuario dame: D: \ src \ archivo.txt
necesito como resultado: d: \ src \ archivo.txt

+1

Por "sensible" ¿Qué significa "case sensitive"? – Mehrdad

+0

¿Qué código está fallando cuando se utiliza la ruta ingresada por el usuario? Las rutas normalmente no distinguen entre mayúsculas y minúsculas en Windows. – Justin

+3

Tengo la impresión de que Windows tiene un sistema de archivos fundamentalmente insensible a mayúsculas y minúsculas. Siendo ese el caso, esto es, en el mejor de los casos, innecesario, y en el peor ... una tontería. :) –

Respuesta

17

se puede utilizar esta función:

[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; 
} 
+0

¿Has marcado esto? No digo que no funcione, pero tampoco estoy seguro de que funcione, ya que dudo que realmente cree el archivo y cambie la ruta de acceso. – Mehrdad

+0

Gracias, funciona bien – Rodrigo

+0

Lo siento, al principio pensé que esto no funciona, pero estaba probando 'GetFullPathName' y no' GetLongPathName'. Buena solución. – Mehrdad

-3

En Windows, los caminos son mayúsculas y minúsculas. Entonces, ambos caminos son igualmente reales.

Si desea obtener algún tipo de ruta con mayúsculas y minúsculas canónicas (es decir, cómo cree Windows que debe escribirse en mayúscula), puede llamar a FindFirstFile() con la ruta como máscara, y luego tomar el nombre completo del archivo encontrado . Si la ruta no es válida, entonces no obtendrá un nombre canónico, naturalmente.

3

La manera de obtener la ruta real de un archivo (esto no funcionará para las carpetas) es seguir estos pasos:

  1. llamada CreateFileMapping para crear una asignación para el archivo.
  2. Llame al GetMappedFileName para obtener el nombre del archivo.
  3. Utilice QueryDosDevice para convertirlo a un nombre de ruta de acceso estilo MS-DOS.

Si se siente como escribir un programa más robusto que también trabaja con directorios (pero con más dolor y algunas características no documentadas), siga estos pasos:

  1. obtener un identificador para el archivo/carpeta con CreateFile o NtOpenFile.
  2. Llame al NtQueryObject para obtener el nombre completo de la ruta.
  3. Llame al NtQueryInformationFile con FileNameInformation para obtener la ruta relativa al volumen.
  4. Usando las dos rutas anteriores, obtenga el componente de la ruta que representa el volumen en sí. Por ejemplo, si obtiene \Device\HarddiskVolume1\Hello.txt para la primera ruta y \Hello.txt para la segunda, ahora sabe que la ruta del volumen es \Device\HarddiskVolume1.
  5. Utilice los Códigos de control de E/S de Mount Manager pobremente documentados o QueryDosDevice para convertir la parte del volumen de la ruta completa de estilo NT con la letra de la unidad.

Ahora tiene la ruta real del archivo.

+0

Presumiblemente dado un directorio que podría crear un archivo temporal, use la primera técnica para obtener la ruta real del archivo, y luego quite la parte del nombre de archivo. (Bueno, si tiene acceso de escritura, de todos modos) –

+0

También hay ['GetFinalPathNameByHandle'] (http://msdn.microsoft.com/en-us/library/windows/desktop/aa364962%28v=vs.85 % 29.aspx) a partir de Windows Vista. –

7

Como veterano, siempre utilicé FindFirstFile para este propósito. La traducción es .Net:

Directory.GetFiles(Path.GetDirectoryName(userSuppliedName), Path.GetFileName(userSuppliedName)).FirstOrDefault(); 

Esto sólo se consigue la caja correcta para la parte de nombre de archivo de la ruta, no entonces toda camino.

El comentario de JeffreyLWhitledge proporciona un enlace a una versión recursiva que puede funcionar (aunque no siempre) para resolver la ruta completa.

+0

agradable; Me encanta el único liner sin las dllimports – milkplus

+1

Esto no genera la ruta de salida correcta que se desea. – Paul

+0

@Paul ¿Puedes dar un ejemplo específico donde esto falla? – Tergiver

1

Como la respuesta de Borja no funciona para volúmenes donde 8,3 nombres están deshabilitados, aquí la implementación recursiva que sugiere Tergiver (funciona para archivos y carpetas, así como los archivos y carpetas de recursos compartidos UNC pero no compartir nombres).

archivo no existente o carpetas no son un problema, lo que existe es verificada y corregida, pero es posible que tenga problemas de redirección de carpetas, por ejemplo cuando se trata de obtener la ruta correcta de "C: \ Windows \ System32 \ drivers \ eTC \ Hosts "obtendrá" C: \ Windows \ System32 \ drivers \ eTC \ hosts "en una ventana de 64 bits ya que no hay una carpeta" etc "con" C: \ Windows \ sysWOW64 \ drivers ".

prueba Escenario:

 Directory.CreateDirectory(@"C:\Temp\SomeFolder"); 
     File.WriteAllLines(@"C:\Temp\SomeFolder\MyTextFile.txt", new String[] { "Line1", "Line2" }); 

Uso:

 FileInfo myInfo = new FileInfo(@"C:\TEMP\SOMEfolder\MyTeXtFiLe.TxT"); 
     String myResult = myInfo.GetFullNameWithCorrectCase(); //Returns "C:\Temp\SomeFolder\MyTextFile.txt" 

Código:

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

} 
0

Aquí es una solución alternativa, funciona en archivos y directorios. Utiliza GetFinalPathNameByHandle, que solo es compatible con aplicaciones de escritorio en Vista/Server2008 o superior de acuerdo con los documentos.

Tenga en cuenta que resolverá un enlace simbólico si le da uno, que es parte de la búsqueda de la ruta "final".

// 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); 
    } 
} 
0

Solución Alternativa

Aquí es una solución que funcionó para mí para mover archivos entre Windows y un servidor utilizando rutas entre mayúsculas y minúsculas. Recorre el árbol de directorios y corrige cada entrada con GetFileSystemEntries(). Si parte de la ruta no es válida (nombre UNC o carpeta), entonces corrige la ruta solo hasta ese punto y luego usa la ruta original para lo que no puede encontrar. De todos modos, con suerte esto ahorrará tiempo a otros cuando se trata del mismo problema.

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