Làm cách nào để tạo tên tệp Windows hợp lệ từ một chuỗi tùy ý?


97

Tôi có một chuỗi như "Foo: Bar" mà tôi muốn sử dụng làm tên tệp, nhưng trên Windows, char ":" không được phép trong tên tệp.

Có phương pháp nào biến "Foo: Bar" thành một thứ giống như "Foo- Bar" không?


1
Tôi đã làm điều này tương tự ngày hôm nay. Tôi đã không kiểm tra SO vì một số lý do, nhưng dù sao cũng tìm thấy câu trả lời.
Aaron Smith

Câu trả lời:


153

Hãy thử một cái gì đó như sau:

string fileName = "something";
foreach (char c in System.IO.Path.GetInvalidFileNameChars())
{
   fileName = fileName.Replace(c, '_');
}

Biên tập:

GetInvalidFileNameChars()sẽ trả về 10 hoặc 15 ký tự, tốt hơn nên sử dụng một StringBuilderthay vì một chuỗi đơn giản; phiên bản gốc sẽ lâu hơn và tiêu tốn nhiều bộ nhớ hơn.


1
Bạn có thể sử dụng StringBuilder nếu muốn, nhưng nếu tên ngắn và tôi đoán nó không đáng. Bạn cũng có thể tạo phương thức của riêng mình để tạo một ký tự [] và thay thế tất cả các ký tự sai trong một lần lặp. Luôn luôn là tốt hơn để giữ cho nó đơn giản trừ khi nó không làm việc, bạn có thể có cổ chai tồi tệ hơn
Diego Jancic

2
InvalidFileNameChars = new char [] {'"', '<', '>', '|', '\ 0', '\ x0001', '\ x0002', '\ x0003', '\ x0004', '\ x0005 ',' \ x0006 ',' \ a ',' \ b ',' \ t ',' \ n ',' \ v ',' \ f ',' \ r ',' \ x000e ',' \ x000f ',' \ x0010 ',' \ x0011 ',' \ x0012 ',' \ x0013 ',' \ x0014 ',' \ x0015 ',' \ x0016 ',' \ x0017 ',' \ x0018 ',' \ x0019 ',' \ x001a ',' \ x001b ',' \ x001c ',' \ x001d ',' \ x001e ',' \ x001f ',': ',' * ','? ',' \\ ', '/'};
Diego Jancic 09/09/09

9
Xác suất để có hơn 2 ký tự không hợp lệ khác nhau trong chuỗi là rất nhỏ nên việc quan tâm đến hiệu suất của string.Replace () là vô nghĩa.
Serge Wautier

1
Giải pháp tuyệt vời, thú vị sang một bên, người bán lại đã đề xuất phiên bản Linq này: fileName = System.IO.Path.GetInvalidFileNameChars (). Aggregate (fileName, (current, c) => current.Replace (c, '_')); Tôi tự hỏi nếu có bất kỳ cải tiến hiệu suất có thể có ở đó. Tôi đã giữ bản gốc vì mục đích dễ đọc vì hiệu suất không phải là mối quan tâm lớn nhất của tôi. Nhưng nếu có ai quan tâm, có thể là giá trị điểm chuẩn
chrispepper1989

1
@AndyM Không cần đâu. file.name.txt.pdflà một pdf hợp lệ. Windows chỉ đọc .phần mở rộng cuối cùng .
Diego Jancic

33
fileName = fileName.Replace(":", "-") 

Tuy nhiên, ":" không phải là ký tự bất hợp pháp duy nhất cho Windows. Bạn cũng sẽ phải xử lý:

/, \, :, *, ?, ", <, > and |

Chúng được chứa trong System.IO.Path.GetInvalidFileNameChars ();

Ngoài ra (trên Windows), "." không thể là ký tự duy nhất trong tên tệp (cả ".", "..", "...", v.v. đều không hợp lệ). Hãy cẩn thận khi đặt tên tệp bằng ".", Ví dụ:

echo "test" > .test.

Sẽ tạo một tệp có tên ".test"

Cuối cùng, nếu bạn thực sự muốn thực hiện mọi thứ một cách chính xác, có một số tên tệp đặc biệt mà bạn cần chú ý. Trên Windows, bạn không thể tạo tệp có tên:

CON, PRN, AUX, CLOCK$, NUL
COM0, COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9
LPT0, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, and LPT9.

