C # Очистить имя файла

174

Недавно я переместил кучу MP3 из разных мест в хранилище. Я создавал новые имена файлов, используя теги ID3 (спасибо, TagLib-Sharp!), И я заметил, что получаю System.NotSupportedException:

Msgstr "Формат данного пути не поддерживается."

Это было сгенерировано либо File.Copy()или Directory.CreateDirectory().

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

public static string SanitizePath_(string path, char replaceChar)
{
    string dir = Path.GetDirectoryName(path);
    foreach (char c in Path.GetInvalidPathChars())
        dir = dir.Replace(c, replaceChar);

    string name = Path.GetFileName(path);
    foreach (char c in Path.GetInvalidFileNameChars())
        name = name.Replace(c, replaceChar);

    return dir + name;
}

К моему удивлению, я продолжал получать исключения. Оказалось, что ':' не входит в набор Path.GetInvalidPathChars(), потому что он действителен в корне пути. Я полагаю, это имеет смысл, но это должно быть довольно распространенной проблемой. У кого-нибудь есть какой-нибудь короткий код, который очищает путь? Самое тщательное, что я придумал, но такое ощущение, что это, вероятно, излишне.

    // replaces invalid characters with replaceChar
    public static string SanitizePath(string path, char replaceChar)
    {
        // construct a list of characters that can't show up in filenames.
        // need to do this because ":" is not in InvalidPathChars
        if (_BadChars == null)
        {
            _BadChars = new List<char>(Path.GetInvalidFileNameChars());
            _BadChars.AddRange(Path.GetInvalidPathChars());
            _BadChars = Utility.GetUnique<char>(_BadChars);
        }

        // remove root
        string root = Path.GetPathRoot(path);
        path = path.Remove(0, root.Length);

        // split on the directory separator character. Need to do this
        // because the separator is not valid in a filename.
        List<string> parts = new List<string>(path.Split(new char[]{Path.DirectorySeparatorChar}));

        // check each part to make sure it is valid.
        for (int i = 0; i < parts.Count; i++)
        {
            string part = parts[i];
            foreach (char c in _BadChars)
            {
                part = part.Replace(c, replaceChar);
            }
            parts[i] = part;
        }

        return root + Utility.Join(parts, Path.DirectorySeparatorChar.ToString());
    }

Любые улучшения, чтобы сделать эту функцию более быстрой и менее барочной, будут высоко оценены.

Джейсон Сундрам
источник

Ответы:

314

Чтобы очистить имя файла, вы можете сделать это

private static string MakeValidFileName( string name )
{
   string invalidChars = System.Text.RegularExpressions.Regex.Escape( new string( System.IO.Path.GetInvalidFileNameChars() ) );
   string invalidRegStr = string.Format( @"([{0}]*\.+$)|([{0}]+)", invalidChars );

   return System.Text.RegularExpressions.Regex.Replace( name, invalidRegStr, "_" );
}
Andre
источник
3
Вопрос был о путях, а не именах файлов, и недопустимые символы для них разные.
Dour High Arch
15
Может быть, но этот код , конечно , помог мне , когда у меня была такая же проблема :)
MMR
8
И еще один потенциально хороший пользователь SO идет пешком ... Эта функция великолепна. Спасибо Adrevdm ...
Дэн Розенстарк
19
Отличный метод. Не забывайте, однако, что зарезервированные слова будут все еще кусать вас, и вы останетесь чесать голову. Источник: Википедия Имя файла зарезервированные слова
Spud
8
Точки являются недопустимыми символами, если они находятся в конце имени файла, поэтому GetInvalidFileNameCharsне включают их. Он не генерирует исключение в окнах, он просто удаляет их, но может вызвать неожиданное поведение, если вы ожидаете, что период будет там. Я изменил регулярное выражение для обработки этого случая, чтобы .он считался одним из недопустимых символов, если он находится в конце строки.
Скотт Чемберлен
120

Более короткое решение:

var invalids = System.IO.Path.GetInvalidFileNameChars();
var newName = String.Join("_", origFileName.Split(invalids, StringSplitOptions.RemoveEmptyEntries) ).TrimEnd('.');
DenNukem
источник
1
@PeterMajeed: TIL, что подсчет строк начинается с нуля :-)
Гэри МакГилл
Это лучше, чем топовый ответ, особенно для ASP.NET Core, который может возвращать разные символы в зависимости от платформы.
Алексей
79

Основываясь на превосходном ответе Андре, но принимая во внимание комментарий Спуда к зарезервированным словам, я сделал эту версию:

