Eu fiz a mesma coisa hoje. Não marquei SO por algum motivo, mas encontrei a resposta mesmo assim.
Aaron Smith
Respostas:
153
Experimente algo assim:
string fileName ="something";foreach(char c inSystem.IO.Path.GetInvalidFileNameChars()){
fileName = fileName.Replace(c,'_');}
Editar:
Como GetInvalidFileNameChars()retornará 10 ou 15 caracteres, é melhor usar um em StringBuildervez de uma string simples; a versão original vai demorar mais e consumir mais memória.
Você poderia usar um StringBuilder se desejar, mas se os nomes forem curtos e eu acho que não vale a pena. Você também pode criar seu próprio método para criar um char [] e substituir todos os caracteres errados em uma iteração. É sempre melhor manter as coisas simples, a menos que não funcione, você pode ter gargalos de garrafa piores
A probabilidade de ter 2+ caracteres inválidos diferentes na string é tão pequena que se preocupar com o desempenho da string.Replace () é inútil.
Serge Wautier,
1
Ótima solução, interessante à parte, resharper sugeriu esta versão do Linq: fileName = System.IO.Path.GetInvalidFileNameChars (). Aggregate (fileName, (current, c) => current.Replace (c, '_')); Eu me pergunto se há alguma melhoria de desempenho possível lá. Eu mantive o original para fins de legibilidade, pois o desempenho não é minha maior preocupação. Mas se alguém estiver interessado, pode valer a pena fazer um benchmarking
chrispepper1989
1
@AndyM Não precisa. file.name.txt.pdfé um pdf válido. O Windows lê apenas o último .da extensão.
Diego Jancic
33
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:
Além disso, você não pode criar um nome de arquivo começando com um desses nomes reservados, seguido por um decimal. ou seja, con.air.avi
John Conrad
".foo" é um nome de arquivo válido. Não sabia sobre o nome do arquivo "CON" - para que ele serve?
configurador
Risca isso. CON é para console.
configurador
Obrigado configurador; Eu atualizei a resposta, você está correto ".foo" é válido; no entanto, ".foo." leva a resultados possíveis e indesejados. Atualizada.
Phil Price,
13
Isso não é mais eficiente, mas é mais divertido :)
var fileName ="foo:bar";var invalidChars =System.IO.Path.GetInvalidFileNameChars();var cleanFileName =newstring(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.
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 bom e legível. Facilita a leitura e a observação dos bugs: P .. Esta função deve retornar sempre a string original, pois a alteração nunca será verdadeira.
Erti-Chris Eelmaa
Obrigado, acho que está melhor agora. Você sabe o que dizem sobre o código aberto, "muitos olhos tornam todos os bugs superficiais, então não tenho que escrever testes de unidade" ...
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 inSystem.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:
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;}
Isso produz nomes de arquivos como em 1⁄2” spruce.txtvez 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 StringBuildere IndexOfAnycom 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.
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();}
Ele não verifica ., ..ou nomes reservados, como CONporque não está claro o que a substituição deve ser.
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:
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)));}}
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.
Respostas:
Experimente algo assim:
Editar:
Como
GetInvalidFileNameChars()
retornará 10 ou 15 caracteres, é melhor usar um emStringBuilder
vez de uma string simples; a versão original vai demorar mais e consumir mais memória.fonte
file.name.txt.pdf
é um pdf válido. O Windows lê apenas o último.
da extensão.No entanto, ":" não é o único caractere ilegal para Windows. Você também terá que lidar com:
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:
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:
fonte
Isso não é mais eficiente, mas é mais divertido :)
fonte
Caso alguém queira uma versão otimizada com base no
StringBuilder
, use-o. Inclui o truque do rkagerer como opção.fonte
Esta é uma versão da resposta aceita
Linq
que usaEnumerable.Aggregate
:fonte
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:
fonte
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:
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 .
fonte
Aqui está uma versão que usa
StringBuilder
eIndexOfAny
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.
Ele não verifica
.
,..
ou nomes reservados, comoCON
porque não está claro o que a substituição deve ser.fonte
Limpando um pouco meu código e fazendo uma pequena refatoração ... Criei uma extensão para tipo de string:
Agora é mais fácil de usar com:
Se você deseja substituir por um caractere diferente de "_", você pode usar:
E você pode adicionar caracteres para substituir .. por exemplo, você não quer espaços ou vírgulas:
Espero que ajude...
Felicidades
fonte
Outra solução simples:
fonte
Um código simples de uma linha:
Você pode envolvê-lo em um método de extensão se quiser reutilizá-lo.
fonte
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:
fonte
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:
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!
fonte
Você pode fazer isso com um
sed
comando:fonte