如何确定文件系统在.net中是否区分大小写?

时间:2009-01-10 00:30:13

标签: .net linux mono filesystems case-sensitive

.net是否有办法确定本地文件系统是否区分大小写?

10 个答案:

答案 0 :(得分:13)

您可以在临时文件夹中创建一个文件(使用小写文件名),然后检查文件是否存在(使用大写文件名),例如:

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

答案 1 :(得分:7)

.NET类库中没有这样的功能。

但是,您可以自己推出:尝试使用小写名称创建文件,然后尝试使用其名称的upparcase版本打开它。可能有可能改进这种方法,但你明白了。

编辑:您实际上只需要获取根目录中的第一个文件,然后检查filename.ToLower()和filename.ToUpper()是否都存在。不幸的是,很可能存在同一文件的大写和小写变体,因此您应该比较小写和大写变体的FileInfo.Name属性,看它们是否确实相同。这不需要写入磁盘。

显然,如果卷上根本没有文件,则会失败。在这种情况下,只需回到第一个选项(参见Martin对实现的回答)。

答案 2 :(得分:5)

请记住,您可能有多个具有不同大小写规则的文件系统。例如,根文件系统可能区分大小写,但您可以在某处安装不区分大小写的文件系统(例如,带有FAT文件系统的USB记忆棒)。因此,如果您进行此类检查,请确保将它们放在您要访问的目录中。

此外,如果用户将数据从区分大小写复制到不区分大小写的文件系统,该怎么办?如果您的文件仅按大小写不同,则其中一个文件将覆盖另一个文件,从而导致数据丢失。在向另一个方向复制时,您可能还会遇到问题,例如,如果文件A包含对文件“b”的引用,但该文件实际上名为“B”。这适用于原始不区分大小写的文件系统,但不适用于区分大小写的系统。

因此,如果可以,我建议您避免依赖于文件系统是否区分大小写。不要生成仅根据大小写不同的文件名,使用标准文件选择器对话框,准备案例可能会更改等等。

答案 3 :(得分:1)

尝试全部使用小写创建临时文件,然后使用大写字符检查它是否存在。

答案 4 :(得分:1)

它不是.NET函数,但Windows API中的GetVolumeInformation和GetVolumeInformationByHandleW函数可以执行您想要的操作(请参阅yje lpFileSystemFlags参数。

答案 5 :(得分:1)

实际上有两种解释原始问题的方法。

  1. 如何确定特定的文件系统是否能够保留文件名中的区分大小写?
  2. 在使用特定文件系统时,如何确定当前操作系统是否区分大小写来解释文件名。

此答案基于第二种解释,因为我认为这是OP想要了解的,并且对大多数人来说也很重要。

以下代码大致基于M4N和Nicolas Raoul的回答,并尝试创建一个真正可靠的实现,该实现可以确定操作系统是否在指定目录(不包括子目录)内处理区分大小写的文件名。这些可以从另一个文件系统挂载。

它的工作原理是依次创建两个新文件,一个文件使用小写字母,另一个文件使用大写字母。这些文件被排他地锁定,并且在关闭时会自动删除。这样可以避免由于创建文件而造成的任何负面影响。 当然,只有在指定目录存在并且当前用户能够在其中创建文件的情况下,此实现才有效。

该代码是为.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方法内部或外部进行第二次检查,如果结果为假。 但是,我认为,一次遇到这种竞赛条件的可能性(更不用说连续两次)在天文学上很小。

答案 6 :(得分:0)

/// <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的答案,进行以下更改:

  • 静态名称,以便我们确定它包含一个字母,而不仅仅是数字。
  • 也许更具可读性?
  • 包装方法。
  • 文档。

更好的策略是将路径作为参数,并在同一文件系统上创建文件,但在那里写文件可能会产生意想不到的后果。

答案 7 :(得分:0)

我引用了作弊:

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

答案 8 :(得分:0)

这种启发式怎么样?

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

答案 9 :(得分:0)

以下是不使用临时文件的方法:

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