Eu tenho uma string como "Foo: Bar" que quero usar como nome de arquivo, mas no Windows o caractere ":" não é permitido em um nome de arquivo.
Existe um método que transformará "Foo: Bar" em algo como "Foo- Bar"?
Eu tenho uma string como "Foo: Bar" que quero usar como nome de arquivo, mas no Windows o caractere ":" não é permitido em um nome de arquivo.
Existe um método que transformará "Foo: Bar" em algo como "Foo- Bar"?
Respostas:
Experimente algo assim:
string fileName = "something";
foreach (char c in System.IO.Path.GetInvalidFileNameChars())
{
fileName = fileName.Replace(c, '_');
}
Editar:
Como GetInvalidFileNameChars()
retornará 10 ou 15 caracteres, é melhor usar um em StringBuilder
vez de uma string simples; a versão original vai demorar mais e consumir mais memória.
file.name.txt.pdf
é um pdf válido. O Windows lê apenas o último .
da extensão.
fileName = fileName.Replace(":", "-")
No entanto, ":" não é o único caractere ilegal para Windows. Você também terá que lidar com:
/, \, :, *, ?, ", <, > and |
Eles estão contidos em System.IO.Path.GetInvalidFileNameChars ();
Também (no Windows), "." não pode ser o único caractere no nome do arquivo (ambos ".", "..", "..." e assim por diante são inválidos). Tenha cuidado ao nomear arquivos com ".", Por exemplo:
echo "test" > .test.
Irá gerar um arquivo chamado ".test"
Por último, se você realmente deseja fazer as coisas corretamente, existem alguns nomes de arquivos especiais que você precisa observar. No Windows, você não pode criar arquivos chamados:
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.
Isso não é mais eficiente, mas é mais divertido :)
var fileName = "foo:bar";
var invalidChars = System.IO.Path.GetInvalidFileNameChars();
var cleanFileName = new string(fileName.Where(m => !invalidChars.Contains(m)).ToArray<char>());
Caso alguém queira uma versão otimizada com base no StringBuilder
, use-o. Inclui o truque do rkagerer como opção.
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;
}
Esta é uma versão da resposta aceita Linq
que usa Enumerable.Aggregate
:
string fileName = "something";
Path.GetInvalidFileNameChars()
.Aggregate(fileName, (current, c) => current.Replace(c, '_'));
Diego tem a solução correta, mas há um pequeno erro aí. A versão da string.Replace sendo usado deve ser string.Replace (char, char), não há uma string.Replace (char, string)
Não consigo editar a resposta ou teria apenas feito uma pequena alteração.
Portanto, deve ser:
string fileName = "something";
foreach (char c in System.IO.Path.GetInvalidFileNameChars())
{
fileName = fileName.Replace(c, '_');
}
Aqui está uma pequena mudança na resposta de Diego.
Se você não tem medo do Unicode, pode manter um pouco mais de fidelidade substituindo os caracteres inválidos por símbolos Unicode válidos que se assemelham a eles. Aqui está o código que usei em um projeto recente envolvendo listas de corte de madeira:
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;
}
Isso produz nomes de arquivos como em 1⁄2” spruce.txt
vez de1_2_ spruce.txt
Sim, realmente funciona:
Caveat Emptor
Eu sabia que esse truque funcionaria em NTFS, mas fiquei surpreso ao descobrir que também funciona em partições FAT e FAT32. Isso ocorre porque nomes longos de arquivos são armazenados em Unicode , mesmo desde o Windows 95 / NT. Eu testei no Win7, XP e até mesmo em um roteador baseado em Linux e eles mostraram-se OK. Não posso dizer o mesmo para dentro de um DOSBox.
Dito isso, antes de enlouquecer com isso, considere se você realmente precisa de fidelidade extra. As semelhanças com o Unicode podem confundir as pessoas ou programas antigos, por exemplo, sistemas operacionais mais antigos que dependem de páginas de código .
Aqui está uma versão que usa StringBuilder
e IndexOfAny
com acréscimo em massa para eficiência total. Ele também retorna a string original em vez de criar uma string duplicada.
Por último, mas não menos importante, ele tem uma instrução switch que retorna caracteres parecidos que você pode personalizar da maneira que desejar. Confira a pesquisa de confundíveis do Unicode.org para ver quais opções você pode ter, dependendo da fonte.
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();
}
Ele não verifica .
, ..
ou nomes reservados, como CON
porque não está claro o que a substituição deve ser.
Limpando um pouco meu código e fazendo uma pequena refatoração ... Criei uma extensão para tipo de string:
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));
}
Agora é mais fácil de usar com:
var name = "Any string you want using ? / \ or even +.zip";
var validFileName = name.ToValidFileName();
Se você deseja substituir por um caractere diferente de "_", você pode usar:
var validFileName = name.ToValidFileName(replaceChar:'#');
E você pode adicionar caracteres para substituir .. por exemplo, você não quer espaços ou vírgulas:
var validFileName = name.ToValidFileName(includeChars: new [] { ' ', ',' });
Espero que ajude...
Felicidades
Outra solução simples:
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());
}
Um código simples de uma linha:
var validFileName = Path.GetInvalidFileNameChars().Aggregate(fileName, (f, c) => f.Replace(c, '_'));
Você pode envolvê-lo em um método de extensão se quiser reutilizá-lo.
public static string ToValidFileName(this string fileName) => Path.GetInvalidFileNameChars().Aggregate(fileName, (f, c) => f.Replace(c, '_'));
Eu precisava de um sistema que não pudesse criar colisões, então não poderia mapear vários personagens para um. Acabei com:
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)));
}
}
Eu precisava fazer isso hoje ... no meu caso, precisava concatenar o nome de um cliente com a data e a hora para um arquivo .kmz final. Minha solução final foi 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));
Você pode até mesmo fazer com que ele substitua os espaços se adicionar o caractere de espaço ao array inválido.
Talvez não seja o mais rápido, mas como o desempenho não era um problema, achei-o elegante e compreensível.
Felicidades!
Você pode fazer isso com um sed
comando:
sed -e "
s/[?()\[\]=+<>:;©®”,*|]/_/g
s/"$'\t'"/ /g
s/–/-/g
s/\"/_/g
s/[[:cntrl:]]/_/g"