วิธีสร้างชื่อไฟล์ Windows ที่ถูกต้องจากสตริงโดยพลการ?


97

ฉันมีสตริงเช่น "Foo: Bar" ที่ฉันต้องการใช้เป็นชื่อไฟล์ แต่ใน Windows ไม่อนุญาตให้ใช้อักขระ ":" ในชื่อไฟล์

มีวิธีที่จะเปลี่ยน "Foo: Bar" ให้เป็น "Foo-Bar" ได้หรือไม่?


2
วันนี้ฉันทำสิ่งเดียวกันนี้ ฉันไม่ได้ตรวจสอบ SO ด้วยเหตุผลบางประการ แต่ก็พบคำตอบอยู่ดี
Aaron Smith

คำตอบ:


158

ลองทำสิ่งนี้:

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

แก้ไข:

เนื่องจากGetInvalidFileNameChars()จะส่งคืน 10 หรือ 15 ตัวอักษรจึงควรใช้ a StringBuilderแทนสตริงธรรมดา เวอร์ชันดั้งเดิมจะใช้เวลานานกว่าและใช้หน่วยความจำมากขึ้น


1
คุณสามารถใช้ StringBuilder ได้หากต้องการ แต่ถ้าชื่อสั้นและฉันเดาว่ามันไม่คุ้ม คุณยังสามารถสร้างวิธีการของคุณเองเพื่อสร้างถ่าน [] และแทนที่อักขระที่ไม่ถูกต้องทั้งหมดในการวนซ้ำครั้งเดียว จะดีกว่าเสมอที่จะทำให้เรียบง่ายเว้นแต่จะไม่ได้ผลคุณอาจมีคอขวดที่แย่กว่านั้น
Diego Jancic

2
InvalidFileNameChars = ถ่านใหม่ [] {'"', '<', '>', '|', '\ 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

9
ความน่าจะเป็นที่จะมีอักขระที่ไม่ถูกต้องที่แตกต่างกัน 2+ ตัวในสตริงนั้นน้อยมากจนต้องคำนึงถึงประสิทธิภาพของสตริงการแทนที่ () นั้นไม่มีจุดหมาย
Serge Wautier

1
ทางออกที่ดีที่น่าสนใจนอกจากนี้ resharper แนะนำ Linq เวอร์ชันนี้: fileName = System.IO.Path.GetInvalidFileNameChars (). Aggregate (fileName, (current, c) => current.Replace (c, '_')); ฉันสงสัยว่ามีการปรับปรุงประสิทธิภาพที่เป็นไปได้หรือไม่ ฉันเก็บต้นฉบับไว้เพื่อจุดประสงค์ในการอ่านเนื่องจากประสิทธิภาพไม่ใช่ข้อกังวลที่สุดของฉัน แต่ถ้าใครสนใจอาจจะคุ้มกว่า
chrispepper1989

1
@AndyM ไม่ต้อง. file.name.txt.pdfเป็น pdf ที่ถูกต้อง Windows อ่านค่าสุดท้าย.ของส่วนขยายเท่านั้น
Diego Jancic

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

อย่างไรก็ตาม ":" ไม่ใช่อักขระที่ผิดกฎหมายเพียงตัวเดียวสำหรับ Windows คุณจะต้องจัดการ:

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

สิ่งเหล่านี้มีอยู่ใน System.IO.Path.GetInvalidFileNameChars ();

นอกจากนี้ (บน Windows), "." ไม่สามารถเป็นอักขระเดียวในชื่อไฟล์ (ทั้ง ".", ".. ", "... " และอื่น ๆ ไม่ถูกต้อง) โปรดใช้ความระมัดระวังในการตั้งชื่อไฟล์ด้วย "." เช่น

echo "test" > .test.

จะสร้างไฟล์ชื่อ ".test"

สุดท้ายหากคุณจริงๆต้องการที่จะทำสิ่งที่ถูกต้องมีบางชื่อไฟล์พิเศษที่คุณต้องมองออกไปสำหรับ ใน Windowsคุณไม่สามารถสร้างไฟล์ชื่อ:

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
ฉันไม่เคยรู้เกี่ยวกับชื่อที่สงวนไว้ ทำให้รู้สึกดี
Greg Dean

4
นอกจากนี้สำหรับสิ่งที่คุ้มค่าคุณไม่สามารถสร้างชื่อไฟล์โดยเริ่มต้นด้วยชื่อที่สงวนไว้เหล่านี้ตามด้วยทศนิยม เช่น con.air.avi
John Conrad

".foo" เป็นชื่อไฟล์ที่ถูกต้อง ไม่ทราบเกี่ยวกับชื่อไฟล์ "CON" - มีไว้เพื่ออะไร
ปรับแต่ง

เกานั่นแหละ CON สำหรับคอนโซล
กำหนดค่า

ขอบคุณผู้กำหนดค่า; ฉันได้อัปเดตคำตอบแล้วคุณถูกต้อง ".foo" ถูกต้อง อย่างไรก็ตาม ".foo." นำไปสู่ผลลัพธ์ที่เป็นไปได้และไม่ต้องการ อัปเดตแล้ว
Phil Price

13

สิ่งนี้ไม่ได้มีประสิทธิภาพมากขึ้น แต่สนุกกว่า :)

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

