Как определить, является ли файловая система чувствительной к регистру в .net?

Имеет ли .net способ определить, является ли локальная файловая система чувствительной к регистру?

Ответ 1

Вы можете создать файл в папке temp (используя строчное имя файла), а затем проверить, существует ли файл (с использованием имени в верхнем регистре), например:

string file = Path.GetTempPath() + Guid.NewGuid().ToString().ToLower();
File.CreateText(file).Close();
bool isCaseInsensitive = File.Exists(file.ToUpper());
File.Delete(file);

Ответ 2

В библиотеке классов .NET нет такой функции.

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

EDIT. Фактически вы можете просто взять первый файл в корневой каталог, а затем проверить, существуют ли как filename.ToLower(), так и filename.ToUpper(). К сожалению, вполне возможно, что существуют как прописные, так и строчные варианты одного и того же файла, поэтому вы должны сравнить свойства FileInfo.Name как в нижнем, так и в верхнем регистре, чтобы увидеть, действительно ли они одинаковы или нет. Это не требует записи на диск.

Очевидно, что это не сработает, если на томе нет файлов. В этом случае просто вернитесь к первому варианту (см. Ответ Martin для реализации).

Ответ 3

Имейте в виду, что у вас может быть несколько файловых систем с разными правилами обсадной колонны. Например, корневая файловая система может быть чувствительной к регистру, но вы можете иметь файловую систему без учета регистра (например, USB-накопитель с файловой системой FAT на нем). Поэтому, если вы делаете такие проверки, убедитесь, что вы делаете их в каталоге, к которому вы собираетесь получить доступ.

Кроме того, что, если пользователь копирует данные, скажем, с учетом регистра на файловую систему без регистра? Если у вас есть файлы, которые отличаются только случаем, один из них перезапишет другой, что приведет к потере данных. При копировании в другом направлении вы также можете столкнуться с проблемами, например, если файл A содержит ссылку на файл "b", но файл на самом деле называется "B". Это работает с исходной файловой системой без регистра, но не на чувствительной к регистру системе.

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

Ответ 4

Попробуйте создать временный файл во всех строчных строках, а затем проверьте, существует ли он в верхнем регистре.

Ответ 5

