C # Dateinamen bereinigen

174

Ich habe kürzlich eine Reihe von MP3s von verschiedenen Orten in ein Repository verschoben. Ich hatte die neuen Dateinamen mit den ID3-Tags erstellt (danke, TagLib-Sharp!) Und bemerkte, dass ich Folgendes bekam System.NotSupportedException:

"Das Format des angegebenen Pfads wird nicht unterstützt."

Dies wurde entweder von File.Copy()oder generiert Directory.CreateDirectory().

Es dauerte nicht lange, bis mir klar wurde, dass meine Dateinamen bereinigt werden mussten. Also habe ich das Offensichtliche getan:

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

Zu meiner Überraschung bekam ich weiterhin Ausnahmen. Es stellte sich heraus, dass ':' nicht in der Menge von enthalten ist Path.GetInvalidPathChars(), da es in einer Pfadwurzel gültig ist. Ich nehme an, das macht Sinn - aber das muss ein ziemlich häufiges Problem sein. Hat jemand einen Funktionscode, der einen Pfad bereinigt? Das gründlichste, was ich mir ausgedacht habe, aber es fühlt sich wahrscheinlich übertrieben an.

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

Alle Verbesserungen, um diese Funktion schneller und weniger barock zu machen, wären sehr dankbar.

Jason Sundram
quelle

Antworten:

314

Um einen Dateinamen zu bereinigen, können Sie dies tun

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
quelle
3
Die Frage betraf Pfade, keine Dateinamen, und die ungültigen Zeichen für diese sind unterschiedlich.
Dour High Arch
15
Vielleicht, aber dieser Code hat mir sicherlich geholfen, als ich das gleiche Problem hatte :)
mmr
8
Und ein anderer potenziell großartiger SO-Benutzer geht spazieren ... Diese Funktion ist großartig. Vielen Dank Adrevdm ...
Dan Rosenstark
19
Tolle Methode. Vergiss nicht, dass dich reservierte Worte immer noch beißen und du dich am Kopf kratzen wirst. Quelle: Wikipedia Dateiname reservierte Wörter
Spud
8
Punkte sind ungültige Zeichen, wenn sie sich am Ende des Dateinamens befinden und daher GetInvalidFileNameCharsnicht enthalten sind. Es löst keine Ausnahme in Windows aus, es entfernt sie nur, aber es kann zu unerwartetem Verhalten führen, wenn Sie erwarten, dass der Zeitraum dort ist. Ich habe den regulären Ausdruck so geändert, dass dieser Fall behandelt wird, .damit er als eines der ungültigen Zeichen betrachtet wird, wenn er sich am Ende der Zeichenfolge befindet.
Scott Chamberlain
120

Eine kürzere Lösung:

var invalids = System.IO.Path.GetInvalidFileNameChars();
var newName = String.Join("_", origFileName.Split(invalids, StringSplitOptions.RemoveEmptyEntries) ).TrimEnd('.');
DenNukem
quelle
1
@ PeterMajeed: Bis das Zählen der Zeilen bei Null beginnt :-)
Gary McGill
Dies ist besser als die Top-Antwort, insbesondere für ASP.NET Core, das je nach Plattform unterschiedliche Zeichen zurückgeben kann.
Alexei
79

Basierend auf Andres ausgezeichneter Antwort, aber unter Berücksichtigung von Spuds Kommentar zu reservierten Wörtern, habe ich diese Version gemacht:

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

Und das sind meine Unit-Tests

