Question

Est-ce que .net a un moyen de déterminer si le système de fichiers local est sensible à la casse?

Était-ce utile?

La solution

Vous pouvez créer un fichier dans le dossier temporaire (en utilisant le nom de fichier en minuscule), puis vérifier si le fichier existe (en utilisant le nom de fichier en majuscule), par exemple:

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

Autres conseils

Cette fonction n'existe pas dans la bibliothèque de classes .NET.

Vous pouvez toutefois déployer le vôtre: essayez de créer un fichier avec un nom en minuscule, puis essayez de l'ouvrir avec la version supérieure de son nom. Il est probablement possible d’améliorer cette méthode, mais vous en avez l’idée.

MODIFIER : vous pouvez simplement prendre le premier fichier du répertoire racine, puis vérifier si les deux fichiers filename.ToLower () et filename.ToUpper () existent. Malheureusement, il est fort possible qu'il existe à la fois des variantes majuscules et minuscules du même fichier. Vous devez donc comparer les propriétés FileInfo.Name des variantes minuscules et majuscules pour voir si elles sont effectivement identiques ou non. Cela ne nécessitera pas l'écriture sur le disque.

Évidemment, cela échouera s’il n’ya aucun fichier sur le volume. Dans ce cas, il suffit de revenir à la première option (voir la réponse de Martin pour la mise en œuvre).

N'oubliez pas que vous pouvez avoir plusieurs systèmes de fichiers avec des règles de casse différentes. Par exemple, le système de fichiers racine peut être sensible à la casse, mais vous pouvez avoir un système de fichiers ne respectant pas la casse (par exemple, une clé USB avec un système de fichiers FAT) monté quelque part. Donc, si vous faites de telles vérifications, assurez-vous de les placer dans le répertoire auquel vous allez accéder.

De même, que se passe-t-il si l'utilisateur copie les données d'un fichier sensible à la casse, par exemple, à un système de fichiers insensible à la casse? Si vous avez des fichiers ne différant que par cas, l’un d’eux écrasera l’autre, ce qui entraînera une perte de données. Lors de la copie dans l'autre sens, vous pouvez également rencontrer des problèmes, par exemple, si le fichier A contient une référence au fichier "b", mais que le fichier est en réalité nommé "B". Cela fonctionne sur le système de fichiers insensible à la casse d'origine, mais pas sur le système sensible à la casse.

Par conséquent, je vous suggérerais d'éviter si le système de fichiers est sensible à la casse ou non, si vous le pouvez. Ne générez pas de noms de fichiers ne différant que par cas, utilisez les boîtes de dialogue standard du sélecteur de fichiers, soyez prêt à faire en sorte que le cas puisse changer, etc.

Essayez de créer un fichier temporaire en minuscule, puis vérifiez s’il existe en majuscule.

Ce n'est pas une fonction .NET, mais les fonctions GetVolumeInformation et GetVolumeInformationByHandleW de l'API Windows feront ce que vous voulez (voir le paramètre yje lpFileSystemFlags.

Il existe en réalité deux façons d'interpréter la question initiale.

  1. Comment déterminer si un système de fichiers spécifique est capable de préserver la sensibilité à la casse dans les noms de fichier?
  2. Comment déterminer si le système d'exploitation actuel interprète les noms de fichier en respectant la casse lors de l'utilisation d'un système de fichiers spécifique.

Cette réponse est basée sur la deuxième interprétation, car je pense que c’est ce que le PO voulait savoir et ce qui compte pour la plupart des gens.

Le code suivant est vaguement basé sur la réponse de M4N et de Nicolas Raoul et tente de créer une implémentation vraiment robuste capable de déterminer si le système d'exploitation traite les noms de fichiers en respectant la casse dans un répertoire spécifié (à l'exclusion des sous-répertoires, car ceux-ci pourraient être montés à partir d'un autre système de fichiers).

Il fonctionne en créant deux nouveaux fichiers successivement, l’un avec des minuscules, l’autre avec des majuscules. Les fichiers sont exclusivement verrouillés et sont automatiquement supprimés à la fermeture. Cela devrait éviter les effets secondaires négatifs causés par la création de fichiers. Bien entendu, cette implémentation ne fonctionne que si le répertoire spécifié existe et que l'utilisateur actuel est capable de créer des fichiers à l'intérieur de celui-ci.

Le code est écrit pour .NET Framework 4.0 et C # 7.2 (ou version ultérieure).

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

Comme vous pouvez le constater, il existe une possibilité infime de condition de concurrence critique pouvant entraîner un faux négatif. Si vous vous inquiétez vraiment de cette situation critique, je vous suggère de faire la vérification une deuxième fois lorsque le résultat est faux, que ce soit dans la méthode IsFileSystemCaseSensitive ou en dehors de celle-ci. Cependant, à mon avis, la probabilité de rencontrer cette situation critique une fois, et encore moins deux fois de suite, est astronomique.

J'invoque le triche:

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

Selon la réponse de M4N, avec les modifications suivantes:

  • Noms statiques pour nous assurer qu'il contient une lettre et pas seulement des chiffres.
  • Peut-être plus lisible?
  • Enveloppé dans une méthode.
  • Documentation.

Une meilleure stratégie consisterait à prendre un chemin en argument et à créer le fichier sur le même système de fichiers, mais son écriture pourrait avoir des conséquences inattendues.

Qu'en est-il de cette heuristique?

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

Voici l'approche qui n'utilise pas de fichiers temporaires:

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

Au lieu de cela, il contient une connaissance enracinée sur les environnements d'exploitation.

Facilement disponible en tant que package NuGet, fonctionne sur tout .NET 4.0+ et est mis à jour régulièrement: https://github.com/gapotchenko/Gapotchenko.FX/tree/master/Source/Gapotchenko.FX.IO#iscasesensitive

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top