Pregunta

¿Tiene .net una manera de determinar si el sistema de archivos local distingue entre mayúsculas y minúsculas?

¿Fue útil?

Solución

Puede crear un archivo en la carpeta temporal (usando un nombre de archivo en minúscula), luego verifique si el archivo existe (usando un nombre de archivo en mayúscula), por ejemplo:

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

Otros consejos

No existe tal función en la Biblioteca de clases .NET.

Sin embargo, puede implementar el suyo propio: intente crear un archivo con un nombre en minúscula y luego intente abrirlo con la versión en mayúsculas de su nombre. Probablemente sea posible mejorar este método, pero se entiende la idea.

EDITAR : En realidad, podría tomar el primer archivo en el directorio raíz y luego verificar si existen filename.ToLower () y filename.ToUpper (). Desafortunadamente, es muy posible que existan variantes en mayúsculas y minúsculas del mismo archivo, por lo que debe comparar las propiedades FileInfo.Name de las variantes en minúsculas y mayúsculas para ver si realmente son iguales o no. Esto no requerirá escribir en el disco.

Obviamente, esto fallará si no hay ningún archivo en el volumen. En este caso, simplemente recurra a la primera opción (consulte la respuesta de Martin para la implementación).

Tenga en cuenta que puede tener múltiples sistemas de archivos con diferentes reglas de carcasa. Por ejemplo, el sistema de archivos raíz podría distinguir entre mayúsculas y minúsculas, pero puede tener un sistema de archivos que no distinga entre mayúsculas y minúsculas (por ejemplo, una memoria USB con un sistema de archivos FAT) montado en algún lugar. Entonces, si realiza tales comprobaciones, asegúrese de realizarlas en el directorio al que va a acceder.

Además, ¿qué sucede si el usuario copia los datos de un sistema de archivos sensible a mayúsculas y minúsculas? Si tiene archivos que difieren solo por caso, uno de ellos sobrescribirá al otro, causando pérdida de datos. Al copiar en la otra dirección, también puede tener problemas, por ejemplo, si el archivo A contiene una referencia al archivo '' b '', pero el archivo en realidad se llama '' B ''. Esto funciona en el sistema de archivos original sin distinción entre mayúsculas y minúsculas, pero no en el sistema que distingue entre mayúsculas y minúsculas.

Por lo tanto, sugeriría que evite depender de si el sistema de archivos distingue entre mayúsculas y minúsculas o no, si puede. No genere nombres de archivo que difieran solo según el caso, use los cuadros de diálogo de selección de archivos estándar, esté preparado para que el caso pueda cambiar, etc.

Intente crear un archivo temporal en minúsculas y luego verifique si existe en mayúsculas.

No es una función .NET, pero las funciones GetVolumeInformation y GetVolumeInformationByHandleW de la API de Windows harán lo que desee (consulte el parámetro yje lpFileSystemFlags.

En realidad, hay dos formas de interpretar la pregunta original.

  1. ¿Cómo determinar si un sistema de archivos específico puede preservar mayúsculas y minúsculas en los nombres de archivos?
  2. Cómo determinar si el sistema operativo actual interpreta los nombres de archivo con distinción entre mayúsculas y minúsculas cuando se trabaja con un sistema de archivos específico.

Esta respuesta se basa en la segunda interpretación, porque creo que eso es lo que el OP quería saber y también lo que le importa a la mayoría de las personas.

El siguiente código se basa libremente en la respuesta de M4N y Nicolas Raoul e intenta crear una implementación realmente robusta que sea capaz de determinar si el sistema operativo maneja los nombres de archivo entre mayúsculas y minúsculas dentro de un directorio específico (excluyendo subdirectorios, ya que estos podrían montarse desde otro sistema de archivos).

Funciona creando dos nuevos archivos en sucesión, uno con minúsculas y otro con mayúsculas. Los archivos se bloquean exclusivamente y se eliminan automáticamente cuando se cierran. Esto debería evitar cualquier efecto secundario negativo causado por la creación de archivos. Por supuesto, esta implementación solo funciona si el directorio especificado existe y el usuario actual puede crear archivos dentro de él.

El código está escrito para .NET Framework 4.0 y C # 7.2 (o 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 puede ver, existe una pequeña posibilidad de una condición de carrera que puede causar un falso negativo. Si esta condición de carrera es algo de lo que realmente se preocupa, le sugiero que realice la verificación por segunda vez cuando el resultado sea falso, ya sea dentro del método IsFileSystemCaseSensitive o fuera de él. Sin embargo, en mi opinión, la probabilidad de encontrar esta condición de carrera una vez, y mucho menos dos veces seguidas, es astronómicamente pequeña.

Invoco The Cheat:

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

Basado en la respuesta de M4N, con los siguientes cambios:

  • Nombres estáticos para asegurarnos de que contiene una letra y no solo números.
  • ¿Quizás más legible?
  • Envuelto en un método.
  • Documentación.

Una mejor estrategia sería tomar una ruta como argumento y crear el archivo en el mismo sistema de archivos, pero escribir allí podría tener consecuencias inesperadas.

¿Qué tal esta heurística?

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

Aquí está el enfoque que no utiliza archivos temporales:

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

En cambio, contiene un conocimiento arraigado sobre los entornos operativos.

Fácilmente disponible como paquete NuGet, funciona en todo .NET 4.0+ y se actualiza regularmente: https://github.com/gapotchenko/Gapotchenko.FX/tree/master/Source/Gapotchenko.FX.IO#iscasesensitive

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top