.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 클래스 라이브러리에는 그러한 기능이 없습니다.

그러나 자신의 직접 출시 할 수 있습니다. 소문자 이름이있는 파일을 작성한 다음 Upparcase 버전의 이름으로 열어보십시오. 아마도이 방법을 개선 할 수 있지만 아이디어를 얻을 수 있습니다.

편집하다: 실제로 루트 디렉토리에서 첫 번째 파일을 가져간 다음 filename.tolower () 및 filename.toupper ()가 존재하는지 확인할 수 있습니다. 불행히도 동일한 파일의 대문자와 소문자 변형이 모두 존재할 수 있으므로 소문자와 대문자 변형의 FileInfo.Name 속성을 비교하여 실제로 동일인지 아닌지 확인해야합니다. 디스크에 글을 쓰는 것은 필요하지 않습니다.

분명히 볼륨에 파일이 전혀 없으면 실패합니다. 이 경우 첫 번째 옵션으로 돌아갑니다 (구현에 대한 Martin의 답변 참조).

케이싱 규칙이 다른 여러 파일 시스템이있을 수 있습니다. 예를 들어, 루트 파일 시스템은 대소 문자에 민감 할 수 있지만 어딘가에 사례에 민감한 파일 시스템 (예 : 뚱뚱한 파일 시스템이 포함 된 USB 스틱)을 가질 수 있습니다. 따라서 그러한 수표를 수행하는 경우 액세스 할 디렉토리에 확인하십시오.

또한 사용자가 사례에 민감한 파일 시스템에 대한 데이터를 복사하면 어떻게해야합니까? 경우에만 다른 파일이있는 경우 그 중 하나는 다른 파일을 덮어 쓰여 데이터 손실을 초래합니다. 다른 방향으로 복사 할 때, 예를 들어 파일 A에 "b"에 대한 참조가 포함되어 있지만 실제로 파일의 이름이 "b"라는 문제가 발생할 수 있습니다. 이것은 원래 사례에 민감한 파일 시스템에서 작동하지만 대소 문자에 민감한 시스템에는 작동하지 않습니다.

따라서 파일 시스템이 대체에 민감한 지 여부에 따라 피할 수 있습니다. 경우에만 다른 파일 이름을 생성하지 말고 표준 파일 선택기 대화 상자를 사용하고 케이스가 변경 될 수 있도록 준비하십시오.

모든 소문자에 임시 파일을 작성한 다음 대문자를 사용하여 존재하는지 확인하십시오.

.NET 함수는 아니지만 Windows API의 GetVolumeInformation 및 getVolumeInformationByHandlew 함수는 원하는 작업을 수행합니다 (YJE LPFILESYSTEMFLAGS 매개 변수 참조.

원래 질문을 해석하는 두 가지 방법이 실제로 있습니다.

  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 방법 또는 그 밖의. 그러나 제 생각에,이 인종 조건을 한 번 연속으로 한 번 만날 확률은 천문학적으로 작습니다.

나는 치트를 호출한다 :

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/mas

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top