Подтвердить что ты не робот

Получение фактического имени файла (с соответствующим корпусом) в Windows

Файловая система Windows нечувствительна к регистру. Как, учитывая имя файла/папки (например, "somefile" ), я получаю фактическое имя этого файла/папки (например, он должен возвращать "SomeFile", если Explorer показывает это так)?

Некоторые способы я знаю, все они кажутся довольно обратными:

  • Учитывая полный путь, найдите каждую папку на пути (через FindFirstFile). Это дает правильные результаты для каждой папки. На последнем шаге выполните поиск самого файла.
  • Получить имя файла из дескриптора (как в пример MSDN). Это требует открытия файла, создания сопоставления файлов, получения его имени, разбора имен устройств и т.д. Довольно запутанный. И это не работает для папок или файлов с нулевым размером.

Я пропустил какой-то очевидный вызов WinAPI? Простейшие, такие как GetActualPathName() или GetFullPathName(), возвращают имя с помощью оболочки, которая была передана (например, возвращает "программные файлы", если она была передана, даже если она должна быть "Program Files" ).

Я ищу собственное решение (не .NET).

4b9b3361

Ответ 1

И тем самым я отвечаю на свой собственный вопрос, основываясь на исходном ответе от cspirz.

Здесь функция, которая задает абсолютный, относительный или сетевой путь, вернет путь с верхним/нижним регистром, который будет отображаться в Windows. Если какой-либо компонент пути не существует, он вернет переданный путь из этой точки.

Это довольно сложно, потому что он пытается обрабатывать сетевые пути и другие граничные случаи. Он работает с широкими символьными строками и использует std:: wstring. Да, в теории Unicode TCHAR может быть не такой, как wchar_t; это упражнение для читателя:)

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

Опять же, благодаря cspirz для указания на SHGetFileInfo.

Ответ 2

Существует другое решение. Сначала вызовите GetShortPathName(), а затем GetLongPathName(). Угадайте, какой случай персонажа будет использоваться тогда?; -)

Ответ 3

Вы пытались использовать SHGetFileInfo?

Ответ 4

Хорошо, это VBScript, но даже в этом случае я бы предложил использовать объект 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

Ответ, полученный из этого фрагмента,

testFILE.dAt

Надеюсь, что, по крайней мере, вы укажете в правильном направлении.

Ответ 5

EDIT: теперь я перечитал вопрос, кажется, что OP знает об этом решении и ищет другое решение.

Я обнаружил, что FindFirstFile() вернет правильное имя файла обложки (последняя часть пути) в fd.cFileName. Если мы передадим c:\winDOWs\exPLORER.exe в качестве первого параметра в FindFirstFile(), fd.cFileName будет explorer.exe следующим образом:

доказать

Если мы заменим последнюю часть пути на fd.cFileName, мы получим последнюю часть справа; путь станет c:\winDOWs\exPLORER.exe.

Предполагая, что путь всегда является абсолютным путем (без изменения длины текста), мы можем просто применить этот "алгоритм" к каждой части пути (кроме части буквы диска).

Обсуждение дешево, вот код:

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

доказать 2

Преимущества:

  • Код работает с каждой версией Windows с Windows 95.
  • Основная обработка ошибок.
  • Максимально возможная производительность. FindFirstFile() работает очень быстро, управление прямым буфером делает его еще быстрее.
  • Просто C и чистый WinAPI. Малый размер исполняемого файла.

Недостатки:

  • Поддерживается только абсолютный путь, другие - поведение undefined.
  • Не уверен, что он полагается на недокументированное поведение.
  • Код может быть слишком сырым слишком много для некоторых людей. Может быть, вы пламя.

Причина стиля кода:

Я использую goto для обработки ошибок, потому что я был к нему использован (goto очень удобен для обработки ошибок в C). Я использую цикл for для выполнения функций типа strcpy и strchr на лету, потому что я хочу быть уверенным, что было фактически выполнено.

Ответ 6

FindFirstFileNameW будет работать с несколькими недостатками:

  • он не работает по пути UNC
  • он линяет букву диска, поэтому вам нужно добавить ее обратно
  • Если существует более одной жесткой ссылки на ваш файл, вам нужно определить правильный.

Ответ 7

Насколько я знаю, свойство Name объекта System.IO.FileInfo вернет вам фактическое имя из Windows.

Ответ 8

После быстрого теста GetLongPathName() делает то, что вы хотите.

Ответ 9

Это трюк:

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

возвращает 'SomeFile'.

EDIT: Глупо меня, я искал то же самое в Python. Поэтому игнорируйте это для C/С++...