3
Tôi chưa bao giờ biết về những cái tên dành riêng. Làm cho tinh thần mặc dù
Greg Dean

4
Ngoài ra, đối với những gì nó đáng giá, bạn không thể tạo tên tệp bắt đầu bằng một trong những tên dành riêng này, theo sau là số thập phân. tức là con.air.avi
John Conrad

".foo" là tên tệp hợp lệ. Bạn không biết về tên tệp "CON" - nó dùng để làm gì?
cấu hình

Cào đó. CON dành cho bảng điều khiển.
cấu hình

Cảm ơn cấu hình; Tôi đã cập nhật câu trả lời, bạn đúng ".foo" là hợp lệ; tuy nhiên ".foo." dẫn đến những kết quả có thể xảy ra, không mong muốn. Đã cập nhật.
Phil Price

13

Điều này không hiệu quả hơn, nhưng nó thú vị hơn :)

var fileName = "foo:bar";
var invalidChars = System.IO.Path.GetInvalidFileNameChars();
var cleanFileName = new string(fileName.Where(m => !invalidChars.Contains(m)).ToArray<char>());

12

Trong trường hợp bất kỳ ai muốn một phiên bản được tối ưu hóa dựa trên StringBuilder, hãy sử dụng cái này. Bao gồm thủ thuật của rkagerer như một tùy chọn.

static char[] _invalids;

