.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プロパティを比較して、それらが実際に同じかどうかを確認する必要があります。これにはディスクへの書き込みは必要ありません。

明らかに、ボリュームにファイルがまったくない場合、これは失敗します。この場合、最初のオプションに戻ってください(実装についてはMartinの回答を参照してください)。

大文字と小文字が異なる複数のファイルシステムが存在する可能性があることに注意してください。たとえば、ルートファイルシステムでは大文字と小文字が区別されますが、大文字と小文字を区別しないファイルシステム(たとえば、FATファイルシステムを搭載したUSBスティック)をどこかにマウントすることができます。そのため、このようなチェックを行う場合は、アクセスするディレクトリにそれらを作成してください。

また、ユーザーが大文字と小文字を区別しないファイルシステムから大文字と小文字を区別しないファイルシステムにデータをコピーするとどうなりますか?大文字と小文字のみが異なるファイルがある場合、一方が他方を上書きし、データが失われます。他の方向にコピーする場合、たとえば、ファイルAにファイル「b」への参照が含まれているが、実際にはファイル名が「B」である場合、問題が発生する可能性があります。これは、大文字と小文字を区別しない元のファイルシステムでは機能しますが、大文字と小文字を区別するシステムでは機能しません。

したがって、可能であれば、ファイルシステムが大文字と小文字を区別するかどうかに依存しないようにすることをお勧めします。大文字と小文字のみが異なるファイル名を生成したり、標準のファイル選択ダイアログを使用したり、大文字と小文字が変わる可能性があることを準備したりしてください。

すべて小文字で一時ファイルを作成し、大文字を使用して存在するかどうかを確認してください。

.NET関数ではありませんが、Windows APIのGetVolumeInformationおよびGetVolumeInformationByHandleW関数は、必要な処理を行います(yje lpFileSystemFlagsパラメーターを参照してください。

元の質問を解釈するには、実際には2つの方法があります。

  1. 特定のファイルシステムがファイル名の大文字と小文字を区別できるかどうかを判断する方法
  2. 特定のファイルシステムで作業しているときに、現在のオペレーティングシステムが大文字と小文字を区別してファイル名を解釈するかどうかを判断する方法。

この答えは2番目の解釈に基づいています。OPが知りたいことであり、ほとんどの人にとって重要なことだからです。

次のコードは、M4NとNicolas Raoulの回答に大まかに基づいており、指定されたディレクトリ(サブディレクトリを除く、これらは別のファイルシステムからマウントできます)。

2つの新しいファイルを連続して作成することで機能します。1つは小文字で、もう1つは大文字です。ファイルは排他的にロックされ、閉じると自動的に削除されます。これにより、ファイルの作成による悪影響が回避されます。 もちろん、この実装は、指定されたディレクトリが存在し、現在のユーザーがその中にファイルを作成できる場合にのみ機能します。

コードは、.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 メソッドの内部または外部で、結果がfalseのときにもう一度チェックすることをお勧めします。 しかし、私の意見では、この競合状態に一度遭遇する確率は、ましてや2回続けて言うと、天文学的には小さいです。

チートを呼び出す:

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