Bagaimana cara membuat nama file Windows yang valid dari string arbitrer?

97

Saya memiliki string seperti "Foo: Bar" yang ingin saya gunakan sebagai nama file, tetapi di Windows karakter ":" tidak diperbolehkan dalam nama file.

Apakah ada metode yang akan mengubah "Foo: Bar" menjadi seperti "Foo- Bar"?

Ken
sumber
1
Saya melakukan hal yang sama hari ini. Saya tidak memeriksa SO untuk beberapa alasan, tetapi tetap menemukan jawabannya.
Aaron Smith

Jawaban:

154

Coba sesuatu seperti ini:

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

Edit:

Karena GetInvalidFileNameChars()akan menghasilkan 10 atau 15 karakter, lebih baik menggunakan a StringBuilderdaripada string sederhana; versi aslinya akan membutuhkan waktu lebih lama dan menggunakan lebih banyak memori.

Diego Jancic
sumber
1
Anda bisa menggunakan StringBuilder jika Anda mau, tetapi jika namanya pendek dan saya rasa itu tidak sepadan. Anda juga bisa membuat metode Anda sendiri untuk membuat karakter [] dan mengganti semua karakter yang salah dalam satu iterasi. Selalu lebih baik untuk tetap sederhana kecuali tidak berhasil, Anda mungkin memiliki leher botol yang lebih buruk
Diego Jancic
2
InvalidFileNameChars = karakter baru [] {'"', '<', '>', '|', '\ 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
Probabilitas untuk memiliki 2+ karakter tidak valid yang berbeda dalam string sangat kecil sehingga peduli dengan kinerja string.Replace () tidak ada gunanya.
Serge Wautier
1
Solusi hebat, selain menarik, resharper menyarankan versi Linq ini: fileName = System.IO.Path.GetInvalidFileNameChars (). Aggregate (fileName, (current, c) => current.Replace (c, '_')); Saya ingin tahu apakah ada kemungkinan peningkatan kinerja di sana. Saya menyimpan yang asli untuk tujuan keterbacaan karena kinerja bukanlah perhatian terbesar saya. Tetapi jika ada yang tertarik, mungkin layak untuk dijadikan tolok ukur
chrispepper1989
1
@Anda Tidak perlu. file.name.txt.pdfadalah pdf yang valid. Windows hanya membaca yang terakhir .untuk ekstensi.
Diego Jancic
33
fileName = fileName.Replace(":", "-") 

Namun ":" bukan satu-satunya karakter ilegal untuk Windows. Anda juga harus menangani:

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

Ini terkandung dalam System.IO.Path.GetInvalidFileNameChars ();

Juga (di Windows), "." tidak bisa menjadi satu-satunya karakter dalam nama file (baik ".", "..", "...", dan seterusnya tidak valid). Berhati-hatilah saat menamai file dengan ".", Misalnya:

echo "test" > .test.

Akan menghasilkan file bernama ".test"

Terakhir, jika Anda benar - benar ingin melakukan sesuatu dengan benar, ada beberapa nama file khusus yang perlu Anda perhatikan. Di Windows, Anda tidak dapat membuat file dengan nama:

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.
Phil Price
sumber
3
Saya tidak pernah tahu tentang nama yang dicadangkan. Masuk akal meskipun
Greg Dean
4
Juga, untuk apa nilainya, Anda tidak dapat membuat nama file yang dimulai dengan salah satu nama yang dicadangkan ini, diikuti dengan desimal. yaitu con.air.avi
John Conrad
".foo" adalah nama file yang valid. Tidak tahu tentang nama file "CON" - untuk apa ini?
konfigurator
Gores itu. CON untuk konsol.
konfigurator
Terima kasih konfigurator; Saya telah memperbarui jawabannya, Anda benar ".foo" valid; namun ".foo." mengarah pada hasil yang mungkin dan tidak diinginkan. Diperbarui.
Phil Price
13

Ini tidak lebih efisien, tapi lebih menyenangkan :)

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

Jika ada yang menginginkan versi yang dioptimalkan berdasarkan StringBuilder, gunakan ini. Termasuk trik rkagerer sebagai opsi.

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;
}
Qwertie
sumber
1 untuk kode yang bagus dan mudah dibaca. Membuat sangat mudah untuk membaca & memperhatikan bug: P .. Fungsi ini harus selalu mengembalikan string asli karena diubah tidak akan pernah benar.
Erti-Chris Eelmaa
Terima kasih, saya pikir sudah lebih baik sekarang. Anda tahu apa yang mereka katakan tentang open source, "banyak mata membuat semua bug menjadi dangkal jadi saya tidak perlu menulis pengujian unit" ...
Qwertie
8

