web-dev-qa-db-fra.com

Comment supprimer les caractères illégaux des chemins et des noms de fichiers?

J'ai besoin d'un moyen simple et robuste pour supprimer les chemins d'accès illicites et les caractères de fichier d'une simple chaîne. J'ai utilisé le code ci-dessous mais il ne semble rien faire. Qu'est-ce qui me manque?

using System;
using System.IO;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string illegal = "\"M<>\"\\a/ry/ h**ad:>> a\\/:*?\"<>| li*tt|le|| la\"mb.?";

            illegal = illegal.Trim(Path.GetInvalidFileNameChars());
            illegal = illegal.Trim(Path.GetInvalidPathChars());

            Console.WriteLine(illegal);
            Console.ReadLine();
        }
    }
}
383
Gary Willoughby

Essayez quelque chose comme ça à la place;

string illegal = "\"M\"\\a/ry/ h**ad:>> a\\/:*?\"| li*tt|le|| la\"mb.?";
string invalid = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());

foreach (char c in invalid)
{
    illegal = illegal.Replace(c.ToString(), ""); 
}

Mais je suis d'accord avec les commentaires, j'essaierais probablement de traiter la source des chemins illégaux, plutôt que d'essayer de transformer un chemin illégal en un chemin légitime mais probablement non intentionnel.

Edit: Ou une solution potentiellement «meilleure», en utilisant Regex.

string illegal = "\"M\"\\a/ry/ h**ad:>> a\\/:*?\"| li*tt|le|| la\"mb.?";
string regexSearch = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
Regex r = new Regex(string.Format("[{0}]", Regex.Escape(regexSearch)));
illegal = r.Replace(illegal, "");

Pourtant, la question ne demande pas pourquoi on le fait en premier lieu.

448
Matthew Scharley
public string GetSafeFilename(string filename)
{

    return string.Join("_", filename.Split(Path.GetInvalidFileNameChars()));

}

Cette réponse était sur un autre fil de Ceres , je l'aime vraiment propre et simple.

234
Shehab Fawzy

J'utilise Linq pour nettoyer les noms de fichiers. Vous pouvez facilement l'étendre pour vérifier également les chemins valides.

private static string CleanFileName(string fileName)
{
    return Path.GetInvalidFileNameChars().Aggregate(fileName, (current, c) => current.Replace(c.ToString(), string.Empty));
}

Mettre à jour

Certains commentaires indiquent que cette méthode ne fonctionne pas pour eux et j'ai donc inclus un lien vers un extrait de code DotNetFiddle afin que vous puissiez valider la méthode. 

https://dotnetfiddle.net/nw1SWY

203
Michael Minton

Vous pouvez supprimer les caractères illégaux en utilisant Linq comme ceci:

var invalidChars = Path.GetInvalidFileNameChars();

var invalidCharsRemoved = stringWithInvalidChars
.Where(x => !invalidChars.Contains(x))
.ToArray();

MODIFIER
Voici à quoi ça ressemble avec l'édition requise mentionnée dans les commentaires:

var invalidChars = Path.GetInvalidFileNameChars();

string invalidCharsRemoved = new string(stringWithInvalidChars
  .Where(x => !invalidChars.Contains(x))
  .ToArray());
84
Gregor Slavec

Ce sont toutes d'excellentes solutions, mais elles reposent toutes sur Path.GetInvalidFileNameChars, qui peut ne pas être aussi fiable que vous le pensez. Notez la remarque suivante dans la documentation MSDN sur Path.GetInvalidFileNameChars :