[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);
}
Fiat
quelle
1
Dies ist eine äußerst vollständige Antwort, zumindest auf den Dateinamen der Frage, und verdient mehr Gegenstimmen.
Brian MacKay
2
Kleiner Vorschlag, da es so aussieht, als würde die Methode in diese Richtung gehen: Fügen Sie dieses Schlüsselwort hinzu und es wird zu einer praktischen Erweiterungsmethode. public static String CoerceValidFileName (dieser String-Dateiname)
Ryan McArthur
2
Kleiner Fehler: Diese Methode ändert keine reservierten Wörter ohne Dateierweiterungen (z. B. COM1), die ebenfalls nicht zulässig sind. Vorgeschlagene Lösung wäre, das reservierte "^{0}(\\.|$)""_reservedWord_$1"
Wortmuster
31
string clean = String.Concat(dirty.Split(Path.GetInvalidFileNameChars()));
Daten
quelle
5
Betrachten Sie String.Concat(dirty...)stattJoin(String.Empty...
drzaus
DenNukem hat diese Antwort bereits vorgeschlagen: stackoverflow.com/a/13617375/244916 (dies gilt jedoch auch für den Kommentar).
Dude Pascalou
4

Ich verwende die System.IO.Path.GetInvalidFileNameChars() Methode, um ungültige Zeichen zu überprüfen, und habe keine Probleme.

Ich verwende den folgenden Code:

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

Ich wollte die Zeichen auf irgendeine Weise beibehalten und nicht nur durch einen Unterstrich ersetzen.

Eine Möglichkeit, die ich dachte, bestand darin, die Zeichen durch ähnlich aussehende Zeichen zu ersetzen, die (in meiner Situation) wahrscheinlich nicht als reguläre Zeichen verwendet werden. Also nahm ich die Liste der ungültigen Zeichen und fand Look-a-Likes.

Das Folgende sind Funktionen zum Codieren und Decodieren mit den Look-a-Likes.

Dieser Code enthält keine vollständige Liste aller System.IO.Path.GetInvalidFileNameChars () -Zeichen. Es liegt also an Ihnen, den Unterstrichersatz für alle verbleibenden Zeichen zu erweitern oder zu verwenden.

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

Sie können Ihre eigenen Look-a-Likes auswählen. Ich habe die Character Map App in Windows verwendet, um meine auszuwählen%windir%\system32\charmap.exe

Wenn ich durch Erkennung Anpassungen vornehme, werde ich diesen Code aktualisieren.

Valamas
quelle
Beachten Sie, dass es viele Zeichen gibt, die denen ähnlicher sind, wie die Vollbreitenform !"#$%&'()*+,-./:;<=>?@{|}~ oder andere Formen wie /SOLIDUS und `⁄` FRACTION SLASH, die problemlos direkt in Dateinamen verwendet werden können
phuclv
2

Ich denke, das Problem ist, dass Sie zuerst Path.GetDirectoryNamedie schlechte Zeichenfolge aufrufen . Wenn dies Zeichen ohne Dateinamen enthält, kann .Net nicht erkennen, welche Teile der Zeichenfolge Verzeichnisse und Überwürfe sind. Sie müssen Zeichenfolgenvergleiche durchführen.

Angenommen, nur der Dateiname ist schlecht, nicht der gesamte Pfad, versuchen Sie Folgendes:

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();
}
Dour High Arch
quelle
2

Damit war ich in der Vergangenheit erfolgreich.

Schön, kurz und statisch :-)

    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
quelle
2

Hier gibt es viele funktionierende Lösungen. Der Vollständigkeit halber ist hier ein Ansatz, der keinen regulären Ausdruck verwendet, sondern LINQ:

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

Es ist auch eine sehr kurze Lösung;)

kappadoky
quelle
1
Ich liebe einen Liner :)
Larry
1

Hier ist eine effiziente Methode zum verzögerten Laden von Erweiterungen, die auf Andres Code basiert:

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
quelle
0

Ihr Code wäre sauberer, wenn Sie das Verzeichnis und den Dateinamen zusammenfügen und das bereinigen würden, anstatt sie unabhängig voneinander zu bereinigen. Um das: zu bereinigen, nehmen Sie einfach das 2. Zeichen in der Zeichenfolge. Wenn es gleich "replacechar" ist, ersetzen Sie es durch einen Doppelpunkt. Da diese App für Ihren eigenen Gebrauch bestimmt ist, sollte eine solche Lösung vollkommen ausreichen.

Brian
quelle
-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
quelle