/// <summary>
/// Strip illegal chars and reserved words from a candidate filename (should not include the directory path)
/// </summary>
/// <remarks>
/// http://stackoverflow.com/questions/309485/c-sharp-sanitize-file-name
/// </remarks>
public static string CoerceValidFileName(string filename)
{
    var invalidChars = Regex.Escape(new string(Path.GetInvalidFileNameChars()));
    var invalidReStr = string.Format(@"[{0}]+", invalidChars);

    var reservedWords = new []
    {
        "CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4",
        "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4",
        "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"
    };

    var sanitisedNamePart = Regex.Replace(filename, invalidReStr, "_");
    foreach (var reservedWord in reservedWords)
    {
        var reservedWordPattern = string.Format("^{0}\\.", reservedWord);
        sanitisedNamePart = Regex.Replace(sanitisedNamePart, reservedWordPattern, "_reservedWord_.", RegexOptions.IgnoreCase);
    }

    return sanitisedNamePart;
}

И это мои юнит-тесты

[Test]
public void CoerceValidFileName_SimpleValid()
{
    var filename = @"thisIsValid.txt";
    var result = PathHelper.CoerceValidFileName(filename);
    Assert.AreEqual(filename, result);
}

[Test]
public void CoerceValidFileName_SimpleInvalid()
{
    var filename = @"thisIsNotValid\3\\_3.txt";
    var result = PathHelper.CoerceValidFileName(filename);
    Assert.AreEqual("thisIsNotValid_3__3.txt", result);
}

[Test]
public void CoerceValidFileName_InvalidExtension()
{
    var filename = @"thisIsNotValid.t\xt";
    var result = PathHelper.CoerceValidFileName(filename);
    Assert.AreEqual("thisIsNotValid.t_xt", result);
}

[Test]
public void CoerceValidFileName_KeywordInvalid()
{
    var filename = "aUx.txt";
    var result = PathHelper.CoerceValidFileName(filename);
    Assert.AreEqual("_reservedWord_.txt", result);
}