Le tableau renvoyé par cette méthode ne garantit pas que contiendra le jeu complet de caractères non valides dans les noms de fichier et de répertoire. L'ensemble des caractères non valides peut varier selon le système de fichiers. Par exemple, sur les plates-formes de bureau Windows, les chemins d'accès non valides peuvent inclure les caractères ASCII/Unicode 1 à 31, ainsi que quote ("), inférieur à (<), supérieur à (>), pipe (|), retour\b), null (\ 0) et tabulation (\ t).

Ce n'est pas mieux avec Path.GetInvalidPathChars . Il contient exactement la même remarque.

26
René

Pour les débutants, Trim supprime uniquement les caractères du début ou de la fin de la chaîne . Deuxièmement, vous devez évaluer si vous voulez vraiment supprimer les caractères offensants ou si vous échouez rapidement et dites à l'utilisateur que leur nom de fichier est invalide. Mon choix est le dernier, mais ma réponse devrait au moins vous montrer comment faire les choses correctement ET dans le mauvais sens:

Question StackOverflow montrant comment vérifier si une chaîne donnée est un nom de fichier valide . Notez que vous pouvez utiliser l'expression régulière de cette question pour supprimer les caractères remplacés par une expression régulière (si vous en avez vraiment besoin).

18
user7116

Pour les noms de fichiers:

string cleanFileName = String.Join("", fileName.Split(Path.GetInvalidFileNameChars()));

Pour les chemins complets:

string cleanPath = String.Join("", path.Split(Path.GetInvalidPathChars()));
16
Lily Finley

J'utilise des expressions régulières pour y parvenir. Tout d'abord, je construis dynamiquement la regex.

string regex = string.Format(
                   "[{0}]",
                   Regex.Escape(new string(Path.GetInvalidFileNameChars())));
Regex removeInvalidChars = new Regex(regex, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.CultureInvariant);

Ensuite, je viens d'appeler removeInvalidChars.Replace pour rechercher et remplacer. Cela peut évidemment être étendu pour couvrir également les caractères de chemin.

15
Jeff Yates

Le meilleur moyen de supprimer les caractères illégaux de l'entrée utilisateur est de remplacer les caractères illégaux à l'aide de la classe Regex, à créer une méthode dans le code derrière ou à valider côté client à l'aide du contrôle RegularExpression.

public string RemoveSpecialCharacters(string str)
{
    return Regex.Replace(str, "[^a-zA-Z0-9_]+", "_", RegexOptions.Compiled);
}

OU

<asp:RegularExpressionValidator ID="regxFolderName" 
                                runat="server" 
                                ErrorMessage="Enter folder name with  a-z A-Z0-9_" 
                                ControlToValidate="txtFolderName" 
                                Display="Dynamic" 
                                ValidationExpression="^[a-zA-Z0-9_]*$" 
                                ForeColor="Red">
14
anomepani

Je préfère absolument l'idée de Jeff Yates. Cela fonctionnera parfaitement si vous le modifiez légèrement:

string regex = String.Format("[{0}]", Regex.Escape(new string(Path.GetInvalidFileNameChars())));
Regex removeInvalidChars = new Regex(regex, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.CultureInvariant);

L’amélioration consiste simplement à échapper à la regex générée automatiquement.

14
Jan

Voici un extrait de code qui devrait aider pour .NET 3 et versions ultérieures.

using System.IO;
using System.Text.RegularExpressions;

public static class PathValidation
{
    private static string pathValidatorExpression = "^[^" + string.Join("", Array.ConvertAll(Path.GetInvalidPathChars(), x => Regex.Escape(x.ToString()))) + "]+$";
    private static Regex pathValidator = new Regex(pathValidatorExpression, RegexOptions.Compiled);

    private static string fileNameValidatorExpression = "^[^" + string.Join("", Array.ConvertAll(Path.GetInvalidFileNameChars(), x => Regex.Escape(x.ToString()))) + "]+$";
    private static Regex fileNameValidator = new Regex(fileNameValidatorExpression, RegexOptions.Compiled);

    private static string pathCleanerExpression = "[" + string.Join("", Array.ConvertAll(Path.GetInvalidPathChars(), x => Regex.Escape(x.ToString()))) + "]";
    private static Regex pathCleaner = new Regex(pathCleanerExpression, RegexOptions.Compiled);

    private static string fileNameCleanerExpression = "[" + string.Join("", Array.ConvertAll(Path.GetInvalidFileNameChars(), x => Regex.Escape(x.ToString()))) + "]";
    private static Regex fileNameCleaner = new Regex(fileNameCleanerExpression, RegexOptions.Compiled);

    public static bool ValidatePath(string path)
    {
        return pathValidator.IsMatch(path);
    }

    public static bool ValidateFileName(string fileName)
    {
        return fileNameValidator.IsMatch(fileName);
    }

    public static string CleanPath(string path)
    {
        return pathCleaner.Replace(path, "");
    }

    public static string CleanFileName(string fileName)
    {
        return fileNameCleaner.Replace(fileName, "");
    }
}
11
James

La plupart des solutions ci-dessus combinent des caractères illégaux pour le chemin et le nom de fichier, ce qui est incorrect (même si les deux appels renvoient actuellement le même jeu de caractères). Je diviserais d’abord le chemin + nom de fichier en chemin et nom de fichier, puis appliquerais le jeu approprié à l’un ou l’autre, puis combiner les deux à nouveau.

wvd_vegt

8
wvd_vegt

Si vous supprimez ou remplacez par un seul caractère les caractères non valides, vous pouvez avoir des collisions:

<abc -> abc
>abc -> abc

Voici une méthode simple pour éviter cela:

public static string ReplaceInvalidFileNameChars(string s)
{
    char[] invalidFileNameChars = System.IO.Path.GetInvalidFileNameChars();
    foreach (char c in invalidFileNameChars)
        s = s.Replace(c.ToString(), "[" + Array.IndexOf(invalidFileNameChars, c) + "]");
    return s;
}

Le résultat:

 <abc -> [1]abc
 >abc -> [2]abc
6
Maxence

Lancer une exception.

if ( fileName.IndexOfAny(Path.GetInvalidFileNameChars()) > -1 )
            {
                throw new ArgumentException();
            }
5
mirezus

J'ai écrit ce monstre pour le plaisir, il vous permet d'aller et venir:

public static class FileUtility
{
    private const char PrefixChar = '%';
    private static readonly int MaxLength;
    private static readonly Dictionary<char,char[]> Illegals;
    static FileUtility()
    {
        List<char> illegal = new List<char> { PrefixChar };
        illegal.AddRange(Path.GetInvalidFileNameChars());
        MaxLength = illegal.Select(x => ((int)x).ToString().Length).Max();
        Illegals = illegal.ToDictionary(x => x, x => ((int)x).ToString("D" + MaxLength).ToCharArray());
    }

    public static string FilenameEncode(string s)
    {
        var builder = new StringBuilder();
        char[] replacement;
        using (var reader = new StringReader(s))
        {
            while (true)
            {
                int read = reader.Read();
                if (read == -1)
                    break;
                char c = (char)read;
                if(Illegals.TryGetValue(c,out replacement))
                {
                    builder.Append(PrefixChar);
                    builder.Append(replacement);
                }
                else
                {
                    builder.Append(c);
                }
            }
        }
        return builder.ToString();
    }

    public static string FilenameDecode(string s)
    {
        var builder = new StringBuilder();
        char[] buffer = new char[MaxLength];
        using (var reader = new StringReader(s))
        {
            while (true)
            {
                int read = reader.Read();
                if (read == -1)
                    break;
                char c = (char)read;
                if (c == PrefixChar)
                {
                    reader.Read(buffer, 0, MaxLength);
                    var encoded =(char) ParseCharArray(buffer);
                    builder.Append(encoded);
                }
                else
                {
                    builder.Append(c);
                }
            }
        }
        return builder.ToString();
    }

    public static int ParseCharArray(char[] buffer)
    {
        int result = 0;
        foreach (char t in buffer)
        {
            int digit = t - '0';
            if ((digit < 0) || (digit > 9))
            {
                throw new ArgumentException("Input string was not in the correct format");
            }
            result *= 10;
            result += digit;
        }
        return result;
    }
}
4
Johan Larsson

Je pense qu'il est beaucoup plus facile de valider en utilisant une expression rationnelle et en spécifiant les caractères autorisés, au lieu d'essayer de rechercher tous les caractères incorrects . Voir les liens suivants: http: //www.c-sharpcorner. com/UploadFile/prasad_1/RegExpPSD12062005021717AM/RegExpPSD.aspxhttp://www.windowsdevcenter.com/pub/a/oreilly/windows/news/csharp_0101.html

En outre, effectuez une recherche sur les "éditeurs d’expression régulière", ils aident beaucoup. Il y en a autour de qui même sortir le code en c # pour vous.

3
Sandor Davidhazi

Cela semble être O(n) et ne dépense pas trop de mémoire en chaînes:

    private static readonly HashSet<char> invalidFileNameChars = new HashSet<char>(Path.GetInvalidFileNameChars());

    public static string RemoveInvalidFileNameChars(string name)
    {
        if (!name.Any(c => invalidFileNameChars.Contains(c))) {
            return name;
        }

        return new string(name.Where(c => !invalidFileNameChars.Contains(c)).ToArray());
    }
2
Alexey F
public static class StringExtensions
      {
        public static string RemoveUnnecessary(this string source)
        {
            string result = string.Empty;
            string regex = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
            Regex reg = new Regex(string.Format("[{0}]", Regex.Escape(regex)));
            result = reg.Replace(source, "");
            return result;
        }
    }

Vous pouvez utiliser la méthode clairement. 

2
aemre

Une ligne pour nettoyer la chaîne de tout caractère illégal pour le nommage de fichier Windows:

public static string CleanIllegalName(string p_testName) => new Regex(string.Format("[{0}]", Regex.Escape(new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars())))).Replace(p_testName, "");
2
Zananok

