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.
¿Existe algún método que convierta "Foo: Bar" en algo como "Foo-Bar"?
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.
¿Existe algún método que convierta "Foo: Bar" en algo como "Foo-Bar"?
Respuestas:
Intente algo como esto:
string fileName = "something";
foreach (char c in System.IO.Path.GetInvalidFileNameChars())
{
fileName = fileName.Replace(c, '_');
}
Editar:
Dado GetInvalidFileNameChars()
que devolverá 10 o 15 caracteres, es mejor usar a en StringBuilder
lugar de una cadena simple; la versión original tardará más y consumirá más memoria.
file.name.txt.pdf
es un pdf válido. Windows lee solo lo último .
de la extensión.
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:
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.
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>());
En caso de que alguien quiera una versión optimizada basada en StringBuilder
, use esto. Incluye el truco de rkagerer como 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;
}
Aquí hay una versión de la respuesta aceptada usando Linq
which uses Enumerable.Aggregate
:
string fileName = "something";
Path.GetInvalidFileNameChars()
.Aggregate(fileName, (current, c) => current.Replace(c, '_'));
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 in System.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:
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 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 .
Aquí hay una versión que usa StringBuilder
y IndexOfAny
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.
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 busca .
, ..
ni nombres reservados CON
porque no está claro cuál debería ser el reemplazo.
Limpiando un poco mi código y haciendo 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 desea espacios ni comas:
var validFileName = name.ToValidFileName(includeChars: new [] { ' ', ',' });
Espero eso ayude...
Salud
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());
}
Un código simple de una línea:
var validFileName = Path.GetInvalidFileNameChars().Aggregate(fileName, (f, c) => f.Replace(c, '_'));
Puede envolverlo en un método de extensión si desea reutilizarlo.
public static string ToValidFileName(this string fileName) => Path.GetInvalidFileNameChars().Aggregate(fileName, (f, c) => f.Replace(c, '_'));
Necesitaba un sistema que no pudiera crear colisiones, por lo que no podía asignar varios caracteres a uno. Terminé con:
public static class Extension
{
/// <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>
private static readonly HashSet<char> CleanFileNameChars = new HashSet<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>
public static string CleanFileName(this string dirtyFileName)
{
string EscapeHexString(char c) =>
"{" + (c > 255 ? $"{(uint)c:X4}" : $"{(uint)c:X2}") + "}";
return string.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.
¡Salud!
Puedes hacer esto con un sed
comando:
sed -e "
s/[?()\[\]=+<>:;©®”,*|]/_/g
s/"$'\t'"/ /g
s/–/-/g
s/\"/_/g
s/[[:cntrl:]]/_/g"