/// <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 simply remove bad characters.</param>
/// <param name="fancy">Whether 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, returns "_".</returns>
public static string MakeValidFileName(string text, char? replacement = '_', bool fancy = true)
{
    StringBuilder sb = new StringBuilder(text.Length);
    var invalids = _invalids ?? (_invalids = Path.GetInvalidFileNameChars());
    bool changed = false;
    for (int i = 0; i < text.Length; i++) {
        char c = text[i];
        if (invalids.Contains(c)) {
            changed = true;
            var repl = replacement ?? '\0';
            if (fancy) {
                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;
}

+1 cho mã đẹp và dễ đọc. Làm cho rất dễ đọc và nhận thấy các lỗi: P .. Hàm này sẽ trả về chuỗi gốc luôn luôn vì đã thay đổi sẽ không bao giờ đúng.
Erti-Chris Eelmaa

Cảm ơn, tôi nghĩ bây giờ tốt hơn. Bạn biết những gì họ nói về nguồn mở "nhiều đôi mắt làm cho tất cả các lỗi cạn vì vậy tôi không cần phải kiểm tra ghi đơn vị" ...
Qwertie

8

Đây là phiên bản của câu trả lời được chấp nhận sử Linqdụng Enumerable.Aggregate:

string fileName = "something";

Path.GetInvalidFileNameChars()
    .Aggregate(fileName, (current, c) => current.Replace(c, '_'));

7

Diego có giải pháp chính xác nhưng có một sai lầm rất nhỏ trong đó. Phiên bản của string.Replace đang được sử dụng phải là string.Replace (char, char), không có string.Replace (char, string)

Tôi không thể chỉnh sửa câu trả lời hoặc tôi chỉ thực hiện một thay đổi nhỏ.

Nên nó phải là:

string fileName = "something";
foreach (char c in System.IO.Path.GetInvalidFileNameChars())
{
   fileName = fileName.Replace(c, '_');
}

7

Đây là một chút thay đổi về câu trả lời của Diego.

Nếu bạn không sợ Unicode, bạn có thể giữ lại độ trung thực hơn một chút bằng cách thay thế các ký tự không hợp lệ bằng các ký hiệu Unicode hợp lệ giống với chúng. Đây là mã tôi đã sử dụng trong một dự án gần đây liên quan đến danh sách gỗ xẻ:

static string MakeValidFilename(string text) {
  text = text.Replace('\'', '’'); // U+2019 right single quotation mark
  text = text.Replace('"',  '”'); // U+201D right double quotation mark
  text = text.Replace('/', '⁄');  // U+2044 fraction slash
  foreach (char c in System.IO.Path.GetInvalidFileNameChars()) {
    text = text.Replace(c, '_');
  }
  return text;
}

Điều này tạo ra các tên tệp như 1⁄2” spruce.txtthay vì1_2_ spruce.txt

Có, nó thực sự hoạt động:

Mẫu Explorer

Emptor caveat

Tôi biết thủ thuật này sẽ hoạt động trên NTFS nhưng rất ngạc nhiên khi thấy nó cũng hoạt động trên các phân vùng FAT và FAT32. Đó là bởi vì các tên tệp dài được lưu trữ bằng Unicode , ngay cả khi Windows 95 / NT. Tôi đã thử nghiệm trên Win7, XP và thậm chí cả một bộ định tuyến dựa trên Linux và chúng đều hiển thị OK. Không thể nói như vậy đối với bên trong DOSBox.

Điều đó nói rằng, trước khi bắt đầu làm việc này, hãy cân nhắc xem liệu bạn có thực sự cần thêm sự chung thủy hay không. Các bí danh Unicode có thể gây nhầm lẫn cho mọi người hoặc các chương trình cũ, ví dụ như hệ điều hành cũ dựa vào các .


5

Đây là phiên bản sử dụng StringBuilderIndexOfAnyvới phần phụ thêm hàng loạt để đạt hiệu quả đầy đủ. Nó cũng trả về chuỗi gốc thay vì tạo một chuỗi trùng lặp.

Cuối cùng nhưng không kém phần quan trọng, nó có một câu lệnh chuyển đổi trả về các ký tự trông giống nhau mà bạn có thể tùy chỉnh theo bất kỳ cách nào bạn muốn. Kiểm tra tra cứu về sự nhầm lẫn của Unicode.org để xem bạn có thể có những tùy chọn nào, tùy thuộc vào phông chữ.

public static string GetSafeFilename(string arbitraryString)
{
    var invalidChars = System.IO.Path.GetInvalidFileNameChars();
    var replaceIndex = arbitraryString.IndexOfAny(invalidChars, 0);
    if (replaceIndex == -1) return arbitraryString;

    var r = new StringBuilder();
    var i = 0;

    do
    {
        r.Append(arbitraryString, i, replaceIndex - i);

        switch (arbitraryString[replaceIndex])
        {
            case '"':
                r.Append("''");
                break;
            case '<':
                r.Append('\u02c2'); // '˂' (modifier letter left arrowhead)
                break;
            case '>':
                r.Append('\u02c3'); // '˃' (modifier letter right arrowhead)
                break;
            case '|':
                r.Append('\u2223'); // '∣' (divides)
                break;
            case ':':
                r.Append('-');
                break;
            case '*':
                r.Append('\u2217'); // '∗' (asterisk operator)
                break;
            case '\\':
            case '/':
                r.Append('\u2044'); // '⁄' (fraction slash)
                break;
            case '\0':
            case '\f':
            case '?':
                break;
            case '\t':
            case '\n':
            case '\r':
            case '\v':
                r.Append(' ');
                break;
            default:
                r.Append('_');
                break;
        }

        i = replaceIndex + 1;
        replaceIndex = arbitraryString.IndexOfAny(invalidChars, i);
    } while (replaceIndex != -1);

    r.Append(arbitraryString, i, arbitraryString.Length - i);

    return r.ToString();
}

Nó không kiểm tra ., ..hoặc tên reserved thíchCON vì nó không được rõ ràng những gì mà thay thế nên.


3

Đang làm sạch một chút mã của tôi và tái cấu trúc một chút ... Tôi đã tạo một tiện ích mở rộng cho loại chuỗi:

public static string ToValidFileName(this string s, char replaceChar = '_', char[] includeChars = null)
{
  var invalid = Path.GetInvalidFileNameChars();
  if (includeChars != null) invalid = invalid.Union(includeChars).ToArray();
  return string.Join(string.Empty, s.ToCharArray().Select(o => o.In(invalid) ? replaceChar : o));
}

Giờ đây, nó dễ sử dụng hơn với:

var name = "Any string you want using ? / \ or even +.zip";
var validFileName = name.ToValidFileName();

Nếu bạn muốn thay thế bằng một ký tự khác với "_", bạn có thể sử dụng:

var validFileName = name.ToValidFileName(replaceChar:'#');

Và bạn có thể thêm ký tự để thay thế .. ví dụ như bạn không muốn dấu cách hoặc dấu phẩy:

var validFileName = name.ToValidFileName(includeChars: new [] { ' ', ',' });

Hy vọng nó giúp...

Chúc mừng


3

Một giải pháp đơn giản khác:

private string MakeValidFileName(string original, char replacementChar = '_')
{
  var invalidChars = new HashSet<char>(Path.GetInvalidFileNameChars());
  return new string(original.Select(c => invalidChars.Contains(c) ? replacementChar : c).ToArray());
}

3

Một mã một dòng đơn giản:

var validFileName = Path.GetInvalidFileNameChars().Aggregate(fileName, (f, c) => f.Replace(c, '_'));

Bạn có thể bọc nó trong một phương thức mở rộng nếu bạn muốn sử dụng lại nó.

public static string ToValidFileName(this string fileName) => Path.GetInvalidFileNameChars().Aggregate(fileName, (f, c) => f.Replace(c, '_'));

1

Tôi cần một hệ thống không thể tạo ra va chạm vì vậy tôi không thể ánh xạ nhiều ký tự thành một. Tôi đã kết thúc với:

public static class Extension
{
    /// <summary>
    /// Characters allowed in a file name. Note that curly braces don't show up here
    /// becausee they are used for escaping invalid characters.
    /// </summary>
    private static readonly HashSet<char> CleanFileNameChars = new HashSet<char>
    {
        ' ', '!', '#', '$', '%', '&', '\'', '(', ')', '+', ',', '-', '.',
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '=', '@',
        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
        'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
        '[', ']', '^', '_', '`',
        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
        'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
    };

    /// <summary>
    /// Creates a clean file name from one that may contain invalid characters in 
    /// a way that will not collide.
    /// </summary>
    /// <param name="dirtyFileName">
    /// The file name that may contain invalid filename characters.
    /// </param>
    /// <returns>
    /// A file name that does not contain invalid filename characters.
    /// </returns>
    /// <remarks>
    /// <para>
    /// Escapes invalid characters by converting their ASCII values to hexadecimal
    /// and wrapping that value in curly braces. Curly braces are escaped by doubling
    /// them, for example '{' => "{{".
    /// </para>
    /// <para>
    /// Note that although NTFS allows unicode characters in file names, this
    /// method does not.
    /// </para>
    /// </remarks>
    public static string CleanFileName(this string dirtyFileName)
    {
        string EscapeHexString(char c) =>
            "{" + (c > 255 ? $"{(uint)c:X4}" : $"{(uint)c:X2}") + "}";

        return string.Join(string.Empty,
                           dirtyFileName.Select(
                               c =>
                                   c == '{' ? "{{" :
                                   c == '}' ? "}}" :
                                   CleanFileNameChars.Contains(c) ? $"{c}" :
                                   EscapeHexString(c)));
    }
}

0

Tôi cần làm điều này hôm nay ... trong trường hợp của tôi, tôi cần nối tên khách hàng với ngày và giờ cho tệp .kmz cuối cùng. Giải pháp cuối cùng của tôi là:

 string name = "Whatever name with valid/invalid chars";
 char[] invalid = System.IO.Path.GetInvalidFileNameChars();
 string validFileName = string.Join(string.Empty,
                            string.Format("{0}.{1:G}.kmz", name, DateTime.Now)
                            .ToCharArray().Select(o => o.In(invalid) ? '_' : o));

Bạn thậm chí có thể làm cho nó thay thế khoảng trắng nếu bạn thêm ký tự khoảng trắng vào mảng không hợp lệ.

Có thể nó không phải là nhanh nhất, nhưng vì hiệu suất không phải là một vấn đề, tôi thấy nó thanh lịch và dễ hiểu.

Chúc mừng!


-2

Bạn có thể thực hiện việc này bằng sedlệnh:

 sed -e "
 s/[?()\[\]=+<>:;©®”,*|]/_/g
 s/"$'\t'"/ /g
 s/–/-/g
 s/\"/_/g
 s/[[:cntrl:]]/_/g"

cũng thấy một câu hỏi phức tạp hơn nhưng có liên quan tại địa chỉ: stackoverflow.com/questions/4413427/...
DW

Tại sao điều này cần được thực hiện trong C # thay vì Bash? Bây giờ tôi thấy một thẻ C # trên câu hỏi ban đầu, nhưng tại sao?
DW

1
Tôi biết, đúng vậy, tại sao không chỉ chuyển từ ứng dụng C # sang Bash mà có thể không được cài đặt để thực hiện điều này?
Peter Ritchie
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.