Это не функция .NET, но функции GetVolumeInformation и GetVolumeInformationByHandleW из Windows API будут делать то, что вы хотите (см. параметр yje lpFileSystemFlags.

Ответ 6

На самом деле есть два способа интерпретации исходного вопроса.

  1. Как определить, способна ли конкретная файловая система сохранять чувствительность к регистру в именах файлов?
  2. Как определить, интерпретирует ли текущая операционная система имена файлов с учетом регистра при работе с определенной файловой системой.

Этот ответ основан на втором толковании, потому что я думаю, что именно об этом хотел знать ФП, а также то, что важно для большинства людей.

Следующий код свободно основан на ответах M4N и Николаса Рауля и пытается создать действительно надежную реализацию, способную определить, обрабатывает ли операционная система имена файлов с учетом регистра в указанном каталоге (исключая подкаталоги, поскольку они могут быть монтируется из другой файловой системы).

Он работает, создавая два новых файла подряд, один с прописными буквами, другой с заглавными буквами. Файлы заблокированы исключительно и удаляются автоматически при закрытии. Это должно предотвратить любые негативные побочные эффекты, вызванные созданием файлов. Конечно, эта реализация работает, только если указанный каталог существует и текущий пользователь может создавать файлы внутри него.

Код написан для .NET Framework 4.0 и С# 7.2 (или более поздней версии).

using System;
using System.IO;
using System.Reflection;

/// <summary>
/// Check whether the operating system handles file names case-sensitive in the specified directory.
/// </summary>
/// <param name="directoryPath">The path to the directory to check.</param>
/// <returns>A value indicating whether the operating system handles file names case-sensitive in the specified directory.</returns>
/// <exception cref="ArgumentNullException"><paramref name="directoryPath"/> is null.</exception>
/// <exception cref="ArgumentException"><paramref name="directoryPath"/> contains one or more invalid characters.</exception>
/// <exception cref="DirectoryNotFoundException">The specified directory does not exist.</exception>
/// <exception cref="UnauthorizedAccessException">The current user has no write permission to the specified directory.</exception>
private static bool IsFileSystemCaseSensitive(string directoryPath)
{
    if (directoryPath == null)
    {
        throw new ArgumentNullException(nameof(directoryPath));
    }

    while (true)
    {
        string fileNameLower = ".cstest." + Guid.NewGuid().ToString();
        string fileNameUpper = fileNameLower.ToUpperInvariant();

        string filePathLower = Path.Combine(directoryPath, fileNameLower);
        string filePathUpper = Path.Combine(directoryPath, fileNameUpper);

        FileStream fileStreamLower = null;
        FileStream fileStreamUpper = null;
        try
        {
            try
            {
                // Try to create filePathUpper to ensure a unique non-existing file.
                fileStreamUpper = new FileStream(filePathUpper, FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize: 4096, FileOptions.DeleteOnClose);

                // After ensuring that it didn't exist before, filePathUpper must be closed/deleted again to ensure correct opening of filePathLower, regardless of the case-sensitivity of the file system.
                // On case-sensitive file systems there is a tiny chance for a race condition, where another process could create filePathUpper between closing/deleting it here and newly creating it after filePathLower.
                // This method would then incorrectly indicate a case-insensitive file system.
                fileStreamUpper.Dispose();
            }
            catch (IOException ioException) when (IsErrorFileExists(ioException))
            {
                // filePathUpper already exists, try another file name
                continue;
            }

            try
            {
                fileStreamLower = new FileStream(filePathLower, FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize: 4096, FileOptions.DeleteOnClose);
            }
            catch (IOException ioException) when (IsErrorFileExists(ioException))
            {
                // filePathLower already exists, try another file name
                continue;
            }

            try
            {
                fileStreamUpper = new FileStream(filePathUpper, FileMode.CreateNew, FileAccess.Write, FileShare.None, bufferSize: 4096, FileOptions.DeleteOnClose);

                // filePathUpper does not exist, this indicates case-sensitivity
                return true;
            }
            catch (IOException ioException) when (IsErrorFileExists(ioException))
            {
                // fileNameUpper already exists, this indicates case-insensitivity
                return false;
            }
        }
        finally
        {
            fileStreamLower?.Dispose();
            fileStreamUpper?.Dispose();
        }
    }
}

/// <summary>
/// Determines whether the specified <see cref="IOException"/> indicates a "file exists" error.
/// </summary>
/// <param name="ioException">The <see cref="IOException"/> to check.</param>
/// <returns>A value indicating whether the specified <see cref="IOException"/> indicates a "file exists" error.</returns>
private static bool IsErrorFileExists(IOException ioException)
{
    // https://referencesource.microsoft.com/mscorlib/microsoft/win32/win32native.cs.html#dd35d7f626262141
    const int ERROR_FILE_EXISTS = 0x50;

    // The Exception.HResult property get accessor is protected before .NET 4.5, need to get its value via reflection.
    int hresult = (int)typeof(Exception)
        .GetProperty("HResult", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
        .GetValue(ioException, index: null);

    // https://referencesource.microsoft.com/mscorlib/microsoft/win32/win32native.cs.html#9f6ca3226ff8f9ba
    return hresult == unchecked((int)0x80070000 | ERROR_FILE_EXISTS);
}

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

Ответ 7

/// <summary>
/// Check whether the operating system is case-sensitive.
/// For instance on Linux you can have two files/folders called
//// "test" and "TEST", but on Windows the two can not coexist.
/// This method does not extend to mounted filesystems, which might have different properties.
/// </summary>
/// <returns>true if the operating system is case-sensitive</returns>
public static bool IsFileSystemCaseSensitive()
{
    // Actually try.
    string file = Path.GetTempPath() + Guid.NewGuid().ToString().ToLower() + "test";
    File.CreateText(file).Close();
    bool result = ! File.Exists(file.ToUpper());
    File.Delete(file);

    return result;
}

На основании ответа M4N со следующими изменениями:

  • Статические имена, так что мы уверены, что он содержит букву, а не только цифры.
  • Может быть, более читабельным?
  • Завернут в метод.
  • Документация.

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

Ответ 8

Я призываю The Cheat:

Path.DirectorySeparatorChar == '\\' ? "I'm insensitive" : "I'm probably sensitive"

Ответ 9

Как насчет этого эвристического?

public static bool IsCaseSensitiveFileSystem() {
   var tmp = Path.GetTempPath();
   return !Directory.Exists(tmp.ToUpper()) || !Directory.Exists(tmp.ToLower());
}

Ответ 10

Вот подход, который не использует временные файлы:

using System;
using System.Runtime.InteropServices;

static bool IsCaseSensitive()
{
    if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ||
        RuntimeInformation.IsOSPlatform(OSPlatform.OSX))  // HFS+ (the Mac file-system) is usually configured to be case insensitive.
    {
        return false;
    }
    else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
    {
        return true;
    }
    else if (Environment.OSVersion.Platform == PlatformID.Unix)
    {
        return true;
    }
    else
    {
       // A default.
       return false;
    }
}

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

Доступно в виде пакета NuGet и регулярно обновляется: https://github.com/gapotchenko/Gapotchenko.FX/tree/master/Source/Gapotchenko.FX.IO#iscasesensitive