2008-09-16 10 views
15

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 actual de ese archivo/carpeta (por ejemplo, debe devolver "SomeFile" si Explorer lo muestra así)?Obteniendo el nombre real del archivo (con la carcasa adecuada) en Windows

Algunas formas que conozco, todos los cuales parecen bastante al revés:

  1. Dada la ruta completa, la búsqueda de cada carpeta en la ruta de acceso (a través de FindFirstFile). Esto proporciona los resultados adecuados de cada carpeta. En el último paso, busca el archivo en sí.
  2. Obtener el nombre del archivo desde el identificador (como en MSDN example). Esto requiere abrir un archivo, crear una asignación de archivos, obtener su nombre, analizar los nombres de los dispositivos, etc. Muy complicado. Y no funciona para carpetas o archivos de tamaño cero.

¿Me falta alguna obvia llamada WinAPI? Los más simples, como GetActualPathName() o GetFullPathName(), devuelven el nombre usando la cubierta que se pasó (por ejemplo, devuelve "archivos de programa" si se pasó, incluso si debe ser "Archivos de programa").

Estoy buscando una solución nativa (no .NET uno).

Respuesta

6

Y por este medio respondo mi propia pregunta, basada en original answer from cspirz.

Aquí hay una función que, dada la ruta absoluta, relativa o de red, devolverá la ruta con mayúsculas/minúsculas como se mostraría en Windows. Si algún componente de la ruta no existe, devolverá la ruta pasada en ese punto.

Es bastante complicado porque trata de gestionar rutas de red y otros casos extremos. Opera en cadenas de caracteres anchas y usa std :: wstring. Sí, en teoría, Unicode TCHAR podría no ser lo mismo que wchar_t; que es un ejercicio para el lector :)

std::wstring GetActualPathName(const wchar_t* path) 
{ 
    // This is quite involved, but the meat is SHGetFileInfo 

    const wchar_t kSeparator = L'\\'; 

    // copy input string because we'll be temporary modifying it in place 
    size_t length = wcslen(path); 
    wchar_t buffer[MAX_PATH]; 
    memcpy(buffer, path, (length+1) * sizeof(path[0])); 

    size_t i = 0; 

    std::wstring result; 

    // for network paths (\\server\share\RestOfPath), getting the display 
    // name mangles it into unusable form (e.g. "\\server\share" turns 
    // into "share on server (server)"). So detect this case and just skip 
    // up to two path components 
    if(length >= 2 && buffer[0] == kSeparator && buffer[1] == kSeparator) 
    { 
     int skippedCount = 0; 
     i = 2; // start after '\\' 
     while(i < length && skippedCount < 2) 
     { 
      if(buffer[i] == kSeparator) 
       ++skippedCount; 
      ++i; 
     } 

     result.append(buffer, i); 
    } 
    // for drive names, just add it uppercased 
    else if(length >= 2 && buffer[1] == L':') 
    { 
     result += towupper(buffer[0]); 
     result += L':'; 
     if(length >= 3 && buffer[2] == kSeparator) 
     { 
      result += kSeparator; 
      i = 3; // start after drive, colon and separator 
     } 
     else 
     { 
      i = 2; // start after drive and colon 
     } 
    } 

    size_t lastComponentStart = i; 
    bool addSeparator = false; 

    while(i < length) 
    { 
     // skip until path separator 
     while(i < length && buffer[i] != kSeparator) 
      ++i; 

     if(addSeparator) 
      result += kSeparator; 

     // if we found path separator, get real filename of this 
     // last path name component 
     bool foundSeparator = (i < length); 
     buffer[i] = 0; 
     SHFILEINFOW info; 

     // nuke the path separator so that we get real name of current path component 
     info.szDisplayName[0] = 0; 
     if(SHGetFileInfoW(buffer, 0, &info, sizeof(info), SHGFI_DISPLAYNAME)) 
     { 
      result += info.szDisplayName; 
     } 
     else 
     { 
      // most likely file does not exist. 
      // So just append original path name component. 
      result.append(buffer + lastComponentStart, i - lastComponentStart); 
     } 

     // restore path separator that we might have nuked before 
     if(foundSeparator) 
      buffer[i] = kSeparator; 

     ++i; 
     lastComponentStart = i; 
     addSeparator = true; 
    } 

    return result; 
} 

