Как удалить недопустимые символы из путей и имен файлов?

StackOverflow https://stackoverflow.com/questions/146134

  •  02-07-2019
  •  | 
  •  

Вопрос

Мне нужен надежный и простой способ удалить недопустимые символы пути и файла из простой строки.Я использовал приведенный ниже код, но, похоже, он ничего не делает. Что мне не хватает?

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();
        }
    }
}
Это было полезно?

Решение

Вместо этого попробуйте что-нибудь вроде этого;

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(), ""); 
}

Но я должен согласиться с комментариями: я, вероятно, попытаюсь разобраться с источником незаконных путей, а не пытаться превратить незаконный путь в законный, но, вероятно, непреднамеренный.

Редактировать:Или потенциально «лучшее» решение, используя 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, "");

Тем не менее, напрашивается вопрос: почему вы вообще это делаете?

Другие советы

public string GetSafeFilename(string filename)
{

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

}

Этот ответ был в другой теме Цереры., мне очень нравится, всё аккуратно и просто.

Я использую Linq для очистки имен файлов.Вы также можете легко расширить это, чтобы проверять допустимые пути.

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

Обновлять

Некоторые комментарии указывают, что этот метод у них не работает, поэтому я включил ссылку на фрагмент DotNetFiddle, чтобы вы могли проверить этот метод.

https://dotnetfiddle.net/nw1SWY

Вы можете удалить недопустимые символы с помощью Linq следующим образом:

var invalidChars = Path.GetInvalidFileNameChars();

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

РЕДАКТИРОВАТЬ
Вот как это выглядит с необходимыми изменениями, упомянутыми в комментариях:

var invalidChars = Path.GetInvalidFileNameChars();

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

Все это отличные решения, но все они полагаются на Path.GetInvalidFileNameChars, что может быть не так надежно, как вы думаете.Обратите внимание на следующее замечание в документации MSDN по Path.GetInvalidFileNameChars:

