Лучший способ определить, ссылаются ли два пути на один и тот же файл в Windows?

Как мне сравнить две строки, чтобы определить, относятся ли они к одному и тому же пути в Win32 с использованием C / C ++?

Хотя это будет обрабатывать множество случаев, но некоторые вещи упускаются:

_tcsicmp(szPath1, szPath2) == 0

Например:

  • косая черта / обратная косая черта

  • относительные / абсолютные пути.

[Edit] Заголовок изменен в соответствии с существующим вопросом C #.


person Adam Tegen    schedule 18.02.2009    source источник


Ответы (11)


Откройте оба файла с помощью CreateFile, вызовите GetFileInformationByHandle для обоих и сравните dwVolumeSerialNumber, nFileIndexLow, nFileIndexHigh. Если все три равны, они оба указывают на один и тот же файл:

GetFileInformationByHandle function

BY_HANDLE_FILE_INFORMATION Структура

person MSN    schedule 18.02.2009
comment
Файлы заметок должны оставаться открытыми, иначе может оказаться, что одинаковые числа будут соответствовать разным файлам. - person jfs; 15.03.2009
comment
В документации структуры BY_HANDLE_FILE_INFORMATION 2016 года говорится, что 64-битный идентификатор в этой структуре не гарантирует уникальности в ReFS. и Чтобы получить 128-битный идентификатор файла, используйте функцию GetFileInformationByHandleEx с FileIdInfo, чтобы получить структуру FILE_ID_INFO. Этот подход работает в Windows 8 и новее. Требуется опция компиляции -D_WIN32_WINNT = _WIN32_WINNT_WIN8. - person Bruno Haible; 09.12.2016
comment
Номера индексов (inodes) останутся неизменными даже после закрытия дескриптора в локальных файловых системах. Есть только одна проблема с сетевыми дисками. Samba имеет возможность предоставлять inodes, которые работают во время сеанса (перезапуск сервера). - person Lothar; 05.07.2019

См. Этот вопрос: Лучший способ чтобы определить, ссылаются ли два пути на один и тот же файл в C #

Вопрос касается C #, но ответ - это просто вызов Win32 API GetFileInformationByHandle.

person sth    schedule 18.02.2009
comment
В документации для GetFileInformationByHandle говорится: nFileIndexLow: младшая часть уникального идентификатора, связанного с файлом. Это значение полезно ТОЛЬКО КОГДА ФАЙЛ ОТКРЫТ хотя бы одним процессом. Если ни один из процессов не открыл его, индекс может измениться при следующем открытии файла. - person Integer Poet; 28.07.2010

используйте GetFullPathName из kernel32.dll, это даст вам абсолютный путь к файлу. Затем сравните его с другим путем, который у вас есть, используя простое сравнение строк.

изменить: код

TCHAR buffer1[1000];
TCHAR buffer2[1000];
TCHAR buffer3[1000];
TCHAR buffer4[1000];

GetFullPathName(TEXT("C:\\Temp\\..\\autoexec.bat"),1000,buffer1,NULL);
GetFullPathName(TEXT("C:\\autoexec.bat"),1000,buffer2,NULL);
GetFullPathName(TEXT("\\autoexec.bat"),1000,buffer3,NULL);
GetFullPathName(TEXT("C:/autoexec.bat"),1000,buffer4,NULL);
_tprintf(TEXT("Path1: %s\n"), buffer1);
_tprintf(TEXT("Path2: %s\n"), buffer2);
_tprintf(TEXT("Path3: %s\n"), buffer3);
_tprintf(TEXT("Path4: %s\n"), buffer4);

приведенный выше код будет печатать один и тот же путь для всех трех представлений пути .. после этого вы можете выполнить поиск без учета регистра