En parcourant les réponses ici, elles ** semblent toutes impliquer l’utilisation d’un tableau de caractères composé de caractères de nom de fichier non valides. 

Certes, cela peut être une micro-optimisation - mais pour le bénéfice de tous les noms de fichiers valides, il convient de noter que la création d'un hachage de caractères invalides entraînera une performance nettement meilleure. 

J'ai été très surpris (choqué) par le passé de la rapidité avec laquelle un hachage (ou un dictionnaire) surperforme une itération sur une liste. Avec les chaînes, c'est un nombre ridiculement bas (environ 5 à 7 éléments de mémoire). Avec la plupart des autres données simples (références d'objet, numéros, etc.), le croisement magique semble se situer autour de 20 éléments. 

La "liste" de Path.InvalidFileNameChars contient 40 caractères non valides. A fait une recherche aujourd'hui et il y a une bonne référence ici sur StackOverflow qui montre que le hashset prendra un peu plus de la moitié du temps d'un tableau/liste pour 40 éléments: https://stackoverflow.com/a/10762995/949129

Voici la classe d'assistance que j'utilise pour la désinfection des chemins. J'oublie maintenant pourquoi j'ai opté pour l'option de remplacement sophistiqué, mais c'est là un bonus mignon.

Méthode de bonus supplémentaire "IsValidLocalPath" aussi :)

