¿Cómo eliminar caracteres ilegales de la ruta y los nombres de archivo?

456

Necesito una forma sólida y simple de eliminar la ruta ilegal y los caracteres de archivo de una cadena simple. He usado el siguiente código pero no parece hacer nada, ¿qué me estoy perdiendo?

using System;
using System.IO;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string illegal = "\"M<>\"\\a/ry/ h**ad:>> a\\/:*?\"<>| li*tt|le|| la\"mb.?";

            illegal = illegal.Trim(Path.GetInvalidFileNameChars());
            illegal = illegal.Trim(Path.GetInvalidPathChars());

            Console.WriteLine(illegal);
            Console.ReadLine();
        }
    }
}
Gary Willoughby
fuente
1
Recortar elimina caracteres del principio y el final de las cadenas. Sin embargo, probablemente debería preguntar por qué los datos no son válidos, y en lugar de intentar desinfectar / corregir los datos, rechace los datos.
user7116
8
Los nombres de estilo Unix no son válidos en Windows y no quiero tratar con nombres cortos 8.3.
Gary Willoughby
GetInvalidFileNameChars()eliminará cosas como: \ etc de las rutas de carpetas.
CAD bloke
1
Path.GetInvalidPathChars()no parece desnudarse *o?
CAD bloke
19
Probé cinco respuestas de esta pregunta (ciclo temporizado de 100,000) y el siguiente método es el más rápido. La expresión regular ocupó el segundo lugar y fue un 25% más lenta: cadena pública GetSafeFilename (string filename) {return string.Join ("_", filename.Split (Path.GetInvalidFileNameChars ())); }
Brain2000

Respuestas:

494

Pruebe algo como esto en su lugar;

string illegal = "\"M\"\\a/ry/ h**ad:>> a\\/:*?\"| li*tt|le|| la\"mb.?";
string invalid = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());

foreach (char c in invalid)
{
    illegal = illegal.Replace(c.ToString(), ""); 
}

Pero tengo que estar de acuerdo con los comentarios, probablemente intente tratar con la fuente de los caminos ilegales, en lugar de tratar de destrozar un camino ilegal en uno legítimo pero probablemente no intencionado.

Editar: O una solución potencialmente 'mejor', usando Regex's.

string illegal = "\"M\"\\a/ry/ h**ad:>> a\\/:*?\"| li*tt|le|| la\"mb.?";
string regexSearch = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
Regex r = new Regex(string.Format("[{0}]", Regex.Escape(regexSearch)));
illegal = r.Replace(illegal, "");

Aún así, la pregunta pide que te pregunten, ¿por qué estás haciendo esto en primer lugar?

Matthew Scharley
fuente
40
No es necesario agregar las dos listas juntas. La lista de caracteres de nombre de archivo ilegal contiene la lista de caracteres de ruta ilegal y tiene algunos más. Aquí están las listas de ambas listas enviadas a int: 34,60,62,124,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16, 17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,58,42,63,92,47 34,60,62,124,0,1,2 3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27 , 28,29,30,31
Sarel Botha
99
@sjbotha, esto puede ser cierto en la implementación de .NET de Windows y Microsoft. No estoy dispuesto a hacer la misma suposición para decir que mono ejecuta Linux.
Matthew Scharley
77
En cuanto a la primera solución. ¿No debería un StringBuilder ser más eficiente que las asignaciones de cadenas?
epignosisx
66
Para lo que vale, @MatthewScharley, la implementación Mono de GetInvalidPathChars () solo devuelve 0x00 y GetInvalidFileNameChars () solo devuelve 0x00 y '/' cuando se ejecuta en plataformas que no son de Windows. En Windows, las listas de caracteres no válidos son mucho más largas y GetInvalidPathChars () está completamente duplicado dentro de GetInvalidFileNameChars (). Esto no va a cambiar en el futuro previsible, por lo que todo lo que realmente está haciendo es duplicar la cantidad de tiempo que esta función tarda en ejecutarse porque le preocupa que la definición de una ruta válida cambie pronto. Que no lo hará.
Warren Rumak
13
@Charleh esta discusión es tan innecesaria ... el código siempre debe optimizarse y no hay riesgo de que esto sea incorrecto. Un nombre de archivo también es parte de la ruta. Por lo tanto, es ilógico que GetInvalidPathChars()pueda contener caracteres que GetInvalidFileNameChars()no lo harían. No está tomando la corrección sobre la optimización "prematura". Simplemente estás usando un código incorrecto.
Stefan Fabian el
355