person user65157    schedule 18.02.2009
comment
Это не работает, потому что к одному и тому же файлу может быть более одного пути. - person JaredPar; 18.02.2009
comment
да, но GetFullPathName даст вам абсолютный путь к тому же файлу .. так что он должен быть таким же .. - person user65157; 18.02.2009
comment
Жесткие ссылки позволяют одному файлу иметь два разных имени. И да, те из вас, кто знает достаточно, чтобы представлять опасность для себя и других, Windows поддерживает жесткие ссылки. - person Integer Poet; 28.07.2010
comment
@IntegerPoet Пришел, чтобы сказать именно это. Жесткие ссылки - игровая площадка дьявола. Чтобы определить сходство, вам нужно больше, чем просто путь. - person Mike McMahon; 09.12.2014
comment
Это также, похоже, не учитывает каталоги с конечной косой чертой (например, c: \ blah vs c: \ blah). - person basiphobe; 06.07.2015
comment
Жесткие ссылки - величайшее изобретение со времен нарезанного хлеба. Только люди, которые не пишут программы, обрабатывающие жесткие ссылки, являются злом. Как Линус, который написал git без поддержки жестких ссылок. - person Lothar; 05.07.2019

Библиотека файловой системы

Начиная с C ++ 17 вы можете использовать стандартную библиотеку файловой системы. Включите его, используя #include <filesystem>. Вы можете получить к нему доступ даже в более старых версиях C ++, см. Сноску.

Ищете функцию equivalent в пространстве имен std::filesystem:

bool std::filesystem::equivalent(const std::filesystem::path& p1, const filesystem::path& p2 );

Подведем итог из документации: эта функция принимает два пути в качестве параметров и возвращает true если они ссылаются на один и тот же файл или каталог, в противном случае - false. Также существует noexcept перегрузка, которая принимает третий параметр: std::error_code, в котором сохраняется любая возможная ошибка.

Пример

#include <filesystem>
#include <iostream>
//...

int main() {
    std::filesystem::path p1 = ".";
    std::filesystem::path p2 = fs::current_path();
    std::cout << std::filesystem::equivalent(p1, p2);
    //...
}

Вывод:

1

Использование файловой системы до C ++ 17

Чтобы использовать эту библиотеку в версиях до C ++ 17, вам необходимо включить экспериментальные языковые функции в вашем компиляторе и включить библиотеку следующим образом: #include <experimental/filesystem>. Затем вы можете использовать его функции в пространстве имен std::experimental::filesystem. Обратите внимание, что экспериментальная библиотека файловой системы может отличаться от библиотеки C ++ 17. См. Документацию здесь.
Например:

#include <experimental/filesystem>
//...
std::experimental::filesystem::equivalent(p1, p2);
person Stypox    schedule 13.05.2018

Простого сравнения строк недостаточно для сравнения путей на равенство. В Windows c: \ foo \ bar.txt и c: \ temp \ bar.txt вполне могут указывать на один и тот же файл через символьные и жесткие ссылки в файловой системе.

Правильное сравнение путей по существу вынуждает вас открыть оба файла и сравнить низкоуровневую дескрипторную информацию. Любой другой метод будет иметь нестабильные результаты.

Ознакомьтесь с отличным постом Люциана на эту тему. Код написан на VB, но его вполне можно перевести на C / C ++, поскольку он PInvoke использовал большинство методов.

http://blogs.msdn.com/vbteam/archive/2008/09/22/to-compare-two-filenames-lucian-wischik.aspx

person JaredPar    schedule 18.02.2009

На основе ответов о GetFileInformationByHandle () здесь это код.

Примечание. Это сработает, только если файл уже существует ...