(** ceux qui n'utilisent pas d'expressions régulières)

public static class PathExtensions
{
    private static HashSet<char> _invalidFilenameChars;
    private static HashSet<char> InvalidFilenameChars
    {
        get { return _invalidFilenameChars ?? (_invalidFilenameChars = new HashSet<char>(Path.GetInvalidFileNameChars())); }
    }


    /// <summary>Replaces characters in <c>text</c> that are not allowed in file names with the 
    /// specified replacement character.</summary>
    /// <param name="text">Text to make into a valid filename. The same string is returned if 
    /// it is valid already.</param>
    /// <param name="replacement">Replacement character, or NULL to remove bad characters.</param>
    /// <param name="fancyReplacements">TRUE to replace quotes and slashes with the non-ASCII characters ” and ⁄.</param>
    /// <returns>A string that can be used as a filename. If the output string would otherwise be empty, "_" is returned.</returns>
    public static string ToValidFilename(this string text, char? replacement = '_', bool fancyReplacements = false)
    {
        StringBuilder sb = new StringBuilder(text.Length);
        HashSet<char> invalids = InvalidFilenameChars;
        bool changed = false;

        for (int i = 0; i < text.Length; i++)
        {
            char c = text[i];
            if (invalids.Contains(c))
            {
                changed = true;
                char repl = replacement ?? '\0';
                if (fancyReplacements)
                {
                    if (c == '"') repl = '”'; // U+201D right double quotation mark
                    else if (c == '\'') repl = '’'; // U+2019 right single quotation mark
                    else if (c == '/') repl = '⁄'; // U+2044 fraction slash
                }
                if (repl != '\0')
                    sb.Append(repl);
            }
            else
                sb.Append(c);
        }

        if (sb.Length == 0)
            return "_";

        return changed ? sb.ToString() : text;
    }


