Pergunta

O .net tem uma maneira de determinar se o sistema de arquivos local é sensível a maiúsculas?

Foi útil?

Solução

Você pode criar um arquivo na pasta temp (usando letras minúsculas filename), em seguida, verifique se o arquivo existe (usando nome de arquivo em maiúsculas), por exemplo:

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

Outras dicas

Não há nenhuma função tal na Biblioteca de classes .NET.

Você pode, no entanto, rolo a sua própria: Tente criar um arquivo com um nome em minúsculas e tente abri-lo com a versão upparcase de seu nome. Provavelmente, é possível melhorar este método, mas você começa a idéia.

Editar : Você realmente pode apenas tomar o primeiro arquivo no diretório raiz e em seguida, verifique se ambos filename.ToLower () e filename.ToUpper () existir. Infelizmente, é bem possível que tanto maiúsculas e minúsculas variantes do mesmo exist arquivo, então você deve comparar as propriedades FileInfo.Name de ambos os maiúsculas e minúsculas variantes para ver se eles são de fato o mesmo ou não. Isso não vai exigir escrita para o disco.

Obviamente, isso vai falhar se não há arquivos em tudo no volume. Neste caso, apenas cair de volta para a primeira opção (ver resposta de Martin para a implementação).

Tenha em mente que você pode ter vários sistemas de arquivos com diferentes regras de revestimento. Por exemplo, o sistema de arquivos raiz poderia ser maiúsculas de minúsculas, mas você pode ter um sistema de arquivos de maiúsculas e minúsculas (por exemplo, um stick USB com um sistema de ficheiros FAT) montado em algum lugar. Então, se você faz essas verificações, certifique-se de que você fazê-los no diretório que você está indo para o acesso.

Além disso, o que se o usuário copia os dados de dizer a maiúsculas e minúsculas para um sistema de arquivos case-insensitive? Se você tiver arquivos que diferem apenas por caso, um deles irá substituir o outro, causando perda de dados. Ao copiar em outra direção, você também pode ter problemas, por exemplo, se o arquivo A contém uma referência ao arquivo "b", mas o arquivo é na verdade o nome "B". Isso funciona no sistema de arquivos case-insensitive original, mas não no sistema maiúsculas de minúsculas.

Assim, eu sugiro que você evite dependendo se o sistema de arquivos é case-sensitive ou não, se puder. Não gerar nomes de arquivos que diferem apenas por caso, use as caixas de diálogo seletor de arquivos padrão, estar preparado que o caso pode mudar, etc.

Tente criar um arquivo temporário em todas as letras minúsculas, e em seguida, verificar se ela existir usando letras maiúsculas.

Não é uma função .NET, mas as funções GetVolumeInformation e GetVolumeInformationByHandleW da API do Windows irá fazer o que quiser (veja YJE lpFileSystemFlags parâmetro.

Na verdade, existem duas maneiras de interpretar a pergunta original.

  1. Como determinar se um sistema de arquivo específico é capaz de preservar a maiúsculas e minúsculas em nomes de arquivo?
  2. Como determinar se os nomes de arquivos interpreta sistema operacional atual caso-sensível quando se trabalha com um sistema de arquivo específico.

Esta resposta baseia-se na segunda interpretação, porque eu acho que é isso que o OP queria saber e também o que importa para a maioria das pessoas.

O código a seguir é vagamente baseado na resposta e as tentativas de de M4N e Nicolas Raoul para criar uma implementação muito robusto que é capaz de determinar se as alças de sistema operacional nomes de arquivos dentro de maiúsculas e minúsculas de um diretório especificado (sem os subdiretórios, desde estes poderiam ser montado a partir de outro sistema de arquivos).

Ele funciona através da criação de dois novos arquivos em sucessão, uma com letras minúsculas, o outro com letras maiúsculas. Os arquivos são bloqueados exclusivamente e são excluídos automaticamente quando fechado. Isso deve evitar quaisquer efeitos colaterais negativos causados ??pela criação de arquivos. Claro, esta implementação só funciona se existe o diretório especificado e o usuário atual é capaz de criar arquivos dentro do mesmo.

O código é escrito para .NET Framework 4.0 e C # 7.2 (ou posterior).

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);
}

Como você pode ver, há uma pequena possibilidade de uma condição de corrida que pode causar um falso-negativo. Se essa condição de corrida é algo que você realmente se preocupar eu sugiro que você faça a verificação de uma segunda vez quando o resultado é falso, tanto dentro do método IsFileSystemCaseSensitive ou fora dela. No entanto, na minha opinião, a probabilidade de encontrar essa condição de corrida uma vez, muito menos duas vezes seguidas, é astronomicamente pequenas.

invoco a Fraude:

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;
}

Com base na resposta de M4N, com as seguintes alterações:

  • nomes estáticos de modo que temos a certeza que contém uma carta e não apenas números.
  • Talvez mais legível?
  • Envolto em um método.
  • Documentação.

Uma estratégia melhor seria a de tomar um caminho como argumento, e criar o arquivo no mesmo sistema de arquivos, mas escrever não pode ter consequências inesperadas.

Como sobre esta heurística?

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

Aqui é a abordagem que não usa arquivos temporários:

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;
    }
}

Em vez disso, ele contém um conhecimento enraizado sobre ambientes operacionais.

Prontamente disponível como um pacote NuGet, trabalha em tudo .NET 4.0+ e atualizado em uma base regular: https://github.com/gapotchenko/Gapotchenko.FX/tree/master/Source/Gapotchenko.FX.IO#iscasesensitive

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top