//Determine if 2 paths point ot the same file...
//Note: This only works if the file exists
static bool IsSameFile(LPCWSTR szPath1, LPCWSTR szPath2)
{
    //Validate the input
    _ASSERT(szPath1 != NULL);
    _ASSERT(szPath2 != NULL);

    //Get file handles
    HANDLE handle1 = ::CreateFileW(szPath1, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 
    HANDLE handle2 = ::CreateFileW(szPath2, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 

    bool bResult = false;

    //if we could open both paths...
    if (handle1 != INVALID_HANDLE_VALUE && handle2 != INVALID_HANDLE_VALUE)
    {
        BY_HANDLE_FILE_INFORMATION fileInfo1;
        BY_HANDLE_FILE_INFORMATION fileInfo2;
        if (::GetFileInformationByHandle(handle1, &fileInfo1) && ::GetFileInformationByHandle(handle2, &fileInfo2))
        {
            //the paths are the same if they refer to the same file (fileindex) on the same volume (volume serial number)
            bResult = fileInfo1.dwVolumeSerialNumber == fileInfo2.dwVolumeSerialNumber &&
                      fileInfo1.nFileIndexHigh == fileInfo2.nFileIndexHigh &&
                      fileInfo1.nFileIndexLow == fileInfo2.nFileIndexLow;
        }
    }

    //free the handles
    if (handle1 != INVALID_HANDLE_VALUE )
    {
        ::CloseHandle(handle1);
    }

    if (handle2 != INVALID_HANDLE_VALUE )
    {
        ::CloseHandle(handle2);
    }

    //return the result
    return bResult;
}
person Adam Tegen    schedule 18.02.2009
comment
В документации для GetFileInformationByHandle говорится: nFileIndexLow: младшая часть уникального идентификатора, связанного с файлом. Это значение полезно ТОЛЬКО КОГДА ФАЙЛ ОТКРЫТ хотя бы одним процессом. Если ни один из процессов не открыл его, индекс может измениться при следующем открытии файла. - person Integer Poet; 28.07.2010
comment
@IntegerPoet: А ваша точка зрения? Вы даже не можете вызвать GetFileInformationByHandle, если файл не открыт, и этот код не закрывает ни один дескриптор между вызовами. - person Ben Voigt; 31.12.2011
comment
Хороший вопрос. Я написал этот комментарий так давно, что уже не помню, что имел в виду. - person Integer Poet; 04.01.2012
comment
Должно быть, я имел в виду, что возвращаемое значение IsSameFile (bResult) устаревает даже до того, как возвращается IsSameFile. - person Integer Poet; 09.02.2012
comment
В документации структуры BY_HANDLE_FILE_INFORMATION 2016 года говорится, что 64-битный идентификатор в этой структуре не гарантирует уникальности в ReFS. и Чтобы получить 128-битный идентификатор файла, используйте функцию GetFileInformationByHandleEx с FileIdInfo, чтобы получить структуру FILE_ID_INFO. Этот подход работает в Windows 8 и новее. Требуется опция компиляции -D_WIN32_WINNT = _WIN32_WINNT_WIN8. - person Bruno Haible; 09.12.2016

Если у вас есть доступ к библиотекам Boost, попробуйте

bool boost::filesystem::path::equivalent( const path& p1, const path& p2 )

http://www.boost.org/doc/libs/1_53_0/libs/filesystem/doc/reference.html#equivalent

Подводя итог из документации: возвращает true, если данные path объекты разрешаются в один и тот же объект файловой системы, иначе false.

person aldo    schedule 21.05.2013
comment
В документации по ссылке выше указано, что они делают в режиме ускорения. Они сравнивают результаты stat (), который довольно легко сделать самому. В отличие от большинства других решений, он не зависит от платформы. - person Johannes Jendersie; 18.02.2014
comment
Истинный. Можно найти исходный код и использовать его как руководство для самостоятельной реализации этого небольшого фрагмента. - person aldo; 19.02.2014

Что вам нужно сделать, так это получить канонический путь.

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

Затем сравните канонический путь или уникальный идентификатор.

Примечание. Не пытайтесь самостоятельно вычислить конический путь. Файловая система может делать вещи с символическими ссылками и т. Д., Которые нелегко отследить, если вы не очень знакомы с файловой системой.

person Martin York    schedule 18.02.2009

Сравнение строк фактического пути не даст точных результатов, если вы ссылаетесь на UNC или канонические пути (то есть на что-либо, кроме локального пути).

shlwapi.h имеет несколько функций пути, которые могут быть полезным для определения, совпадают ли ваши пути.

Он содержит такие функции, как PathIsRoot, которые можно использовать в функции большего размаха.

person user62572    schedule 18.02.2009

Если файлы существуют, и вы можете справиться с потенциальным состоянием гонки и снижением производительности при открытии файлов, несовершенное решение, которое должно работать на любой платформе, состоит в том, чтобы открыть один файл для записи отдельно, закрыть его, а затем снова открыть для записи после открытия другого файла для записи. Поскольку доступ на запись должен быть только монопольным, если вы смогли открыть первый файл для записи в первый раз, но не во второй раз, скорее всего, вы заблокировали свой собственный запрос, когда пытались открыть оба файла.

(есть вероятность, что в какой-то другой части системы один из ваших файлов открыт)

person Matthew    schedule 04.01.2010

Откройте оба файла и используйте GetFinalPathNameByHandle() против HANDLEs. Затем сравните пути.

person CodeAngry    schedule 15.03.2014