    /// <summary>
    /// Returns TRUE if the specified path is a valid, local filesystem path.
    /// </summary>
    /// <param name="pathString"></param>
    /// <returns></returns>
    public static bool IsValidLocalPath(this string pathString)
    {
        // From solution at https://stackoverflow.com/a/11636052/949129
        Uri pathUri;
        Boolean isValidUri = Uri.TryCreate(pathString, UriKind.Absolute, out pathUri);
        return isValidUri && pathUri != null && pathUri.IsLoopback;
    }
}
2
Daniel Scott
public static bool IsValidFilename(string testName)
{
    return !new Regex("[" + Regex.Escape(new String(System.IO.Path.GetInvalidFileNameChars())) + "]").IsMatch(testName);
}
1
mbdavis

Le nom de fichier ne peut pas contenir de caractères issus des symboles Path.GetInvalidPathChars(), + et # et d'autres noms spécifiques. Nous avons combiné tous les chèques en une seule classe:

public static class FileNameExtensions
{
    private static readonly Lazy<string[]> InvalidFileNameChars =
        new Lazy<string[]>(() => Path.GetInvalidPathChars()
            .Union(Path.GetInvalidFileNameChars()
            .Union(new[] { '+', '#' })).Select(c => c.ToString(CultureInfo.InvariantCulture)).ToArray());


    private static readonly HashSet<string> ProhibitedNames = new HashSet<string>
    {
        @"aux",
        @"con",
        @"clock$",
        @"nul",
        @"prn",

        @"com1",
        @"com2",
        @"com3",
        @"com4",
        @"com5",
        @"com6",
        @"com7",
        @"com8",
        @"com9",

        @"lpt1",
        @"lpt2",
        @"lpt3",
        @"lpt4",
        @"lpt5",
        @"lpt6",
        @"lpt7",
        @"lpt8",
        @"lpt9"
    };

    public static bool IsValidFileName(string fileName)
    {
        return !string.IsNullOrWhiteSpace(fileName)
            && fileName.All(o => !IsInvalidFileNameChar(o))
            && !IsProhibitedName(fileName);
    }

    public static bool IsProhibitedName(string fileName)
    {
        return ProhibitedNames.Contains(fileName.ToLower(CultureInfo.InvariantCulture));
    }

    private static string ReplaceInvalidFileNameSymbols([CanBeNull] this string value, string replacementValue)
    {
        if (value == null)
        {
            return null;
        }

        return InvalidFileNameChars.Value.Aggregate(new StringBuilder(value),
            (sb, currentChar) => sb.Replace(currentChar, replacementValue)).ToString();
    }