La pregunta original pedía "eliminar caracteres ilegales":

public string RemoveInvalidChars(string filename)
{
    return string.Concat(filename.Split(Path.GetInvalidFileNameChars()));
}

En su lugar, puede reemplazarlos:

public string ReplaceInvalidChars(string filename)
{
    return string.Join("_", filename.Split(Path.GetInvalidFileNameChars()));    
}

Esta respuesta fue en otro hilo de Ceres , realmente me gusta ordenada y simple.

Shehab Fawzy
fuente
10
Para responder con precisión la pregunta del OP, necesitaría usar "" en lugar de "_", pero su respuesta probablemente se aplique a más de nosotros en la práctica. Creo que reemplazar los caracteres ilegales por algunos legales es más común.
BH
37
Probé cinco métodos a partir de esta pregunta (ciclo temporizado de 100,000) y este método es el más rápido. La expresión regular tomó el segundo lugar y fue un 25% más lenta que este método.
Brain2000
10
Para abordar el comentario de @BH, uno simplemente puede usar string.Concat (name.Split (Path.GetInvalidFileNameChars ()))
Michael Sutton
210

Yo uso Linq para limpiar nombres de archivos. También puede extender esto fácilmente para buscar rutas válidas también.

private static string CleanFileName(string fileName)
{
    return Path.GetInvalidFileNameChars().Aggregate(fileName, (current, c) => current.Replace(c.ToString(), string.Empty));
}

Actualizar

Algunos comentarios indican que este método no funciona para ellos, por lo que he incluido un enlace a un fragmento de DotNetFiddle para que pueda validar el método.

https://dotnetfiddle.net/nw1SWY

Michael Minton
fuente
44
Esto no funcionó para mí. El método no devuelve la cadena limpia. Está devolviendo el nombre de archivo pasado como es.
Karan
Lo que dijo @Karan, esto no funciona, la cadena original vuelve.
Jon
En realidad se puede hacer esto con LINQ como esto sin embargo: var invalid = new HashSet<char>(Path.GetInvalidPathChars()); return new string(originalString.Where(s => !invalid.Contains(s)).ToArray()). El rendimiento probablemente no sea excelente, pero eso probablemente no importe.
Casey
2
@Karan o Jon ¿Qué entrada está enviando esta función? Vea mi edición para verificar este método.
Michael Minton
3
Es fácil: los chicos pasaban cadenas con caracteres válidos. Votado a favor de una solución agregada genial.
Nickmaovich
89

Puede eliminar caracteres ilegales usando Linq de esta manera:

var invalidChars = Path.GetInvalidFileNameChars();

var invalidCharsRemoved = stringWithInvalidChars
.Where(x => !invalidChars.Contains(x))
.ToArray();

EDITAR
Así es como se ve con la edición requerida mencionada en los comentarios:

var invalidChars = Path.GetInvalidFileNameChars();

string invalidCharsRemoved = new string(stringWithInvalidChars
  .Where(x => !invalidChars.Contains(x))
  .ToArray());
Gregor Slavec
fuente
1
Me gusta de esta manera: solo mantiene los caracteres permitidos en la cadena (que no es más que una matriz de caracteres).
Amigo Pascalou
66
Sé que esta es una vieja pregunta, pero esta es una respuesta increíble. Sin embargo, quería agregar que en c # no se puede convertir de char [] a cadena, ya sea implícita o explícitamente (loco, lo sé), por lo que deberá colocarlo en un constructor de cadenas.
JNYRanger
1
No he confirmado esto, pero espero que Path.GetInvalidPathChars () sea un superconjunto de GetInvalidFileNameChars () y cubra tanto los nombres de archivo como las rutas, por lo que probablemente usaría eso en su lugar.
angularsen
3
@anjdreas en realidad Path.GetInvalidPathChars () parece ser un subconjunto de Path.GetInvalidFileNameChars (), no al revés. Path.GetInvalidPathChars () no devolverá '?', Por ejemplo.
Rafael Costa
1
Esta es una buena respuesta. Utilizo la lista de nombre de archivo y la lista de ruta de archivo: ____________________________ string cleanData = new string (data.Where (x =>! Path.GetInvalidFileNameChars (). Contiene (x) &&! Path.GetInvalidPathChars (). Contiene (x)). ToArray ());
goamn
27