12

ในกรณีที่ใครต้องการรุ่นที่ปรับให้เหมาะสมStringBuilderให้ใช้สิ่งนี้ รวมเคล็ดลับของ rkagererเป็นตัวเลือก

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 สำหรับรหัสที่ดีและอ่านได้ ทำให้ง่ายต่อการอ่านและสังเกตข้อบกพร่อง: P .. ฟังก์ชันนี้ควรส่งคืนสตริงเดิมเสมอเนื่องจากการเปลี่ยนแปลงจะไม่เป็นจริง
Erti-Chris Eelmaa

ขอบคุณฉันคิดว่าตอนนี้ดีขึ้นแล้ว คุณรู้ไหมว่าพวกเขาพูดอะไรเกี่ยวกับโอเพ่นซอร์ส "ตาจำนวนมากทำให้จุดบกพร่องทั้งหมดตื้นขึ้นดังนั้นฉันไม่ต้องเขียนการทดสอบหน่วย" ...
Qwertie

8

นี่เป็นการบิดเล็กน้อยสำหรับคำตอบของ Diego

หากคุณไม่กลัว Unicode คุณสามารถรักษาความเที่ยงตรงได้มากขึ้นโดยการแทนที่อักขระที่ไม่ถูกต้องด้วยสัญลักษณ์ Unicode ที่ถูกต้องซึ่งมีลักษณะคล้ายกับสัญลักษณ์เหล่านั้น นี่คือรหัสที่ฉันใช้ในโครงการล่าสุดเกี่ยวกับรายการตัดไม้:

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

สิ่งนี้สร้างชื่อไฟล์เช่น1⁄2” spruce.txtแทนที่จะเป็น1_2_ spruce.txt

ใช่มันใช้งานได้จริง:

ตัวอย่าง Explorer

Caveat Emptor

ฉันรู้ว่าเคล็ดลับนี้ใช้ได้กับ NTFS แต่รู้สึกประหลาดใจที่พบว่ามันใช้ได้กับพาร์ติชัน FAT และ FAT32 ด้วย นั่นเป็นเพราะชื่อไฟล์แบบยาวจะถูกเก็บไว้ใน Unicodeแม้ว่าจะย้อนกลับไปถึง Windows 95 / NT ก็ตาม ฉันทดสอบบน Win7, XP และแม้แต่เราเตอร์ที่ใช้ Linux และพวกเขาก็ปรากฏว่าตกลง ไม่สามารถพูดเช่นเดียวกันกับภายใน DOSBox

ที่กล่าวว่าก่อนที่คุณจะเลิกใช้สิ่งนี้ให้พิจารณาว่าคุณต้องการความซื่อสัตย์เป็นพิเศษหรือไม่ ยูนิโค้ดดูลุคอาจสร้างความสับสนให้คนหรือโปรแกรมเก่าเช่นเก่าของ OS อาศัยcodepages