Una vez más, gracias a cspirz por dirigirme a SHGetFileInfo.

+2

[SHGetFileInfo] (https://msdn.microsoft.com/en-us/library/bb762179.aspx): * "** SHGFI_DISPLAYNAME: ** Recuperar el nombre para mostrar del archivo, que es el nombre tal como aparece en Windows Explorer. [...] ** Tenga en cuenta que el nombre para mostrar puede verse afectado por la configuración, como si se muestran extensiones. ** "* ¿Está seguro de que esto es lo que está buscando? – IInspectable

+1

Esto ** NO FUNCIONARÁ ** para la carpeta 'C: \ Users \ YourAccountName \ Documents' en Windows 8: devolverá' C: \ Users \ YourAccountName \ My Documents' en su lugar ya que la carpeta "Documentos" se muestra como "Mi Documentos "en el Explorador de Windows. – izogfif

-1

Por lo que sé, la propiedad Nombre de clase System.IO.FileInfo le devolverá el nombre real de Windows.

+1

esto es .NET, código no nativo – linquize

1

Vale, esto es VBScript, pero aún así me gustaría sugerir el uso del objeto Scripting.FileSystemObject

Dim fso 
Set fso = CreateObject("Scripting.FileSystemObject") 
Dim f 
Set f = fso.GetFile("C:\testfile.dat") 'actually named "testFILE.dAt" 
wscript.echo f.Name 

La respuesta que recibo es de este fragmento es

testFILE.dAt 

la esperanza de que al menos te señala en la dirección correcta.

3

¿Ha intentado utilizar SHGetFileInfo?

+0

Yay, esto funciona. Devuelve solo el último componente de nombre de ruta (es decir, la carpeta final o el nombre de archivo de toda la ruta), pero es mucho más simple que usar FindFirstFile al menos. – NeARAZ

-1

Después de una prueba rápida, GetLongPathName() hace lo que quiere.

+0

No funciona. GetLongPathName() devuelve cualquier canalización que se haya pasado ("c: \ program files" lo devolverá en minúscula, lo mismo para los nombres de archivo). – NeARAZ

-2

Esto hace el truco:

win32file.FindFilesW('somefile')[0][-2] 

devuelve 'somefile'.

EDIT: Estúpida, yo estaba buscando lo mismo en Python. Así que ignora esto para C/C++ ...

4

Hay otra solución. Primero llame a GetShortPathName() y luego a GetLongPathName(). Adivina qué personaje se usará entonces? ;-)

+0

¡Funciona! ¡Gracias! – ybungalobill

+0

Es muy sucio, pero funciona como un encanto. – StilesCrisis

+0

¿Funciona esto si los nombres de archivos cortos están deshabilitados en el sistema de archivos? –

0

FindFirstFileNameW trabajará con algunos inconvenientes:

  • que no funciona en rutas UNC
  • se despoja a la letra de la unidad por lo que necesita para volver a añadirlo
  • si hay más de un enlace rígido a su archivo necesita identificar el correcto
1

EDIT: Ahora que volví a leer la pregunta, parece que OP es consciente de esta solución y busca otra solución.

Encontré que FindFirstFile() devolverá el nombre correcto del archivo de la carcasa (última parte de la ruta) en fd.cFileName. Si pasamos c:\winDOWs\exPLORER.exe como primer parámetro a FindFirstFile(), la fd.cFileName habría explorer.exe así:

prove

Si sustituimos la última parte del camino con fd.cFileName, obtendremos la última parte de la derecha; la ruta se convertiría en c:\winDOWs\explorer.exe.