Berikut sedikit perubahan pada jawaban Diego.

Jika Anda tidak takut dengan Unicode, Anda dapat mempertahankan sedikit lebih banyak ketepatan dengan mengganti karakter yang tidak valid dengan simbol Unicode valid yang mirip dengan mereka. Inilah kode yang saya gunakan dalam proyek baru-baru ini yang melibatkan daftar potong kayu:

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

Ini menghasilkan nama file seperti 1⁄2” spruce.txtbukan1_2_ spruce.txt

Ya, ini benar-benar berfungsi:

Contoh penjelajah

Caveat Emptor

Saya tahu trik ini akan berfungsi pada NTFS tetapi saya terkejut menemukannya juga berfungsi pada partisi FAT dan FAT32. Itu karena nama file panjang yang disimpan dalam Unicode , bahkan sejauh sebagai Windows 95 / NT. Saya menguji pada Win7, XP, dan bahkan router berbasis Linux dan mereka muncul dengan baik. Tidak bisa mengatakan hal yang sama untuk di dalam DOSBox.

Karena itu, sebelum Anda menjadi gila dengan ini, pertimbangkan apakah Anda benar-benar membutuhkan kesetiaan ekstra. Mirip dengan Unicode dapat membingungkan orang atau program lama, misalnya OS lama yang mengandalkan halaman kode .

rkagerer
sumber
8

Berikut adalah versi jawaban yang diterima Linqyang menggunakan Enumerable.Aggregate:

string fileName = "something";

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

Diego memang memiliki solusi yang tepat, tetapi ada satu kesalahan kecil di sana. Versi string.Replace yang digunakan harus string.Replace (char, char), tidak ada string. Ganti (char, string)

Saya tidak dapat mengedit jawabannya atau saya akan membuat perubahan kecil.

Jadi seharusnya:

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

Berikut adalah versi yang menggunakan StringBuilderdan IndexOfAnydengan penambahan massal untuk efisiensi penuh. Ini juga mengembalikan string asli daripada membuat string duplikat.

Last but not least, ia memiliki pernyataan switch yang mengembalikan karakter yang mirip yang dapat Anda sesuaikan sesuka Anda. Lihat pencarian membingungkan Unicode.org untuk melihat opsi apa yang mungkin Anda miliki, tergantung pada fontnya.

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

Ini tidak memeriksa ., ..atau nama-nama yang dicadangkan seperti CONkarena tidak jelas apa pengganti harus.

jnm2.dll
sumber
3

Membersihkan sedikit kode saya dan membuat sedikit refactoring ... Saya membuat ekstensi untuk tipe string:

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

Sekarang lebih mudah digunakan dengan:

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

Jika Anda ingin mengganti dengan karakter yang berbeda dari "_", Anda dapat menggunakan:

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

Dan Anda dapat menambahkan karakter untuk diganti .. misalnya Anda tidak ingin spasi atau koma:

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

Semoga membantu ...

Bersulang

Joan Vilariño
sumber
3

Solusi sederhana lainnya:

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

Kode satu baris sederhana:

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

Anda dapat membungkusnya dengan metode ekstensi jika Anda ingin menggunakannya kembali.

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

Saya membutuhkan sistem yang tidak bisa membuat tabrakan jadi saya tidak bisa memetakan banyak karakter menjadi satu. Saya berakhir dengan:

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)));
    }
}
mheyman
sumber
0

Saya perlu melakukan ini hari ini ... dalam kasus saya, saya perlu menggabungkan nama pelanggan dengan tanggal dan waktu untuk file .kmz akhir. Solusi terakhir saya adalah ini:

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

Anda bahkan dapat membuatnya mengganti spasi jika Anda menambahkan spasi char ke array yang tidak valid.

Mungkin ini bukan yang tercepat, tetapi karena kinerja bukanlah masalah, saya menganggapnya elegan dan dapat dimengerti.

Bersulang!

Joan Vilariño
sumber
-2

Anda dapat melakukan ini dengan sedperintah:

 sed -e "
 s/[?()\[\]=+<>:;©®”,*|]/_/g
 s/"$'\t'"/ /g
 s/–/-/g
 s/\"/_/g
 s/[[:cntrl:]]/_/g"
DW
sumber
lihat juga pertanyaan yang lebih rumit namun terkait di: stackoverflow.com/questions/4413427/…
DW
Mengapa ini perlu dilakukan di C # daripada Bash? Sekarang saya melihat tanda C # pada pertanyaan awal, tapi mengapa?
DW
1
Saya tahu, bukan, mengapa tidak keluar saja dari aplikasi C # ke Bash yang mungkin tidak diinstal untuk mencapai ini?
Peter Ritchie