C # Sanitize Ім'я файлу


174

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

"Формат заданого шляху не підтримується."

Це було створено 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, "_" );
}

3
Питання стосувалося шляхів, а не імен файлів, а недійсні символи для них різні.
Dour High Arch

15
Можливо, але цей код, безумовно, допоміг мені, коли у мене була така ж проблема :)
mmr

8
І ще один потенційно великий користувач SO піде на прогулянку ... Ця функція чудова. Дякую, Adrevdm ...
Dan Rosenstark

19
Чудовий метод. Не забувайте, хоча ці зарезервовані слова все одно будуть вас кусати, і вам залишиться чесати голову. Джерело: Вікіпедія Назва файлу зарезервовані
Спуд

8
Періоди є недійсними символами, якщо вони знаходяться в кінці імені файлу, тому GetInvalidFileNameCharsвони не включають їх. Він не кидає виняток у Windows, він просто позбавляє їх, але це може спричинити несподівану поведінку, якщо ви очікуєте, що період буде там. Я змінив регулярний вираз, щоб обробити цей випадок, щоб змусити .його вважати одним з недійсних символів, якщо він знаходиться в кінці рядка.
Скотт Чемберлен

120

Коротше рішення:

var invalids = System.IO.Path.GetInvalidFileNameChars();
var newName = String.Join("_", origFileName.Split(invalids, StringSplitOptions.RemoveEmptyEntries) ).TrimEnd('.');

1
@PeterMajeed: Якщо цей підрахунок ліній починається з нуля :-)
Гері Макгілл

Це краще, ніж найкраща відповідь, особливо для 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
Незначна пропозиція, оскільки, схоже, метод пішов у цьому напрямку: Додайте це ключове слово і воно стане зручним методом розширення. загальнодоступна статична String CoerceValidFileName (це ім'я файлу String)
Ryan McArthur

2
Мала помилка: цей метод не змінює зарезервовані слова без розширень файлів (напр. COM1), Які також заборонено. Запропонованим виправленням буде змінити зарезервовану "^{0}(\\.|$)""_reservedWord_$1"
Dehalion


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

Оскільки я вноситиму корективи через відкриття, я оновлю цей код.


зауважте, що існує багато символів, схожих на такі, як форма повної ширини !"#$%&'()*+,-./:;<=>?@{|}~ або інші їх форми, такі як /SOLIDUS та `⁄` 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();
}

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

2

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

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

Також це дуже коротке рішення;)


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-й символ у рядку. Якщо вона дорівнює "заміннику", замініть її двокрапкою. Оскільки ця програма призначена для власного використання, такого рішення має бути цілком достатньо.


-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();
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.