Suponiendo que la ruta sea siempre una ruta absoluta (sin cambios en la longitud del texto), podemos aplicar este 'algoritmo' a cada parte de la ruta (excepto la parte de letra de unidad).

Hablar es fácil, aquí está el código:

#include <windows.h> 
#include <stdio.h> 

/* 
    c:\windows\windowsupdate.log --> c:\windows\WindowsUpdate.log 
*/ 
static HRESULT MyProcessLastPart(LPTSTR szPath) 
{ 
    HRESULT hr = 0; 
    HANDLE hFind = NULL; 
    WIN32_FIND_DATA fd = {0}; 
    TCHAR *p = NULL, *q = NULL; 
    /* thePart = GetCorrectCasingFileName(thePath); */ 
    hFind = FindFirstFile(szPath, &fd); 
    if (hFind == INVALID_HANDLE_VALUE) { 
     hr = HRESULT_FROM_WIN32(GetLastError()); 
     hFind = NULL; goto eof; 
    } 
    /* thePath = thePath.ReplaceLast(thePart); */ 
    for (p = szPath; *p; ++p); 
    for (q = fd.cFileName; *q; ++q, --p); 
    for (q = fd.cFileName; *p = *q; ++p, ++q); 
eof: 
    if (hFind) { FindClose(hFind); } 
    return hr; 
} 

/* 
    Important! 'szPath' should be absolute path only. 
    MUST NOT SPECIFY relative path or UNC or short file name. 
*/ 
EXTERN_C 
HRESULT __stdcall 
CorrectPathCasing(
    LPTSTR szPath) 
{ 
    HRESULT hr = 0; 
    TCHAR *p = NULL; 
    if (GetFileAttributes(szPath) == -1) { 
     hr = HRESULT_FROM_WIN32(GetLastError()); goto eof; 
    } 
    for (p = szPath; *p; ++p) 
    { 
     if (*p == '\\' || *p == '/') 
     { 
      TCHAR slashChar = *p; 
      if (p[-1] == ':') /* p[-2] is drive letter */ 
      { 
       p[-2] = toupper(p[-2]); 
       continue; 
      } 
      *p = '\0'; 
      hr = MyProcessLastPart(szPath); 
      *p = slashChar; 
      if (FAILED(hr)) goto eof; 
     } 
    } 
    hr = MyProcessLastPart(szPath); 
eof: 
    return hr; 
} 

int main() 
{ 
    TCHAR szPath[] = TEXT("c:\\windows\\EXPLORER.exe"); 
    HRESULT hr = CorrectPathCasing(szPath); 
    if (SUCCEEDED(hr)) 
    { 
     MessageBox(NULL, szPath, TEXT("Test"), MB_ICONINFORMATION); 
    } 
    return 0; 
} 

prove 2

Ventajas:

  • El código funciona en todas las versiones de Windows desde Windows 95.
  • de error básico -manejo.
  • Mayor rendimiento posible. FindFirstFile() es muy rápido, la manipulación directa del buffer lo hace aún más rápido.
  • Just C y WinAPI puro. Pequeño tamaño ejecutable.

Desventajas: se apoya

  • Sólo ruta absoluta, otros son un comportamiento indefinido.
  • No estoy seguro de si se basa en un comportamiento no documentado.
  • Es posible que el código sea demasiado crudo para muchas personas. Podría hacerte flamear.

razón detrás del estilo de código:

lo uso goto para control de errores, ya que estaba acostumbrado a ella (goto es muy útil para el manejo de errores en C).Utilizo el bucle for para realizar funciones como strcpy y strchr sobre la marcha porque quiero estar seguro de lo que realmente se ejecutó.

+0

En el código de producción, no se puede asumir que la longitud de la ruta no cambie, desafortunadamente. Casos como ingresar un archivo con múltiples separadores de ruta '\' consecutivos son totalmente compatibles en el sistema operativo, pero darían como resultado que el código devuelva una ruta incorrecta. –

Cuestiones relacionadas