C # Sanitize Nome file


174

Recentemente ho spostato un sacco di MP3 da varie posizioni in un repository. Stavo costruendo i nuovi nomi di file usando i tag ID3 (grazie, TagLib-Sharp!), E ho notato che stavo ottenendo un System.NotSupportedException:

"Il formato del percorso specificato non è supportato."

Questo è stato generato da File.Copy()o Directory.CreateDirectory().

Non ci volle molto per capire che i miei nomi di file dovevano essere disinfettati. Quindi ho fatto la cosa ovvia:

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

Con mia sorpresa, ho continuato a ricevere eccezioni. Si è scoperto che ':' non è nell'insieme di Path.GetInvalidPathChars(), perché è valido in una radice del percorso. Suppongo che abbia un senso, ma questo deve essere un problema piuttosto comune. Qualcuno ha qualche codice breve che sanifica un percorso? Il più approfondito che ho pensato a questo, ma sembra che sia probabilmente eccessivo.

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

Qualsiasi miglioramento per rendere questa funzione più veloce e meno barocca sarebbe molto apprezzato.


Risposte:


314

Per ripulire un nome file è possibile farlo

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, "_" );
}

3
La domanda riguardava i percorsi, non i nomi dei file e i caratteri non validi per questi sono diversi.
Dour High Arch,

15
Forse, ma questo codice mi ha sicuramente aiutato quando ho avuto lo stesso problema :)
mmr

8
E un altro utente SO potenzialmente fantastico va a spasso ... Questa funzione è eccezionale. Grazie Adrevdm ...
Dan Rosenstark,

19
Ottimo metodo. Non dimenticare, però, che le parole riservate ti morderanno ancora e rimarrai a grattarti la testa. Fonte: parole riservate al nome file di Wikipedia
Spud,

8
I periodi sono caratteri non validi se si trovano alla fine del nome del file, quindi GetInvalidFileNameCharsnon li includono. Non genera un'eccezione in Windows, le elimina semplicemente, ma potrebbe causare comportamenti imprevisti se ti aspetti che il periodo sia lì. Ho modificato il regex per gestire quel caso in modo .da essere considerato uno dei caratteri non validi se si trova alla fine della stringa.
Scott Chamberlain,

120

Una soluzione più breve:

var invalids = System.IO.Path.GetInvalidFileNameChars();
var newName = String.Join("_", origFileName.Split(invalids, StringSplitOptions.RemoveEmptyEntries) ).TrimEnd('.');

1
@PeterMajeed: TIL che il conteggio delle righe inizia da zero :-)
Gary McGill

Questa è migliore della risposta principale soprattutto per ASP.NET Core che potrebbe restituire caratteri diversi in base alla piattaforma.
Alexei,

79

Basandomi sull'ottima risposta di Andre, ma tenendo conto del commento di Spud sulle parole riservate, ho realizzato questa versione:

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

E questi sono i miei test unitari

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

1
Questa è una risposta estremamente completa, almeno per la parte del nome del file della domanda, e merita più voti.
Brian MacKay,

2
Suggerimento minore poiché sembra che il metodo stia andando in questa direzione: aggiungi una parola chiave e diventa un pratico metodo di estensione. public static String CoerceValidFileName (questo nome file String)
Ryan McArthur

2
Piccolo bug: questo metodo non modifica le parole riservate senza estensioni di file (ad es. COM1), Che sono anche vietate. La soluzione suggerita sarebbe quella di cambiare il riservatoWordPattern in "^{0}(\\.|$)"e la stringa di sostituzione in"_reservedWord_$1"
Dehalion


4

Sto usando il System.IO.Path.GetInvalidFileNameChars() metodo per controllare i caratteri non validi e non ho problemi.

Sto usando il seguente codice:

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

3

Volevo mantenere i personaggi in qualche modo, non semplicemente sostituire il personaggio con un carattere di sottolineatura.

Un modo in cui pensavo fosse quello di sostituire i personaggi con personaggi simili che sono (nella mia situazione), improbabili che vengano usati come personaggi normali. Quindi ho preso la lista dei personaggi non validi e ho trovato dei look-a-like.

Di seguito sono riportate le funzioni per codificare e decodificare con i look-a-like.

Questo codice non include un elenco completo per tutti i caratteri System.IO.Path.GetInvalidFileNameChars (). Quindi spetta a te estendere o utilizzare la sostituzione di sottolineatura per tutti i caratteri rimanenti.

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

Puoi selezionare il tuo look-a-like. Ho usato l'app Mappa caratteri in Windows per selezionare la mia%windir%\system32\charmap.exe

Mentre eseguo le modifiche tramite il rilevamento, aggiornerò questo codice.


nota che ci sono molti personaggi che sembrano più simili a quelli, come la forma a tutta larghezza !"#$%&'()*+,-./:;<=>?@{|}~ o altre forme come /SOLIDUS e `⁄` FRASH SLASH che possono essere usati direttamente nei nomi dei file senza problemi
phuclv

2

Penso che il problema sia che si chiama prima Path.GetDirectoryNamela stringa errata. Se contiene caratteri non-nomefile, .Net non è in grado di dire quali parti della stringa sono directory e lanci. Devi fare un confronto tra stringhe.

Supponendo che sia solo il nome del file a essere errato, non l'intero percorso, prova questo:

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

2

Ho avuto successo con questo in passato.

Bello, corto e statico :-)

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

2

ci sono molte soluzioni funzionanti qui. solo per completezza, ecco un approccio che non usa regex, ma usa LINQ:

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

Inoltre, è una soluzione molto breve;)


1
I love one liner :)
Larry,

1

Ecco un efficiente metodo di estensione del caricamento lento basato sul codice di 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, "_");
        }
    }
}

0

Il tuo codice sarebbe più pulito se aggiungessi la directory e il nome file insieme e lo sanificassi anziché sanificarli in modo indipendente. Per quanto riguarda la sanificazione del:, basta prendere il 2 ° carattere nella stringa. Se è uguale a "replacechar", sostituiscilo con due punti. Poiché questa app è per uso personale, tale soluzione dovrebbe essere perfettamente sufficiente.


-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();
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.