Estas son todas excelentes soluciones, pero todas dependen de ellas Path.GetInvalidFileNameChars, que pueden no ser tan confiables como parece. Observe la siguiente observación en la documentación de MSDN sobre Path.GetInvalidFileNameChars:

No se garantiza que la matriz devuelta por este método contenga el conjunto completo de caracteres que no son válidos en los nombres de archivo y directorio. El conjunto completo de caracteres no válidos puede variar según el sistema de archivos. Por ejemplo, en plataformas de escritorio basadas en Windows, los caracteres de ruta no válidos pueden incluir caracteres ASCII / Unicode del 1 al 31, así como comillas ("), menor que (<), mayor que (>), canalización (|), retroceso ( \ b), nulo (\ 0) y tabulador (\ t).

No es mejor con el Path.GetInvalidPathCharsmétodo. Contiene exactamente el mismo comentario.

René
fuente
13
Entonces, ¿cuál es el punto de Path.GetInvalidFileNameChars? Esperaría que devuelva exactamente los caracteres no válidos para el sistema actual, confiando en .NET para saber en qué sistema de archivos estoy corriendo y presentándome los caracteres inválidos adecuados. Si este no es el caso y solo devuelve caracteres codificados, que no son confiables en primer lugar, este método debe eliminarse ya que tiene un valor cero.
Jan
1
Sé que este es un comentario antiguo, pero, @ Jan, es posible que desee escribir en otro sistema de archivos, tal vez es por eso que hay una advertencia.
fantastik78
3
@ fantastik78 buen punto, pero en este caso me gustaría tener un argumento enum adicional para especificar mi FS remoto. Si esto es demasiado esfuerzo de mantenimiento (que es el caso más probable), todo este método sigue siendo una mala idea, ya que le da la impresión equivocada de seguridad.
Jan
1
@ Jan Estoy totalmente de acuerdo contigo, solo estaba discutiendo sobre la advertencia.
fantastik78
Curiosamente, esta es una especie de "listas negras" de caracteres no válidos. ¡¿No sería mejor "incluir en la lista blanca" solo los caracteres válidos conocidos aquí ?! Me recuerda a la estúpida idea de "virusscanner" en lugar de incluir aplicaciones permitidas en la lista blanca ...
Bernhard
26

Para nombres de archivo:

var cleanFileName = string.Join("", fileName.Split(Path.GetInvalidFileNameChars()));

Para caminos completos:

var cleanPath = string.Join("", path.Split(Path.GetInvalidPathChars()));

Tenga en cuenta que si tiene la intención de usar esto como una característica de seguridad, un enfoque más robusto sería expandir todas las rutas y luego verificar que la ruta proporcionada por el usuario sea realmente un elemento secundario de un directorio al que el usuario debería tener acceso.

Lily Finley
fuente
18

Para empezar, Trim solo elimina caracteres del principio o del final de la cadena . En segundo lugar, debe evaluar si realmente desea eliminar los caracteres ofensivos, o fallar rápidamente y dejar que el usuario sepa que su nombre de archivo no es válido. Mi elección es la última, pero mi respuesta al menos debería mostrarle cómo hacer las cosas de la manera correcta e incorrecta:

Pregunta de StackOverflow que muestra cómo verificar si una cadena dada es un nombre de archivo válido . Tenga en cuenta que puede usar la expresión regular de esta pregunta para eliminar caracteres con un reemplazo de expresión regular (si realmente necesita hacer esto).

usuario7116
fuente
Estoy especialmente de acuerdo con el segundo consejo.
OregonGhost
44
Normalmente estaría de acuerdo con el segundo, pero tengo un programa que genera un nombre de archivo y que puede contener caracteres ilegales en algunas situaciones. Como mi programa está generando nombres de archivo ilegales, creo que es apropiado eliminar / reemplazar esos caracteres. (Solo señalando un caso de uso válido)
JDB todavía recuerda a Mónica
16

La mejor manera de eliminar caracteres ilegales de la entrada del usuario es reemplazar los caracteres ilegales usando la clase Regex, crear un método en el código detrás o también validar en el lado del cliente usando el control RegularExpression.

public string RemoveSpecialCharacters(string str)
{
    return Regex.Replace(str, "[^a-zA-Z0-9_]+", "_", RegexOptions.Compiled);
}

O

<asp:RegularExpressionValidator ID="regxFolderName" 
                                runat="server" 
                                ErrorMessage="Enter folder name with  a-z A-Z0-9_" 
                                ControlToValidate="txtFolderName" 
                                Display="Dynamic" 
                                ValidationExpression="^[a-zA-Z0-9_]*$" 
                                ForeColor="Red">
anomepani
fuente
55
En mi humilde opinión, esta solución es mucho mejor que otras En lugar de buscar todos los caracteres no válidos, simplemente defina cuáles son válidos.
igorushi
15

Yo uso expresiones regulares para lograr esto. Primero, construyo dinámicamente la expresión regular.

string regex = string.Format(
                   "[{0}]",
                   Regex.Escape(new string(Path.GetInvalidFileNameChars())));
Regex removeInvalidChars = new Regex(regex, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.CultureInvariant);

Luego llamo a removeInvalidChars.Replace para hacer la búsqueda y reemplazar. Obviamente, esto también se puede extender para cubrir caracteres de ruta.

Jeff Yates
fuente
Extraño, ha estado trabajando para mí. Lo revisaré dos veces cuando tenga la oportunidad. ¿Puede ser más específico y explicar qué es exactamente lo que no funciona para usted?
Jeff Yates
1
No funcionará (correctamente al menos) porque no estás escapando de los caracteres de la ruta correctamente, y algunos de ellos tienen un significado especial. Consulte mi respuesta para saber cómo hacerlo.
Matthew Scharley
@Jeff: su versión es aún mejor que la de Matthew, si la modifica ligeramente. Consulte mi respuesta sobre cómo.
Jan
2
También agregaría algunos otros patrones de nombre de archivo no válidos que se pueden encontrar en MSDN y ampliaría su solución a la siguiente expresión regular:new Regex(String.Format("^(CON|PRN|AUX|NUL|CLOCK\$|COM[1-9]|LPT[1-9])(?=\..|$)|(^(\.+|\s+)$)|((\.+|\s+)$)|([{0}])", Regex.Escape(new String(Path.GetInvalidFileNameChars()))), RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.CultureInvariant);
yar_shukan
13

Prefiero absolutamente la idea de Jeff Yates. Funcionará perfectamente, si lo modifica ligeramente:

string regex = String.Format("[{0}]", Regex.Escape(new string(Path.GetInvalidFileNameChars())));
Regex removeInvalidChars = new Regex(regex, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.CultureInvariant);

La mejora es solo para escapar de la expresión regular generada automáticamente.

ene
fuente
11

Aquí hay un fragmento de código que debería ayudar para .NET 3 y superior.

using System.IO;
using System.Text.RegularExpressions;

public static class PathValidation
{
    private static string pathValidatorExpression = "^[^" + string.Join("", Array.ConvertAll(Path.GetInvalidPathChars(), x => Regex.Escape(x.ToString()))) + "]+$";
    private static Regex pathValidator = new Regex(pathValidatorExpression, RegexOptions.Compiled);

    private static string fileNameValidatorExpression = "^[^" + string.Join("", Array.ConvertAll(Path.GetInvalidFileNameChars(), x => Regex.Escape(x.ToString()))) + "]+$";
    private static Regex fileNameValidator = new Regex(fileNameValidatorExpression, RegexOptions.Compiled);

    private static string pathCleanerExpression = "[" + string.Join("", Array.ConvertAll(Path.GetInvalidPathChars(), x => Regex.Escape(x.ToString()))) + "]";
    private static Regex pathCleaner = new Regex(pathCleanerExpression, RegexOptions.Compiled);

    private static string fileNameCleanerExpression = "[" + string.Join("", Array.ConvertAll(Path.GetInvalidFileNameChars(), x => Regex.Escape(x.ToString()))) + "]";
    private static Regex fileNameCleaner = new Regex(fileNameCleanerExpression, RegexOptions.Compiled);

    public static bool ValidatePath(string path)
    {
        return pathValidator.IsMatch(path);
    }

    public static bool ValidateFileName(string fileName)
    {
        return fileNameValidator.IsMatch(fileName);
    }

    public static string CleanPath(string path)
    {
        return pathCleaner.Replace(path, "");
    }

    public static string CleanFileName(string fileName)
    {
        return fileNameCleaner.Replace(fileName, "");
    }
}
James
fuente
8

La mayoría de las soluciones anteriores combinan caracteres ilegales para la ruta y el nombre de archivo que es incorrecto (incluso cuando ambas llamadas devuelven el mismo conjunto de caracteres). Primero dividiría la ruta + nombre de archivo en ruta y nombre de archivo, luego aplicaría el conjunto apropiado a cualquiera de ellos y luego combinaría los dos nuevamente.

wvd_vegt

wvd_vegt
fuente
+1: Muy cierto. Hoy, trabajando en .NET 4.0, la solución regex de la respuesta principal eliminó todas las barras invertidas en una ruta completa. Así que hice una expresión regular para la ruta del directorio y una expresión regular solo para el nombre del archivo, limpiado por separado y recombinado
dario_ramos
Eso podría ser cierto, pero esto no responde la pregunta. No estoy seguro de que un vago "lo haría así" sea terriblemente útil en comparación con algunas de las soluciones completas que ya están aquí (ver, por ejemplo, la respuesta de Lilly, a continuación)
Ian Grainger
6

Si elimina o reemplaza con un solo carácter los caracteres no válidos, puede tener colisiones:

<abc -> abc
>abc -> abc

Aquí hay un método simple para evitar esto:

public static string ReplaceInvalidFileNameChars(string s)
{
    char[] invalidFileNameChars = System.IO.Path.GetInvalidFileNameChars();
    foreach (char c in invalidFileNameChars)
        s = s.Replace(c.ToString(), "[" + Array.IndexOf(invalidFileNameChars, c) + "]");
    return s;
}

El resultado:

 <abc -> [1]abc
 >abc -> [2]abc
Maxence
fuente
5

Lanza una excepción.

if ( fileName.IndexOfAny(Path.GetInvalidFileNameChars()) > -1 )
            {
                throw new ArgumentException();
            }
mirezus
fuente
4

Escribí este monstruo por diversión, te permite un viaje de ida y vuelta:

public static class FileUtility
{
    private const char PrefixChar = '%';
    private static readonly int MaxLength;
    private static readonly Dictionary<char,char[]> Illegals;
    static FileUtility()
    {
        List<char> illegal = new List<char> { PrefixChar };
        illegal.AddRange(Path.GetInvalidFileNameChars());
        MaxLength = illegal.Select(x => ((int)x).ToString().Length).Max();
        Illegals = illegal.ToDictionary(x => x, x => ((int)x).ToString("D" + MaxLength).ToCharArray());
    }

    public static string FilenameEncode(string s)
    {
        var builder = new StringBuilder();
        char[] replacement;
        using (var reader = new StringReader(s))
        {
            while (true)
            {
                int read = reader.Read();
                if (read == -1)
                    break;
                char c = (char)read;
                if(Illegals.TryGetValue(c,out replacement))
                {
                    builder.Append(PrefixChar);
                    builder.Append(replacement);
                }
                else
                {
                    builder.Append(c);
                }
            }
        }
        return builder.ToString();
    }

    public static string FilenameDecode(string s)
    {
        var builder = new StringBuilder();
        char[] buffer = new char[MaxLength];
        using (var reader = new StringReader(s))
        {
            while (true)
            {
                int read = reader.Read();
                if (read == -1)
                    break;
                char c = (char)read;
                if (c == PrefixChar)
                {
                    reader.Read(buffer, 0, MaxLength);
                    var encoded =(char) ParseCharArray(buffer);
                    builder.Append(encoded);
                }
                else
                {
                    builder.Append(c);
                }
            }
        }
        return builder.ToString();
    }

    public static int ParseCharArray(char[] buffer)
    {
        int result = 0;
        foreach (char t in buffer)
        {
            int digit = t - '0';
            if ((digit < 0) || (digit > 9))
            {
                throw new ArgumentException("Input string was not in the correct format");
            }
            result *= 10;
            result += digit;
        }
        return result;
    }
}
Johan Larsson
fuente
1
Me gusta esto porque evita tener dos cadenas diferentes creando la misma ruta resultante.
Kim
3

Creo que es mucho más fácil validar usando una expresión regular y especificar qué caracteres están permitidos, en lugar de intentar verificar todos los caracteres malos. Consulte estos enlaces: http://www.c-sharpcorner.com/UploadFile/prasad_1/RegExpPSD12062005021717AM/RegExpPSD.aspx http://www.windowsdevcenter.com/pub/a/oreilly/windows/news/csharp_0101.html

Además, haga una búsqueda de "editor de expresiones regulares", que ayudan mucho. Hay algunos que incluso generan el código en C # para usted.

Sandor Davidhazi
fuente
Dado que .net es un marco que está destinado a permitir que los programas se ejecuten en múltiples plataformas (por ejemplo, Linux / Unix y Windows), creo que Path.GetInvalidFileNameChars () es mejor ya que contendrá el conocimiento de lo que es o no es t válido en el sistema de archivos en el que se ejecuta su programa. Incluso si su programa nunca se ejecutará en Linux (tal vez esté lleno de código WPF), siempre existe la posibilidad de que algún nuevo sistema de archivos de Windows aparezca en el futuro y tenga diferentes caracteres válidos / inválidos. Rodar el tuyo con regex es reinventar la rueda y cambiar un problema de plataforma a tu propio código.
Daniel Scott,
Sin embargo, estoy de acuerdo con su consejo sobre editores / probadores de expresiones regulares en línea. Los encuentro invaluables (ya que las expresiones regulares son cosas difíciles y llenas de sutilezas que pueden hacer que te tropieces fácilmente, dándote una expresión regular que se comporta de una manera extremadamente inesperada con casos extremos). Mi favorito es regex101.com (me gusta cómo descompone la expresión regular y le muestra claramente lo que espera que coincida). También me gusta bastante debuggex.com ya que tiene una representación visual compacta de grupos de coincidencias y clases de personajes y demás.
Daniel Scott,
3

Esto parece ser O (n) y no gasta demasiada memoria en cadenas:

    private static readonly HashSet<char> invalidFileNameChars = new HashSet<char>(Path.GetInvalidFileNameChars());

    public static string RemoveInvalidFileNameChars(string name)
    {
        if (!name.Any(c => invalidFileNameChars.Contains(c))) {
            return name;
        }

        return new string(name.Where(c => !invalidFileNameChars.Contains(c)).ToArray());
    }
Alexey F
fuente
1
No creo que sea O (n) cuando usas la función 'Cualquiera'.
II FLECHAS
@IIARROWS y ¿qué es en tu opinión?
Alexey F
No sé, simplemente no me sentí así cuando escribí mi comentario ... ahora que intenté calcularlo, parece que tienes razón.
II FLECHAS
Seleccioné este debido a su consideración de rendimiento. Gracias.
Berend Engelbrecht
3

Al examinar las respuestas aquí, todas ** parecen implicar el uso de una matriz de caracteres de caracteres de nombre de archivo no válidos.

De acuerdo, esto puede ser una micro optimización, pero para el beneficio de cualquiera que esté buscando verificar una gran cantidad de valores para ser nombres de archivo válidos, vale la pena señalar que construir un hashset de caracteres no válidos traerá un rendimiento notablemente mejor.

Me ha sorprendido (sorprendido) en el pasado lo rápido que un hashset (o diccionario) supera el iterar sobre una lista. Con cadenas, es un número ridículamente bajo (aproximadamente 5-7 elementos de la memoria). Con la mayoría de los otros datos simples (referencias de objetos, números, etc.), el crossover mágico parece ser de alrededor de 20 elementos.

Hay 40 caracteres no válidos en la "lista" de Path.InvalidFileNameChars. Hice una búsqueda hoy y hay un punto de referencia bastante bueno aquí en StackOverflow que muestra que el hashset tomará un poco más de la mitad del tiempo de una matriz / lista para 40 elementos: https://stackoverflow.com/a/10762995/949129

Aquí está la clase auxiliar que uso para desinfectar caminos. Ahora olvido por qué tenía la opción de reemplazo elegante, pero está ahí como una bonificación adicional.

Método adicional adicional "IsValidLocalPath" también :)