    public static bool IsInvalidFileNameChar(char value)
    {
        return InvalidFileNameChars.Value.Contains(value.ToString(CultureInfo.InvariantCulture));
    }

    public static string GetValidFileName([NotNull] this string value)
    {
        return GetValidFileName(value, @"_");
    }

    public static string GetValidFileName([NotNull] this string value, string replacementValue)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            throw new ArgumentException(@"value should be non empty", nameof(value));
        }

        if (IsProhibitedName(value))
        {
            return (string.IsNullOrWhiteSpace(replacementValue) ? @"_" : replacementValue) + value; 
        }

        return ReplaceInvalidFileNameSymbols(value, replacementValue);
    }

    public static string GetFileNameError(string fileName)
    {
        if (string.IsNullOrWhiteSpace(fileName))
        {
            return CommonResources.SelectReportNameError;
        }

        if (IsProhibitedName(fileName))
        {
            return CommonResources.FileNameIsProhibited;
        }

        var invalidChars = fileName.Where(IsInvalidFileNameChar).Distinct().ToArray();

        if(invalidChars.Length > 0)
        {
            return string.Format(CultureInfo.CurrentCulture,
                invalidChars.Length == 1 ? CommonResources.InvalidCharacter : CommonResources.InvalidCharacters,
                StringExtensions.JoinQuoted(@",", @"'", invalidChars.Select(c => c.ToString(CultureInfo.CurrentCulture))));
        }

        return string.Empty;
    }
}

La méthode GetValidFileName remplace toutes les données incorrectes par _.

1
Backs

J'ai créé une méthode d'extension qui combine plusieurs suggestions:

  1. Tenir des caractères illégaux dans un hachage
  2. Filtrage des caractères sous ascii 127. Comme Path.GetInvalidFileNameChars n'inclut pas tous les caractères non valides possibles avec les codes ascii compris entre 0 et 255. Voir ici et MSDN
  3. Possibilité de définir le personnage de remplacement

La source:

public static class FileNameCorrector
{
    private static HashSet<char> invalid = new HashSet<char>(Path.GetInvalidFileNameChars());

    public static string ToValidFileName(this string name, char replacement = '\0')
    {
        var builder = new StringBuilder();
        foreach (var cur in name)
        {
            if (cur > 31 && cur < 128 && !invalid.Contains(cur))
            {
                builder.Append(cur);
            }
            else if (replacement != '\0')
            {
                builder.Append(replacement);
            }
        }

        return builder.ToString();
    }
}
0
schoetbi

Cela ne veut que vous voulez, et éviter les collisions

 static string SanitiseFilename(string key)
    {
        var invalidChars = Path.GetInvalidFileNameChars();
        var sb = new StringBuilder();
        foreach (var c in key)
        {
            var invalidCharIndex = -1;
            for (var i = 0; i < invalidChars.Length; i++)
            {
                if (c == invalidChars[i])
                {
                    invalidCharIndex = i;
                }
            }
            if (invalidCharIndex > -1)
            {
                sb.Append("_").Append(invalidCharIndex);
                continue;
            }

            if (c == '_')
            {
                sb.Append("__");
                continue;
            }

            sb.Append(c);
        }
        return sb.ToString();

    }
0
mcintyre321

Je pense que la question n'a pas déjà été complétée .... .... Les réponses ne décrivent que le chemin propre du nom de fichier OR ... pas les deux. Voici ma solution:

private static string CleanPath(string path)
{
    string regexSearch = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
    Regex r = new Regex(string.Format("[{0}]", Regex.Escape(regexSearch)));
    List<string> split = path.Split('\\').ToList();
    string returnValue = split.Aggregate(string.Empty, (current, s) => current + (r.Replace(s, "") + @"\"));
    returnValue = returnValue.TrimEnd('\\');
    return returnValue;
}
0
Suplanus