[Test]
public void CoerceValidFileName_KeywordValid()
{
    var filename = "auxillary.txt";
    var result = PathHelper.CoerceValidFileName(filename);
    Assert.AreEqual("auxillary.txt", result);
}
указ
источник
1
Это очень полный ответ, по крайней мере, на часть вопроса, касающуюся имени файла, и заслуживает большего количества голосов.
Брайан Маккей
2
Незначительное предположение, поскольку метод выглядит так: добавьте ключевое слово this, и он станет удобным методом расширения. public static String CoerceValidFileName (это имя файла String)
Райан Макартур,
2
Небольшая ошибка: этот метод не изменяет зарезервированные слова без расширений файлов (например, COM1), которые также запрещены. Предложенное исправление будет состоять в том, чтобы изменить зарезервированныйWordPattern "^{0}(\\.|$)"и заменяющую строку на"_reservedWord_$1"
Dehalion
31
string clean = String.Concat(dirty.Split(Path.GetInvalidFileNameChars()));
данные
источник
5
рассмотрим String.Concat(dirty...)вместоJoin(String.Empty...
drzaus
DenNukem уже предлагал этот ответ: stackoverflow.com/a/13617375/244916 (хотя учтите комментарий, хотя).
Чувак Паскало
4

Я использую System.IO.Path.GetInvalidFileNameChars() метод для проверки недопустимых символов, и у меня нет проблем.

Я использую следующий код:

foreach( char invalidchar in System.IO.Path.GetInvalidFileNameChars())
{
    filename = filename.Replace(invalidchar, '_');
}
Андре Лил
источник
3

Я хотел каким-то образом сохранить символы, а не просто заменить символ подчеркиванием.

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

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

Этот код не включает полный список всех символов System.IO.Path.GetInvalidFileNameChars (). Так что это зависит от вас, чтобы расширить или использовать замену подчеркивания для любых оставшихся символов.

private static Dictionary<string, string> EncodeMapping()
{
    //-- Following characters are invalid for windows file and folder names.
    //-- \/:*?"<>|
    Dictionary<string, string> dic = new Dictionary<string, string>();
    dic.Add(@"\", "Ì"); // U+OOCC
    dic.Add("/", "Í"); // U+OOCD
    dic.Add(":", "¦"); // U+00A6
    dic.Add("*", "¤"); // U+00A4
    dic.Add("?", "¿"); // U+00BF
    dic.Add(@"""", "ˮ"); // U+02EE
    dic.Add("<", "«"); // U+00AB
    dic.Add(">", "»"); // U+00BB
    dic.Add("|", "│"); // U+2502
    return dic;
}

public static string Escape(string name)
{
    foreach (KeyValuePair<string, string> replace in EncodeMapping())
    {
        name = name.Replace(replace.Key, replace.Value);
    }

    //-- handle dot at the end
    if (name.EndsWith(".")) name = name.CropRight(1) + "°";

    return name;
}

public static string UnEscape(string name)
{
    foreach (KeyValuePair<string, string> replace in EncodeMapping())
    {
        name = name.Replace(replace.Value, replace.Key);
    }

    //-- handle dot at the end
    if (name.EndsWith("°")) name = name.CropRight(1) + ".";

    return name;
}

Вы можете выбрать свои собственные приглядывания. Я использовал приложение «Карта персонажей» в Windows, чтобы выбрать мое%windir%\system32\charmap.exe

Поскольку я делаю настройки через открытие, я обновлю этот код.

Valamas
источник
обратите внимание, что есть много символов, которые выглядят более похожими на эти, такие как форма с полной шириной!"#$%&'()*+,-./:;<=>?@{|}~ или другие формы, такие как /SOLIDUS и `FR` FRACTION SLASH, которые можно без проблем использовать непосредственно в именах файлов
phuclv
2

Я думаю, проблема в том, что вы сначала вызываете Path.GetDirectoryNameплохую строку. Если в нем есть символы не из имен файлов, .Net не может определить, какие части строки являются каталогами и выбрасывает. Вы должны сделать сравнение строк.

Предполагая, что это только имя файла, а не весь путь, попробуйте это:

public static string SanitizePath(string path, char replaceChar)
{
    int filenamePos = path.LastIndexOf(Path.DirectorySeparatorChar) + 1;
    var sb = new System.Text.StringBuilder();
    sb.Append(path.Substring(0, filenamePos));
    for (int i = filenamePos; i < path.Length; i++)
    {
        char filenameChar = path[i];
        foreach (char c in Path.GetInvalidFileNameChars())
            if (filenameChar.Equals(c))
            {
                filenameChar = replaceChar;
                break;
            }

        sb.Append(filenameChar);
    }

    return sb.ToString();
}
Dour High Arch
источник
2

Я имел успех с этим в прошлом.

Хороший, короткий и статичный :-)

    public static string returnSafeString(string s)
    {
        foreach (char character in Path.GetInvalidFileNameChars())
        {
            s = s.Replace(character.ToString(),string.Empty);
        }

        foreach (char character in Path.GetInvalidPathChars())
        {
            s = s.Replace(character.ToString(), string.Empty);
        }

        return (s);
    }
Helix 88
источник
2

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

var invalids = Path.GetInvalidFileNameChars();
filename = invalids.Aggregate(filename, (current, c) => current.Replace(c, '_'));

Кроме того, это очень короткое решение;)

kappadoky
источник
1
Я люблю один
Ларри
1

Вот эффективный метод расширения отложенной загрузки, основанный на коде Андре:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LT
{
    public static class Utility
    {
        static string invalidRegStr;

        public static string MakeValidFileName(this string name)
        {
            if (invalidRegStr == null)
            {
                var invalidChars = System.Text.RegularExpressions.Regex.Escape(new string(System.IO.Path.GetInvalidFileNameChars()));
                invalidRegStr = string.Format(@"([{0}]*\.+$)|([{0}]+)", invalidChars);
            }

            return System.Text.RegularExpressions.Regex.Replace(name, invalidRegStr, "_");
        }
    }
}
Брайан Легенд
источник
0

Ваш код будет чище, если вы добавите каталог и имя файла вместе и очистите их, а не очистите их независимо. Что касается очистки от:, просто возьмите 2-й символ в строке. Если он равен «replacechar», замените его двоеточием. Поскольку это приложение для вашего собственного использования, такого решения должно быть вполне достаточно.

Брайан
источник
-1
using System;
using System.IO;
using System.Linq;
using System.Text;

public class Program
{
    public static void Main()
    {
        try
        {
            var badString = "ABC\\DEF/GHI<JKL>MNO:PQR\"STU\tVWX|YZA*BCD?EFG";
            Console.WriteLine(badString);
            Console.WriteLine(SanitizeFileName(badString, '.'));
            Console.WriteLine(SanitizeFileName(badString));
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }
    }

    private static string SanitizeFileName(string fileName, char? replacement = null)
    {
        if (fileName == null) { return null; }
        if (fileName.Length == 0) { return ""; }

        var sb = new StringBuilder();
        var badChars = Path.GetInvalidFileNameChars().ToList();

        foreach (var @char in fileName)
        {
            if (badChars.Contains(@char)) 
            {
                if (replacement.HasValue)
                {
                    sb.Append(replacement.Value);
                }
                continue; 
            }
            sb.Append(@char);
        }
        return sb.ToString();
    }
}
Ralf
источник