(** los que no usan expresiones regulares)

public static class PathExtensions
{
    private static HashSet<char> _invalidFilenameChars;
    private static HashSet<char> InvalidFilenameChars
    {
        get { return _invalidFilenameChars ?? (_invalidFilenameChars = new HashSet<char>(Path.GetInvalidFileNameChars())); }
    }


    /// <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 remove bad characters.</param>
    /// <param name="fancyReplacements">TRUE 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, "_" is returned.</returns>
    public static string ToValidFilename(this string text, char? replacement = '_', bool fancyReplacements = false)
    {
        StringBuilder sb = new StringBuilder(text.Length);
        HashSet<char> invalids = InvalidFilenameChars;
        bool changed = false;

        for (int i = 0; i < text.Length; i++)
        {
            char c = text[i];
            if (invalids.Contains(c))
            {
                changed = true;
                char repl = replacement ?? '\0';
                if (fancyReplacements)
                {
                    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;
    }


    /// <summary>
    /// Returns TRUE if the specified path is a valid, local filesystem path.
    /// </summary>
    /// <param name="pathString"></param>
    /// <returns></returns>
    public static bool IsValidLocalPath(this string pathString)
    {
        // From solution at https://stackoverflow.com/a/11636052/949129
        Uri pathUri;
        Boolean isValidUri = Uri.TryCreate(pathString, UriKind.Absolute, out pathUri);
        return isValidUri && pathUri != null && pathUri.IsLoopback;
    }
}
Daniel Scott
fuente
2
public static class StringExtensions
      {
        public static string RemoveUnnecessary(this string source)
        {
            string result = string.Empty;
            string regex = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
            Regex reg = new Regex(string.Format("[{0}]", Regex.Escape(regex)));
            result = reg.Replace(source, "");
            return result;
        }
    }

Puedes usar el método claramente.

aemre
fuente
2

Nombre de archivo no puede contener caracteres de Path.GetInvalidPathChars(), +y #símbolos, y otros nombres específicos. Combinamos todos los cheques en una clase:

public static class FileNameExtensions
{
    private static readonly Lazy<string[]> InvalidFileNameChars =
        new Lazy<string[]>(() => Path.GetInvalidPathChars()
            .Union(Path.GetInvalidFileNameChars()
            .Union(new[] { '+', '#' })).Select(c => c.ToString(CultureInfo.InvariantCulture)).ToArray());


    private static readonly HashSet<string> ProhibitedNames = new HashSet<string>
    {
        @"aux",
        @"con",
        @"clock$",
        @"nul",
        @"prn",

        @"com1",
        @"com2",
        @"com3",
        @"com4",
        @"com5",
        @"com6",
        @"com7",
        @"com8",
        @"com9",

        @"lpt1",
        @"lpt2",
        @"lpt3",
        @"lpt4",
        @"lpt5",
        @"lpt6",
        @"lpt7",
        @"lpt8",
        @"lpt9"
    };

    public static bool IsValidFileName(string fileName)
    {
        return !string.IsNullOrWhiteSpace(fileName)
            && fileName.All(o => !IsInvalidFileNameChar(o))
            && !IsProhibitedName(fileName);
    }

    public static bool IsProhibitedName(string fileName)
    {
        return ProhibitedNames.Contains(fileName.ToLower(CultureInfo.InvariantCulture));
    }

    private static string ReplaceInvalidFileNameSymbols([CanBeNull] this string value, string replacementValue)
    {
        if (value == null)
        {
            return null;
        }

        return InvalidFileNameChars.Value.Aggregate(new StringBuilder(value),
            (sb, currentChar) => sb.Replace(currentChar, replacementValue)).ToString();
    }

    public static bool IsInvalidFileNameChar(char value)
    {
        return InvalidFileNameChars.Value.Contains(value.ToString(CultureInfo.InvariantCulture));
    }

    public static string GetValidFileName([NotNull] this string value)
    {
        return GetValidFileName(value, @"_");
    }

    public static string GetValidFileName([NotNull] this string value, string replacementValue)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            throw new ArgumentException(@"value should be non empty", nameof(value));
        }

        if (IsProhibitedName(value))
        {
            return (string.IsNullOrWhiteSpace(replacementValue) ? @"_" : replacementValue) + value; 
        }

        return ReplaceInvalidFileNameSymbols(value, replacementValue);
    }

    public static string GetFileNameError(string fileName)
    {
        if (string.IsNullOrWhiteSpace(fileName))
        {
            return CommonResources.SelectReportNameError;
        }

        if (IsProhibitedName(fileName))
        {
            return CommonResources.FileNameIsProhibited;
        }

        var invalidChars = fileName.Where(IsInvalidFileNameChar).Distinct().ToArray();

        if(invalidChars.Length > 0)
        {
            return string.Format(CultureInfo.CurrentCulture,
                invalidChars.Length == 1 ? CommonResources.InvalidCharacter : CommonResources.InvalidCharacters,
                StringExtensions.JoinQuoted(@",", @"'", invalidChars.Select(c => c.ToString(CultureInfo.CurrentCulture))));
        }

        return string.Empty;
    }
}

El método GetValidFileNamereemplaza todos los datos incorrectos a _.

Espaldas
fuente
2

Un revestimiento para limpiar la cadena de cualquier carácter ilegal para la denominación de archivos de Windows:

public static string CleanIllegalName(string p_testName) => new Regex(string.Format("[{0}]", Regex.Escape(new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars())))).Replace(p_testName, "");
Zananok
fuente
1
public static bool IsValidFilename(string testName)
{
    return !new Regex("[" + Regex.Escape(new String(System.IO.Path.GetInvalidFileNameChars())) + "]").IsMatch(testName);
}
mbdavis
fuente
0