Массив, возвращаемый этим методом, не гарантируется, что он будет содержать полный набор недопустимых символов в именах файлов и каталогов. Полный набор недопустимых символов может различаться в зависимости от файловой системы.Например, на настольных платформах под управлением Windows недопустимые символы пути могут включать символы ASCII/Unicode от 1 до 31, а также кавычки ("), меньше (<), больше (>), вертикальную черту (|), возврат () \b), ноль (\0) и вкладка ( ).

с этим не лучше Path.GetInvalidPathChars метод.Там содержится точно такое же замечание.

Для имен файлов:

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

Для полных путей:

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

Обратите внимание: если вы собираетесь использовать это в качестве функции безопасности, более надежным подходом будет расширение всех путей, а затем проверка того, что указанный пользователем путь действительно является дочерним по отношению к каталогу, к которому пользователь должен иметь доступ.

Для начинающих, Trim удаляет только символы из начала или конца строки..Во-вторых, вам следует оценить, действительно ли вы хотите удалить оскорбительные символы или потерпеть неудачу и сообщить пользователю, что его имя файла недействительно.Я выбираю последнее, но мой ответ должен, по крайней мере, показать вам, как поступать правильно И неправильно:

Вопрос StackOverflow, показывающий, как проверить, является ли данная строка допустимым именем файла.Обратите внимание, что вы можете использовать регулярное выражение из этого вопроса для удаления символов с заменой регулярного выражения (если вам действительно это нужно).

Для этого я использую регулярные выражения.Сначала я динамически создаю регулярное выражение.

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

Затем я просто вызываю removeInvalidChars.Replace, чтобы выполнить поиск и замену.Очевидно, это можно распространить и на символы пути.

Лучший способ удалить недопустимый символ из пользовательского ввода — заменить недопустимый символ с помощью класса Regex, создать метод в коде или также проверить его на стороне клиента с помощью элемента управления RegularExpression.

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

ИЛИ

<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">

Я абсолютно предпочитаю идею Джеффа Йейтса.Он будет работать отлично, если вы его немного модифицируете:

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

Улучшение заключается в том, чтобы избежать автоматически создаваемого регулярного выражения.

Вот фрагмент кода, который должен помочь для .NET 3 и более поздних версий.

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

Большинство приведенных выше решений сочетают недопустимые символы как для пути, так и для имени файла, что неверно (даже если оба вызова в настоящее время возвращают один и тот же набор символов).Я бы сначала разделил путь + имя файла на путь и имя файла, затем применил бы соответствующий набор к любому из них, а затем снова объединил бы их.

wvd_vegt

Если вы удалите или замените недопустимые символы одним символом, у вас могут возникнуть коллизии:

<abc -> abc
>abc -> abc

Вот простой способ избежать этого:

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

Результат:

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

Выбросить исключение.

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

Я написал этого монстра ради развлечения, он позволяет вам путешествовать туда и обратно:

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

Я думаю, что гораздо проще проверить использование регулярного выражения и указать, какие символы разрешены, вместо того, чтобы пытаться проверить все плохие символы.Посмотрите эти ссылки:http://www.c-sharpcorner.com/UploadFile/prasad_1/RegExpPSD12062005021717AM/RegExpPSD.aspx http://www.windowsdevcenter.com/pub/a/oreilly/windows/news/csharp_0101.html

Кроме того, выполните поиск «редакторов регулярных выражений», они очень помогают.Некоторые из них даже выводят для вас код на C#.

Кажется, это O(n) и не тратит слишком много памяти на строки:

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

Просматривая ответы здесь, кажется, что все они** связаны с использованием массива символов недопустимых символов имени файла.

Конечно, это может быть микрооптимизация, но для тех, кто хочет проверить большое количество значений на предмет допустимости имен файлов, стоит отметить, что создание хэш-набора недопустимых символов приведет к заметному повышению производительности.

В прошлом я был очень удивлен (шокирован) тем, насколько быстро хэш-набор (или словарь) превосходит итерацию по списку.Для строк это смехотворно малое число (около 5-7 элементов по памяти).С большинством других простых данных (ссылки на объекты, числа и т. д.) волшебное пересечение составляет около 20 элементов.

В «списке» Path.InvalidFileNameChars содержится 40 недопустимых символов.Выполнил поиск сегодня, и здесь, на StackOverflow, есть довольно хороший тест, который показывает, что хэш-набор займет чуть более половины времени массива/списка для 40 элементов: https://stackoverflow.com/a/10762995/949129

Вот вспомогательный класс, который я использую для очистки путей.Теперь я забыл, почему у меня была такая необычная опция замены, но она есть как приятный бонус.

Дополнительный бонусный метод IsValidLocalPath :)

(** те, которые не используют регулярные выражения)

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

Вы можете использовать метод четко.

Имя файла не может содержать символы из Path.GetInvalidPathChars(), + и # символы и другие конкретные имена.Мы объединили все проверки в один класс:

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

Метод GetValidFileName заменяет все неверные данные на _.

Один вкладыш для очистки строки от любых недопустимых символов в именах файлов 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, "");
public static bool IsValidFilename(string testName)
{
    return !new Regex("[" + Regex.Escape(new String(System.IO.Path.GetInvalidFileNameChars())) + "]").IsMatch(testName);
}

Это сделает то, что вы хотите, и избежите столкновений.

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

    }

Я думаю, что на вопрос уже не полный ответ...Ответы описывают только чистое имя файла ИЛИ путь...не оба.Вот мое решение:

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

Я создал метод расширения, который сочетает в себе несколько предложений:

  1. Хранение недопустимых символов в хэш-наборе
  2. Фильтрация символов ниже ascii 127.Поскольку Path.GetInvalidFileNameChars не включает все недопустимые символы, возможные с кодами ASCII от 0 до 255. Глянь сюда и MSDN
  3. Возможность определения символа замены

Источник:

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

Или вы можете просто сделать

[YOUR STRING].Replace('\\', ' ').Replace('/', ' ').Replace('"', ' ').Replace('*', ' ').Replace(':', ' ').Replace('?', ' ').Replace('<', ' ').Replace('>', ' ').Replace('|', ' ').Trim();
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top