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

StackOverflow https://stackoverflow.com/questions/430256

Вопрос

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

Это было полезно?

Решение

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

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

Другие советы

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

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

РЕДАКТИРОВАТЬ . На самом деле вы можете просто взять первый файл в корневом каталоге и затем проверить, существуют ли filename.ToLower () и filename.ToUpper (). К сожалению, вполне возможно, что прописные и строчные варианты одного и того же файла существуют, поэтому вы должны сравнить свойства FileInfo.Name как строчных, так и прописных вариантов, чтобы увидеть, действительно ли они одинаковы или нет. Это не потребует записи на диск.

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

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

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

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

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

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

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

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

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

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

    Код написан для .NET Framework 4.0 и C # 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's 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 , либо вне его. Однако, по моему мнению, вероятность столкновения с этим состоянием гонки один раз, не говоря уже два раза подряд, астрономически мала.

    Я призываю Чит:

    Path.DirectorySeparatorChar == '\\' ? "I'm insensitive" : "I'm probably sensitive"
    
    /// <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, со следующими изменениями:

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

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

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

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

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

    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, работает на всех .NET 4.0+ и регулярно обновляется: https://github.com/gapotchenko/Gapotchenko.FX/tree/master/Source/Gapotchenko.FX.IO#iscasesensitive

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top