Esto hará lo que quieras y evitar colisiones

 static string SanitiseFilename(string key)
    {
        var invalidChars = Path.GetInvalidFileNameChars();
        var sb = new StringBuilder();
        foreach (var c in key)
        {
            var invalidCharIndex = -1;
            for (var i = 0; i < invalidChars.Length; i++)
            {
                if (c == invalidChars[i])
                {
                    invalidCharIndex = i;
                }
            }
            if (invalidCharIndex > -1)
            {
                sb.Append("_").Append(invalidCharIndex);
                continue;
            }

            if (c == '_')
            {
                sb.Append("__");
                continue;
            }

            sb.Append(c);
        }
        return sb.ToString();

    }
mcintyre321
fuente
0

Creo que la pregunta ya no está completa ... Las respuestas solo describen el nombre de archivo limpio O la ruta ... no ambas. Aquí está mi solución:

private static string CleanPath(string path)
{
    string regexSearch = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
    Regex r = new Regex(string.Format("[{0}]", Regex.Escape(regexSearch)));
    List<string> split = path.Split('\\').ToList();
    string returnValue = split.Aggregate(string.Empty, (current, s) => current + (r.Replace(s, "") + @"\"));
    returnValue = returnValue.TrimEnd('\\');
    return returnValue;
}
Suplanus
fuente
0

Creé un método de extensión que combina varias sugerencias:

  1. Mantener caracteres ilegales en un conjunto de hash
  2. Filtrar caracteres debajo de ASCII 127. Dado que Path.GetInvalidFileNameChars no incluye todos los caracteres no válidos posibles con códigos ASCII de 0 a 255. Ver aquí y MSDN
  3. Posibilidad de definir el personaje de reemplazo

Fuente:

public static class FileNameCorrector
{
    private static HashSet<char> invalid = new HashSet<char>(Path.GetInvalidFileNameChars());

    public static string ToValidFileName(this string name, char replacement = '\0')
    {
        var builder = new StringBuilder();
        foreach (var cur in name)
        {
            if (cur > 31 && cur < 128 && !invalid.Contains(cur))
            {
                builder.Append(cur);
            }
            else if (replacement != '\0')
            {
                builder.Append(replacement);
            }
        }

        return builder.ToString();
    }
}
Schoetbi
fuente
0

Aquí hay una función que reemplaza todos los caracteres ilegales en un nombre de archivo por un carácter de reemplazo:

public static string ReplaceIllegalFileChars(string FileNameWithoutPath, char ReplacementChar)
{
  const string IllegalFileChars = "*?/\\:<>|\"";
  StringBuilder sb = new StringBuilder(FileNameWithoutPath.Length);
  char c;

  for (int i = 0; i < FileNameWithoutPath.Length; i++)
  {
    c = FileNameWithoutPath[i];
    if (IllegalFileChars.IndexOf(c) >= 0)
    {
      c = ReplacementChar;
    }
    sb.Append(c);
  }
  return (sb.ToString());
}

Por ejemplo, el guión bajo se puede usar como un carácter de reemplazo:

NewFileName = ReplaceIllegalFileChars(FileName, '_');
Hans-Peter Kalb
fuente
Además de la respuesta que ha proporcionado, considere proporcionar una breve explicación de por qué y cómo soluciona el problema.
jtate
-7

O simplemente puedes hacer

[YOUR STRING].Replace('\\', ' ').Replace('/', ' ').Replace('"', ' ').Replace('*', ' ').Replace(':', ' ').Replace('?', ' ').Replace('<', ' ').Replace('>', ' ').Replace('|', ' ').Trim();
Danny Fallas
fuente