7

ดิเอโกมีวิธีแก้ปัญหาที่ถูกต้อง แต่มีข้อผิดพลาดเล็กน้อยอยู่ในนั้น เวอร์ชันของสตริงการแทนที่ที่ใช้ควรเป็นสตริงแทนที่ (char, char) ไม่มีสตริงแทนที่ (char, string)

ฉันไม่สามารถแก้ไขคำตอบได้หรือฉันเพิ่งทำการเปลี่ยนแปลงเล็กน้อย

ดังนั้นควรเป็น:

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

5

นี่คือเวอร์ชันที่ใช้StringBuilderและIndexOfAnyผนวกรวมเพื่อประสิทธิภาพสูงสุด นอกจากนี้ยังส่งคืนสตริงเดิมแทนที่จะสร้างสตริงที่ซ้ำกัน

สุดท้าย แต่ไม่ท้ายสุดมันมีคำสั่ง switch ที่ส่งคืนอักขระที่เหมือนกันซึ่งคุณสามารถปรับแต่งได้ตามที่คุณต้องการ ตรวจสอบการค้นหาความสับสนของ Unicode.orgเพื่อดูว่าคุณมีตัวเลือกอะไรบ้างขึ้นอยู่กับแบบอักษร

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

มันไม่ได้ตรวจสอบ., ..หรือชื่อที่สงวนชอบCONเพราะมันไม่ชัดเจนว่าการเปลี่ยนที่ควรจะเป็น


3

ทำความสะอาดโค้ดของฉันเล็กน้อยและทำการ refactoring เล็กน้อย ... ฉันสร้างส่วนขยายสำหรับประเภทสตริง:

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

ตอนนี้ใช้งานง่ายขึ้นด้วย:

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

หากคุณต้องการแทนที่ด้วยอักขระอื่นที่ไม่ใช่ "_" คุณสามารถใช้:

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

และคุณสามารถเพิ่มตัวอักษรเพื่อแทนที่ .. ตัวอย่างเช่นคุณไม่ต้องการเว้นวรรคหรือลูกน้ำ:

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

หวังว่ามันจะช่วย ...

ไชโย


3

อีกวิธีง่ายๆ:

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

รหัสง่ายๆเพียงบรรทัดเดียว:

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

คุณสามารถห่อด้วยวิธีการขยายหากคุณต้องการใช้ซ้ำ

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

1

ฉันต้องการระบบที่ไม่สามารถสร้างการชนกันได้ดังนั้นฉันจึงไม่สามารถจับคู่อักขระหลายตัวเป็นตัวเดียวได้ ฉันลงเอยด้วย:

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

ฉันต้องทำวันนี้ ... ในกรณีของฉันฉันต้องเชื่อมชื่อลูกค้ากับวันที่และเวลาสำหรับไฟล์. kmz สุดท้าย ทางออกสุดท้ายของฉันคือ:

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

คุณสามารถทำให้มันแทนที่ช่องว่างได้หากคุณเพิ่มอักขระช่องว่างลงในอาร์เรย์ที่ไม่ถูกต้อง

อาจจะไม่เร็วที่สุด แต่เนื่องจากประสิทธิภาพไม่ใช่ปัญหาฉันจึงพบว่ามันสวยงามและเข้าใจได้

ไชโย!


-2

คุณสามารถทำได้ด้วยsedคำสั่ง:

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

ดูคำถามที่ซับซ้อนกว่า แต่เกี่ยวข้องได้ที่: stackoverflow.com/questions/4413427/…
DW

เหตุใดจึงต้องทำใน C # แทนที่จะเป็น Bash? ตอนนี้ฉันเห็นแท็ก C # ในคำถามเดิม แต่ทำไม?
DW

2
ฉันรู้ใช่มั้ยทำไมไม่เพียงแค่แยกออกจากแอปพลิเคชัน C # ไปยัง Bash ที่อาจไม่ได้ติดตั้งเพื่อทำสิ่งนี้ให้สำเร็จ
Peter Ritchie
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.