C # Sanitasi Nama File

174

Baru-baru ini saya telah memindahkan banyak MP3 dari berbagai lokasi ke repositori. Saya telah membangun nama file baru menggunakan tag ID3 (terima kasih, TagLib-Sharp!), Dan saya perhatikan bahwa saya mendapatkan System.NotSupportedException:

"Format jalur yang diberikan tidak didukung."

Ini dihasilkan oleh salah satu File.Copy()atau Directory.CreateDirectory().

Tidak butuh waktu lama untuk menyadari bahwa nama file saya perlu disanitasi. Jadi saya melakukan hal yang jelas:

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

Yang mengejutkan saya, saya terus mendapatkan pengecualian. Ternyata ':' tidak ada di set Path.GetInvalidPathChars(), karena valid di root path. Saya kira itu masuk akal - tetapi ini harus menjadi masalah yang cukup umum. Adakah yang punya kode pendek yang membersihkan jalur? Yang paling teliti saya datang dengan ini, tapi rasanya seperti itu mungkin berlebihan.

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

Setiap perbaikan untuk membuat fungsi ini lebih cepat dan lebih sedikit barok akan sangat dihargai.

Jason Sundram
sumber

Jawaban:

314

Untuk membersihkan nama file Anda bisa melakukan ini

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, "_" );
}
Andre
sumber
3
Pertanyaannya adalah tentang jalur, bukan nama file, dan karakter yang tidak valid untuk ini berbeda.
Dour High Arch
15
Mungkin, tapi kode ini jelas membantu saya ketika saya punya masalah yang sama :)
MMR
8
Dan pengguna SO lain yang berpotensi besar berjalan ... Fungsi ini hebat. Terima kasih Adrevdm ...
Dan Rosenstark
19
Metode yang bagus. Jangan lupa bahwa kata-kata yang sudah Anda pesan masih akan menggigit Anda, dan Anda akan dibiarkan menggaruk kepala. Sumber: Wikipedia Nama file, kata dilindungi undang-undang
Spud
8
Periode adalah karakter yang tidak valid jika mereka berada di akhir nama file jadi GetInvalidFileNameCharstidak termasuk mereka. Itu tidak melempar pengecualian di windows, itu hanya menghapusnya, tetapi bisa menyebabkan perilaku yang tidak terduga jika Anda mengharapkan periode berada di sana. Saya memodifikasi regex untuk menangani case .agar dianggap salah satu karakter yang tidak valid jika berada di akhir string.
Scott Chamberlain
120

Solusi yang lebih pendek:

var invalids = System.IO.Path.GetInvalidFileNameChars();
var newName = String.Join("_", origFileName.Split(invalids, StringSplitOptions.RemoveEmptyEntries) ).TrimEnd('.');
DenNukem
sumber
1
@PeterMajeed: TIL penghitungan baris dimulai dari nol :-)
Gary McGill
Ini lebih baik daripada jawaban teratas terutama untuk ASP.NET Core yang mungkin mengembalikan karakter berbeda berdasarkan platform.
Alexei
79

Berdasarkan jawaban Andre yang luar biasa tetapi dengan mempertimbangkan komentar Spud tentang kata-kata yang dipesan, saya membuat versi ini:

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

Dan ini adalah unit test saya

[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);
}
perintah
sumber
1
Ini adalah jawaban yang sangat lengkap, setidaknya untuk bagian nama file dari pertanyaan, dan layak mendapat lebih banyak upvotes.
Brian MacKay
2
Saran kecil karena sepertinya metode menuju ke arah ini: Tambahkan kata kunci ini dan itu menjadi metode ekstensi yang berguna. String CoerceValidFileName statis publik (nama file String ini)
Ryan McArthur
2
Bug kecil: metode ini tidak mengubah kata-kata yang dipesan tanpa ekstensi file (mis. COM1), Yang juga tidak diizinkan. Perbaikan yang disarankan adalah untuk mengubah reservedWordPattern ke "^{0}(\\.|$)"dan string pengganti ke"_reservedWord_$1"
Dehalion
31
string clean = String.Concat(dirty.Split(Path.GetInvalidFileNameChars()));
data
sumber
5
pertimbangkan String.Concat(dirty...)alih-alihJoin(String.Empty...
drzaus
DenNukem sudah menyarankan jawaban ini: stackoverflow.com/a/13617375/244916 (pertimbangkan juga komentar yang sama).
Dude Pascalou
4

Saya menggunakan System.IO.Path.GetInvalidFileNameChars() metode ini untuk memeriksa karakter yang tidak valid dan saya tidak punya masalah.

Saya menggunakan kode berikut:

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

Saya ingin mempertahankan karakter dengan cara tertentu, bukan hanya mengganti karakter dengan garis bawah.

Salah satu cara yang saya pikirkan adalah mengganti karakter dengan karakter yang mirip (dalam situasi saya), tidak mungkin digunakan sebagai karakter biasa. Jadi saya mengambil daftar karakter yang tidak valid dan menemukan look-a-likes.

Berikut ini adalah fungsi untuk menyandikan dan mendekode dengan look-a-likes.

Kode ini tidak termasuk daftar lengkap untuk semua karakter System.IO.Path.GetInvalidFileNameChars (). Jadi terserah Anda untuk memperpanjang atau menggunakan pengganti garis bawah untuk karakter yang tersisa.

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

Anda dapat memilih tampilan yang disukai. Saya menggunakan aplikasi Character Map di windows untuk memilih milik saya%windir%\system32\charmap.exe

Saat saya melakukan penyesuaian melalui penemuan, saya akan memperbarui kode ini.

Valamas
sumber
perhatikan bahwa ada banyak karakter yang terlihat lebih mirip dengan itu, seperti bentuk fullwidth !"#$%&'()*+,-./:;<=>?@{|}~ atau bentuk lainnya seperti /SOLIDUS dan `⁄` FRACTION SLASH yang dapat digunakan secara langsung dalam nama file tanpa masalah
phuclv
2

Saya pikir masalahnya adalah Anda pertama kali memanggil Path.GetDirectoryNamestring yang buruk. Jika ini memiliki karakter non-nama file di dalamnya, .Net tidak dapat menentukan bagian string mana yang merupakan direktori dan lemparan. Anda harus melakukan perbandingan string.

Dengan asumsi itu hanya nama file yang buruk, bukan seluruh jalur, coba ini:

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();
}
Lengkungan Tinggi
sumber
2

Saya telah sukses dengan ini di masa lalu.

Bagus, pendek dan statis :-)

    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);
    }
Helix 88
sumber
2

ada banyak solusi yang bekerja di sini. hanya demi kelengkapan, berikut ini adalah pendekatan yang tidak menggunakan regex, tetapi menggunakan LINQ:

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

Juga, ini solusi yang sangat singkat;)

kappadoky
sumber
1
Saya suka satu liners :)
Larry
1

Berikut ini adalah metode ekstensi pemuatan malas yang efisien berdasarkan kode 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, "_");
        }
    }
}
Bryan Legend
sumber
0

Kode Anda akan lebih bersih jika Anda menambahkan direktori dan nama file bersama dan membersihkannya daripada membersihkannya secara mandiri. Sedangkan untuk membersihkan,: ambil saja karakter ke-2 dalam string. Jika sama dengan "replacechar", ganti dengan titik dua. Karena aplikasi ini untuk Anda gunakan sendiri, solusi seperti itu harus cukup memadai.

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