Tên tệp vệ sinh C #


174

Gần đây tôi đã chuyển một loạt các MP3 từ nhiều địa điểm khác nhau vào một kho lưu trữ. Tôi đã xây dựng tên tệp mới bằng cách sử dụng thẻ ID3 (cảm ơn, TagLib-Sharp!) Và tôi nhận thấy rằng tôi đang nhận được System.NotSupportedException:

"Định dạng của đường dẫn đã cho không được hỗ trợ."

Điều này đã được tạo ra bởi một trong hai File.Copy()hoặc Directory.CreateDirectory().

Không mất nhiều thời gian để nhận ra rằng tên tệp của tôi cần được vệ sinh. Vì vậy, tôi đã làm điều rõ ràng:

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

Trước sự ngạc nhiên của tôi, tôi tiếp tục nhận được ngoại lệ. Hóa ra ':' không có trong tập hợp Path.GetInvalidPathChars(), bởi vì nó hợp lệ trong một đường dẫn gốc. Tôi cho rằng điều đó có ý nghĩa - nhưng đây phải là một vấn đề khá phổ biến. Có ai có một số mã ngắn vệ sinh một con đường? Kỹ lưỡng nhất tôi đã đưa ra với điều này, nhưng cảm giác như nó có thể là quá mức cần thiết.

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

Bất kỳ cải tiến để làm cho chức năng này nhanh hơn và ít baroque hơn sẽ được đánh giá cao.


Câu trả lời:


314

Để xóa tên tập tin, bạn có thể làm điều này

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
Câu hỏi là về các đường dẫn, không phải tên tệp và các ký tự không hợp lệ cho chúng là khác nhau.
Dour High Arch

15
Có thể, nhưng mã này chắc chắn đã giúp tôi khi tôi gặp vấn đề tương tự :)
mmr

8
Và một người dùng SO tuyệt vời khác có thể đi bộ ... Chức năng này rất tuyệt. Cảm ơn bạn Adrevdm ...
Dan Rosenstark

19
Phương pháp tuyệt vời. Đừng quên mặc dù những từ dành riêng vẫn sẽ cắn bạn, và bạn sẽ phải gãi đầu. Nguồn: Wikipedia Tên từ dành riêng
Spud

8
Dấu chấm là các ký tự không hợp lệ nếu chúng ở cuối tên tệp vì vậy GetInvalidFileNameCharskhông bao gồm chúng. Nó không ném ngoại lệ vào các cửa sổ, nó chỉ loại bỏ chúng, nhưng nó có thể gây ra hành vi bất ngờ nếu bạn đang mong đợi khoảng thời gian ở đó. Tôi đã sửa đổi regex để xử lý trường hợp đó .để được coi là một trong những ký tự không hợp lệ nếu nó ở cuối chuỗi.
Scott Chamberlain

120

Một giải pháp ngắn hơn:

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

1
@PeterMajeed: TIL rằng việc đếm dòng bắt đầu từ 0 :-)
Gary McGill

Điều này tốt hơn câu trả lời hàng đầu đặc biệt là đối với ASP.NET Core có thể trả về các ký tự khác nhau dựa trên nền tảng.
Alexei

79

Dựa trên câu trả lời xuất sắc của Andre nhưng có tính đến nhận xét của Spud về các từ dành riêng, tôi đã tạo phiên bản này:

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

Và đây là những bài kiểm tra đơn vị của tôi

[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
Đây là một câu trả lời cực kỳ đầy đủ, ít nhất là phần tên tệp của câu hỏi và xứng đáng được nâng cấp hơn.
Brian MacKay

2
Gợi ý nhỏ vì có vẻ như phương thức đã đi theo hướng này: Thêm một từ khóa này và nó trở thành một phương thức mở rộng tiện dụng. Chuỗi tĩnh công khai CoerceValidFileName (tên tệp Chuỗi này)
Ryan McArthur

2
Lỗi nhỏ: phương pháp này không thay đổi các từ dành riêng mà không có phần mở rộng tệp (ví dụ COM1:), cũng không được phép. Khắc phục được đề xuất sẽ là thay "^{0}(\\.|$)""_reservedWord_$1"
đổiWordPotype


4

Tôi đang sử dụng System.IO.Path.GetInvalidFileNameChars() phương pháp để kiểm tra các ký tự không hợp lệ và tôi không gặp vấn đề gì.

Tôi đang sử dụng mã sau đây:

foreach( char invalidchar in System.IO.Path.GetInvalidFileNameChars())
{
    filename = filename.Replace(invalidchar, '_');
}

3

Tôi muốn giữ lại các nhân vật theo một cách nào đó, không chỉ đơn giản là thay thế nhân vật bằng dấu gạch dưới.

Một cách tôi nghĩ là thay thế các nhân vật bằng các nhân vật trông tương tự (trong tình huống của tôi), không có khả năng được sử dụng như các nhân vật thông thường. Vì vậy, tôi đã lấy danh sách các ký tự không hợp lệ và tìm thấy lượt thích.

Sau đây là các chức năng để mã hóa và giải mã với giao diện.

Mã này không bao gồm một danh sách đầy đủ cho tất cả các ký tự System.IO.Path.GetInvalidFileNameChars (). Vì vậy, tùy thuộc vào bạn để mở rộng hoặc sử dụng thay thế gạch dưới cho bất kỳ ký tự còn lại.

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

Bạn có thể chọn giao diện của riêng bạn. Tôi đã sử dụng ứng dụng Bản đồ nhân vật trong các cửa sổ để chọn của tôi%windir%\system32\charmap.exe

Khi tôi điều chỉnh thông qua khám phá, tôi sẽ cập nhật mã này.


lưu ý rằng có nhiều ký tự trông giống với các ký tự đó, như dạng toàn băng thông !"#$%&'()*+,-./:;<=>?@{|}~ hoặc các dạng khác của chúng như /SOLIDUS và `⁄` FRACTION SLASH có thể được sử dụng trực tiếp trong tên tệp mà không gặp vấn đề gì
phuclv

2

Tôi nghĩ vấn đề là lần đầu tiên bạn gọi Path.GetDirectoryNamevào chuỗi xấu. Nếu cái này có các ký tự không phải tên tệp trong đó, .Net không thể biết phần nào của chuỗi là thư mục và ném. Bạn phải làm so sánh chuỗi.

Giả sử chỉ có tên tệp là xấu, không phải toàn bộ đường dẫn, hãy thử điều này:

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

Tôi đã có thành công với điều này trong quá khứ.

Đẹp, ngắn và tĩnh :-)

    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

có rất nhiều giải pháp làm việc ở đây chỉ vì mục đích hoàn chỉnh, đây là một cách tiếp cận không sử dụng regex, nhưng sử dụng LINQ:

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

Ngoài ra, đó là một giải pháp rất ngắn;)


1
Tôi yêu một lớp lót :)
Larry

1

Đây là một phương pháp mở rộng tải lười biếng hiệu quả dựa trên mã của Andre:

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

Mã của bạn sẽ sạch hơn nếu bạn nối các thư mục và tên tệp lại với nhau và vệ sinh nó thay vì vệ sinh chúng một cách độc lập. Đối với việc vệ sinh đi :, chỉ cần lấy ký tự thứ 2 trong chuỗi. Nếu nó bằng "replacechar", thay thế nó bằng dấu hai chấm. Vì ứng dụng này là dành cho sử dụng của riêng bạn, nên một giải pháp như vậy là hoàn toàn đủ.


-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();
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.