Hice lo mismo hoy. No marqué SO por alguna razón, pero encontré la respuesta de todos modos.
Aaron Smith
Respuestas:
153
Intente algo como esto:
string fileName ="something";foreach(char c inSystem.IO.Path.GetInvalidFileNameChars()){
fileName = fileName.Replace(c,'_');}
Editar:
Dado GetInvalidFileNameChars()que devolverá 10 o 15 caracteres, es mejor usar a en StringBuilderlugar de una cadena simple; la versión original tardará más y consumirá más memoria.
Podría usar un StringBuilder si lo desea, pero si los nombres son cortos y supongo que no vale la pena. También puede crear su propio método para crear un carácter [] y reemplazar todos los caracteres incorrectos en una iteración. Siempre es mejor mantenerlo simple a menos que no funcione, es posible que tenga peores cuellos de botella
La probabilidad de tener 2+ caracteres inválidos diferentes en la cadena es tan pequeña que preocuparse por el rendimiento de string.Replace () no tiene sentido.
Serge Wautier
1
Gran solución, aparte de interesante, resharper sugirió esta versión de Linq: fileName = System.IO.Path.GetInvalidFileNameChars (). Aggregate (fileName, (current, c) => current.Replace (c, '_')); Me pregunto si hay posibles mejoras de rendimiento allí. He conservado el original por motivos de legibilidad, ya que el rendimiento no es mi mayor preocupación. Pero si alguien está interesado, podría valer la pena hacer una evaluación comparativa
chrispepper1989
1
@AndyM No es necesario. file.name.txt.pdfes un pdf válido. Windows lee solo lo último .de la extensión.
Diego Jancic
33
fileName = fileName.Replace(":","-")
Sin embargo, ":" no es el único carácter ilegal para Windows. También tendrás que 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 de 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 realmente desea hacer las cosas correctamente, hay algunos nombres de archivo especiales que debe tener en cuenta. En Windows no puede crear archivos con el nombre:
Además, por si sirve de algo, no puede crear un nombre de archivo que comience con uno de estos nombres reservados, seguido de un decimal. ie con.air.avi
John Conrad
".foo" es un nombre de archivo válido. No conocía el nombre de archivo "CON", ¿para qué sirve?
configurador
Rasca eso. CON es para consola.
configurador
Gracias configurador; He actualizado la respuesta, estás en lo correcto ".foo" es válido; sin embargo ".foo". conduce a posibles resultados no deseados. Actualizado.
Phil Price
13
Esto no es más eficiente, pero es más divertido :)
var fileName ="foo:bar";var invalidChars =System.IO.Path.GetInvalidFileNameChars();var cleanFileName =newstring(fileName.Where(m =>!invalidChars.Contains(m)).ToArray<char>());
En caso de que alguien quiera una versión optimizada basada en StringBuilder, use esto. Incluye el truco de rkagerer como opción.
staticchar[] _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>publicstaticstringMakeValidFileName(string text,char? replacement ='_',bool fancy =true){StringBuilder sb =newStringBuilder(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 markelseif(c =='\'') repl ='’';// U+2019 right single quotation markelseif(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;}
+1 para código agradable y legible. Hace muy fácil de leer y notar los errores: P .. Esta función debe devolver siempre la cadena original ya que cambiada nunca será verdadera.
Erti-Chris Eelmaa
Gracias, creo que ahora está mejor. Ya sabes lo que dicen sobre el código abierto, "muchos ojos hacen que todos los errores sean superficiales, así que no tengo que escribir pruebas unitarias" ...
Qwertie
8
Aquí hay una versión de la respuesta aceptada usando Linqwhich uses Enumerable.Aggregate:
Diego tiene la solución correcta, pero hay un pequeño error ahí. La versión de string.Replace que se está utilizando debe ser string.Replace (char, char), no hay una cadena.Replace (char, string)
No puedo editar la respuesta o simplemente habría hecho un pequeño cambio.
Entonces debería ser:
string fileName ="something";foreach(char c inSystem.IO.Path.GetInvalidFileNameChars()){
fileName = fileName.Replace(c,'_');}
Aquí hay un pequeño 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 parezcan a ellos. Aquí está el código que utilicé en un proyecto reciente que involucraba listas de corte de madera:
staticstringMakeValidFilename(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 slashforeach(char c inSystem.IO.Path.GetInvalidFileNameChars()){
text = text.Replace(c,'_');}return text;}
Esto produce nombres de archivo como en 1⁄2” spruce.txtlugar de1_2_ spruce.txt
Sí, realmente funciona:
Caveat Emptor
Sabía que este truco funcionaría en NTFS, pero me sorprendió descubrir que también funciona en particiones FAT y FAT32. Eso es porque los nombres de archivo largos se almacenan en Unicode , incluso desde hace mucho tiempo. Windows 95 / NT. Probé en Win7, XP e incluso en un enrutador basado en Linux y salieron bien. No puedo decir lo mismo dentro de un DOSBox.
Dicho esto, antes de volverse loco con esto, considere si realmente necesita la fidelidad adicional. Los imitaciones de Unicode podrían confundir a las personas o los programas antiguos, por ejemplo, los sistemas operativos más antiguos que se basan en páginas de códigos .
Aquí hay una versión que usa StringBuildery IndexOfAnycon agregación masiva 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 personajes similares que puede personalizar de la forma que desee. Consulte la búsqueda de confusables de Unicode.org para ver qué opciones puede tener, según la fuente.
publicstaticstringGetSafeFilename(string arbitraryString){var invalidChars =System.IO.Path.GetInvalidFileNameChars();var replaceIndex = arbitraryString.IndexOfAny(invalidChars,0);if(replaceIndex ==-1)return arbitraryString;var r =newStringBuilder();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 busca ., ..ni nombres reservados CONporque no está claro cuál debería ser el reemplazo.
Necesitaba un sistema que no pudiera crear colisiones, por lo que no podía asignar varios caracteres a uno. Terminé con:
publicstaticclassExtension{/// <summary>/// Characters allowed in a file name. Note that curly braces don't show up here/// becausee they are used for escaping invalid characters./// </summary>privatestaticreadonlyHashSet<char>CleanFileNameChars=newHashSet<char>{' ','!','#','$','%','&','\'','(',')','+',',','-','.','0','1','2','3','4','5','6','7','8','9','=','@','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','[',']','^','_','`','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',};/// <summary>/// Creates a clean file name from one that may contain invalid characters in /// a way that will not collide./// </summary>/// <param name="dirtyFileName">/// The file name that may contain invalid filename characters./// </param>/// <returns>/// A file name that does not contain invalid filename characters./// </returns>/// <remarks>/// <para>/// Escapes invalid characters by converting their ASCII values to hexadecimal/// and wrapping that value in curly braces. Curly braces are escaped by doubling/// them, for example '{' => "{{"./// </para>/// <para>/// Note that although NTFS allows unicode characters in file names, this/// method does not./// </para>/// </remarks>publicstaticstringCleanFileName(thisstring dirtyFileName){stringEscapeHexString(char c)=>"{"+(c >255? $"{(uint)c:X4}": $"{(uint)c:X2}")+"}";returnstring.Join(string.Empty,
dirtyFileName.Select(
c =>
c =='{'?"{{":
c =='}'?"}}":CleanFileNameChars.Contains(c)? $"{c}":EscapeHexString(c)));}}
Necesitaba hacer esto hoy ... en mi caso, necesitaba concatenar el nombre de un cliente con la fecha y la hora para un archivo .kmz final. Mi solución final fue esta:
string name ="Whatever name with valid/invalid chars";char[] invalid =System.IO.Path.GetInvalidFileNameChars();string validFileName =string.Join(string.Empty,string.Format("{0}.{1:G}.kmz", name,DateTime.Now).ToCharArray().Select(o => o.In(invalid)?'_': o));
Incluso puede hacer que reemplace espacios si agrega el carácter de espacio a la matriz no válida.
Quizás no sea el más rápido, pero como el rendimiento no fue un problema, lo encontré elegante y comprensible.
Respuestas:
Intente algo como esto:
Editar:
Dado
GetInvalidFileNameChars()
que devolverá 10 o 15 caracteres, es mejor usar a enStringBuilder
lugar de una cadena simple; la versión original tardará más y consumirá más memoria.fuente
file.name.txt.pdf
es un pdf válido. Windows lee solo lo último.
de la extensión.Sin embargo, ":" no es el único carácter ilegal para Windows. También tendrás que manejar:
Estos están contenidos en System.IO.Path.GetInvalidFileNameChars ();
También (en Windows), "." no puede ser el único carácter en el nombre de archivo (tanto ".", "..", "...", etc. no son válidos). Tenga cuidado al nombrar archivos con ".", Por ejemplo:
Generará un archivo llamado ".test"
Por último, si realmente desea hacer las cosas correctamente, hay algunos nombres de archivo especiales que debe tener en cuenta. En Windows no puede crear archivos con el nombre:
fuente
Esto no es más eficiente, pero es más divertido :)
fuente
En caso de que alguien quiera una versión optimizada basada en
StringBuilder
, use esto. Incluye el truco de rkagerer como opción.fuente
Aquí hay una versión de la respuesta aceptada usando
Linq
which usesEnumerable.Aggregate
:fuente
Diego tiene la solución correcta, pero hay un pequeño error ahí. La versión de string.Replace que se está utilizando debe ser string.Replace (char, char), no hay una cadena.Replace (char, string)
No puedo editar la respuesta o simplemente habría hecho un pequeño cambio.
Entonces debería ser:
fuente
Aquí hay un pequeño 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 parezcan a ellos. Aquí está el código que utilicé en un proyecto reciente que involucraba listas de corte de madera:
Esto produce nombres de archivo como en
1⁄2” spruce.txt
lugar de1_2_ spruce.txt
Sí, realmente funciona:
Caveat Emptor
Sabía que este truco funcionaría en NTFS, pero me sorprendió descubrir que también funciona en particiones FAT y FAT32. Eso es porque los nombres de archivo largos se almacenan en Unicode , incluso desde hace mucho tiempo. Windows 95 / NT. Probé en Win7, XP e incluso en un enrutador basado en Linux y salieron bien. No puedo decir lo mismo dentro de un DOSBox.
Dicho esto, antes de volverse loco con esto, considere si realmente necesita la fidelidad adicional. Los imitaciones de Unicode podrían confundir a las personas o los programas antiguos, por ejemplo, los sistemas operativos más antiguos que se basan en páginas de códigos .
fuente
Aquí hay una versión que usa
StringBuilder
yIndexOfAny
con agregación masiva 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 personajes similares que puede personalizar de la forma que desee. Consulte la búsqueda de confusables de Unicode.org para ver qué opciones puede tener, según la fuente.
No busca
.
,..
ni nombres reservadosCON
porque no está claro cuál debería ser el reemplazo.fuente
Limpiando un poco mi código y haciendo una pequeña refactorización ... Creé una extensión para el tipo de cadena:
Ahora es más fácil de usar con:
Si desea reemplazar con un carácter diferente a "_", puede usar:
Y puede agregar caracteres para reemplazar ... por ejemplo, no desea espacios ni comas:
Espero eso ayude...
Salud
fuente
Otra solución simple:
fuente
Un código simple de una línea:
Puede envolverlo en un método de extensión si desea reutilizarlo.
fuente
Necesitaba un sistema que no pudiera crear colisiones, por lo que no podía asignar varios caracteres a uno. Terminé con:
fuente
Necesitaba hacer esto hoy ... en mi caso, necesitaba concatenar el nombre de un cliente con la fecha y la hora para un archivo .kmz final. Mi solución final fue esta:
Incluso puede hacer que reemplace espacios si agrega el carácter de espacio a la matriz no válida.
Quizás no sea el más rápido, pero como el rendimiento no fue un problema, lo encontré elegante y comprensible.
¡Salud!
fuente
Puedes hacer esto con un
sed
comando:fuente