Pregunta ¿Cómo hacer un nombre de archivo de Windows válido a partir de una cadena arbitraria?


Tengo una cadena como "Foo: Bar" que quiero usar como nombre de archivo, pero en Windows el carácter ":" no está permitido en un nombre de archivo.

¿Hay algún método que convierta a "Foo: Bar" en algo así como "Foo-Bar"?


73
2018-03-06 22:05


origen


Respuestas:


Pruebe algo como esto:

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

Editar:

Ya que GetInvalidFileNameChars() devolverá 10 o 15 caracteres, es mejor usar un StringBuilder en lugar de una cadena simple; la versión original llevará más tiempo y consumirá más memoria.


127
2018-03-06 22:09



fileName = fileName.Replace(":", "-") 

Sin embargo, ":" no es el único carácter ilegal para Windows. También deberá manejar:

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

Estos están contenidos en System.IO.Path.GetInvalidFileNameChars ();

También (en Windows), "." no puede ser el único carácter en el nombre del archivo (tanto ".", "..", "...", etc. no son válidos). Tenga cuidado al nombrar archivos con ".", Por ejemplo:

echo "test" > .test.

Generará un archivo llamado ".test"

Por último, si De Verdad querer hacer las cosas correctamente, hay algunas nombres de archivos especiales tienes que estar atento. En Windows no puedes crear archivos con el nombre:

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.

29
2018-03-06 22:14



Esto no es más eficiente, pero es más divertido :)

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

10
2017-11-10 16:16



En caso de que alguien quiera una versión optimizada basada en StringBuilder, utilizar esta. Incluye el truco de rkagerer como una opción.

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

7
2017-08-09 22:56



Diego tiene la solución correcta, pero hay un pequeño error allí. La versión de string.Replace que se usa debe ser string.Replace (char, char), no hay una cadena.Replace (char, string)

No puedo editar la respuesta o simplemente habría hecho el cambio menor.

Entonces debería ser:

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

6
2017-08-01 08:09



Aquí hay un ligero giro en la respuesta de Diego.

Si no le teme a Unicode, puede conservar un poco más de fidelidad reemplazando los caracteres no válidos con símbolos Unicode válidos que se parecen a ellos. Aquí está el código que utilicé en un proyecto reciente que involucraba a los líderes de la industria maderera:

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

Esto produce nombres de archivo como 1⁄2” spruce.txt en lugar de 1_2_ spruce.txt

Sí, realmente funciona:

Explorer sample

Caveat Emptor

Sabía que este truco funcionaría en NTFS, pero me sorprendió descubrir que también funciona en las particiones FAT y FAT32. Eso es porque nombres de archivos largos son almacenado en Unicodeincluso tan atrás como Windows 95 / NT. Probé en Win7, XP e incluso en un enrutador basado en Linux y aparecieron bien. No puedo decir lo mismo para dentro de un DOSBox.

Dicho esto, antes de volverse loco con esto, considere si realmente necesita la fidelidad adicional. El estilo similar a Unicode podría confundir a personas o programas antiguos, p. OS más antiguos confiando en páginas de códigos.


5
2017-08-01 10:47



Limpiar un poco mi código y hacer una pequeña refactorización ... Creé una extensión para el tipo de cadena:

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

Ahora es más fácil de usar con:

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

Si desea reemplazar con un carácter diferente a "_", puede usar:

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

Y puede agregar caracteres para reemplazar ... por ejemplo, no quiere espacios ni comas:

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

Espero eso ayude...

Aclamaciones


2
2018-05-08 14:20



Aquí hay una versión que usa StringBuilder y IndexOfAny con anexión a granel para una eficiencia total. También devuelve la cadena original en lugar de crear una cadena duplicada.

Por último, pero no menos importante, tiene una declaración de cambio que devuelve caracteres parecidos que puede personalizar de la manera que desee. Revisa Búsqueda confusables de Unicode.org para ver qué opciones puede tener, dependiendo de la fuente.

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

No verifica ., .., o nombres reservados como CON porque no está claro cuál debería ser el reemplazo.


2
2017-12-10 17:48



Aquí hay una versión de la respuesta aceptada usando Linq que usa Enumerable.Aggregate:

string fileName = "something";

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

1
2018-04-05 19:11



Otra solución simple:

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

1
2017-08-01 10:25