Como remover caracteres ilegais do caminho e nomes de arquivos?

Eu preciso de uma maneira robusta e simples para remover o caminho ilegal e os caracteres de arquivo de uma string simples. Eu usei o código abaixo, mas não parece fazer nada, o que estou perdendo?

using System; using System.IO; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { string illegal = "\"M\"\\a/ry/ h**ad:>> a\\/:*?\"| li*tt|le|| la\"mb.?"; illegal = illegal.Trim(Path.GetInvalidFileNameChars()); illegal = illegal.Trim(Path.GetInvalidPathChars()); Console.WriteLine(illegal); Console.ReadLine(); } } } 

Tente algo assim em vez disso;

 string illegal = "\"M\"\\a/ry/ h**ad:>> a\\/:*?\"| li*tt|le|| la\"mb.?"; string invalid = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars()); foreach (char c in invalid) { illegal = illegal.Replace(c.ToString(), ""); } 

Mas eu tenho que concordar com os comentários, eu provavelmente tentaria lidar com a fonte dos caminhos ilegais, ao invés de tentar manejar um caminho ilegal para um caminho legítimo, mas provavelmente não intencional.

Edit: Ou uma solução potencialmente “melhor”, usando o Regex.

 string illegal = "\"M\"\\a/ry/ h**ad:>> a\\/:*?\"| li*tt|le|| la\"mb.?"; string regexSearch = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars()); Regex r = new Regex(string.Format("[{0}]", Regex.Escape(regexSearch))); illegal = r.Replace(illegal, ""); 

Ainda assim, a questão pede para ser perguntado, por que você está fazendo isso em primeiro lugar.

Eu uso o Linq para limpar nomes de arquivos. Você pode facilmente estender isso para verificar caminhos válidos também.

 private static string CleanFileName(string fileName) { return Path.GetInvalidFileNameChars().Aggregate(fileName, (current, c) => current.Replace(c.ToString(), string.Empty)); } 

Atualizar

Alguns comentários indicam que este método não está funcionando para eles, então incluí um link para um snippet DotNetFiddle para que você possa validar o método.

https://dotnetfiddle.net/nw1SWY

 public string GetSafeFilename(string filename) { return string.Join("_", filename.Split(Path.GetInvalidFileNameChars())); } 

Esta resposta foi em outro segmento por Ceres , eu realmente gosto puro e simples.

Você pode remover chars ilegais usando o Linq assim:

 var invalidChars = Path.GetInvalidFileNameChars(); var invalidCharsRemoved = stringWithInvalidChars .Where(x => !invalidChars.Contains(x)) .ToArray(); 

EDITAR
É assim que parece com a edição necessária mencionada nos comentários:

 var invalidChars = Path.GetInvalidFileNameChars(); string invalidCharsRemoved = new string(stringWithInvalidChars .Where(x => !invalidChars.Contains(x)) .ToArray()); 

Todas são ótimas soluções, mas todas dependem do Path.GetInvalidFileNameChars , que pode não ser tão confiável quanto você pensa. Observe a observação a seguir na documentação do MSDN em Path.GetInvalidFileNameChars :

Não é garantido que a matriz retornada desse método contenha o conjunto completo de caracteres inválidos nos nomes de arquivos e diretórios. O conjunto completo de caracteres inválidos pode variar de acordo com o sistema de arquivos. Por exemplo, em plataformas de desktop baseadas em Windows, caracteres de caminho inválidos podem include caracteres ASCII / Unicode 1 a 31, bem como aspas (“), menor que (< ), maior que (>), pipe (|), backspace ( \ b), nulo (\ 0) e tabulação (\ t).

Não é melhor com o método Path.GetInvalidPathChars . Ele contém exatamente o mesmo comentário.

Para iniciantes, Trim só remove caracteres do início ou fim da string . Em segundo lugar, você deve avaliar se realmente deseja remover os caracteres ofensivos ou falhar rapidamente e informar ao usuário que o nome do arquivo é inválido. A minha escolha é a última, mas a minha resposta deve, pelo menos, mostrar-lhe como fazer as coisas da maneira certa e errada:

StackOverflow pergunta mostrando como verificar se uma determinada string é um nome de arquivo válido . Observe que você pode usar o regex desta pergunta para remover caracteres com uma substituição de expressão regular (se realmente precisar fazer isso).

Eu uso expressões regulares para conseguir isso. Primeiro, eu construo dinamicamente o regex.

 string regex = string.Format( "[{0}]", Regex.Escape(new string(Path.GetInvalidFileNameChars()))); Regex removeInvalidChars = new Regex(regex, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.CultureInvariant); 

Então eu apenas chamo removeInvalidChars.Replace para fazer o find e replace. Isso obviamente pode ser estendido para também cobrir os caracteres de caminho.

Eu absolutamente prefiro a ideia de Jeff Yates. Ele funcionará perfeitamente, se você modificá-lo ligeiramente:

 string regex = String.Format("[{0}]", Regex.Escape(new string(Path.GetInvalidFileNameChars()))); Regex removeInvalidChars = new Regex(regex, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.CultureInvariant); 

A melhoria é apenas para escaping do regex gerado automaticamente.

Para nomes de arquivos:

 string cleanFileName = String.Join("", fileName.Split(Path.GetInvalidFileNameChars())); 

Para caminhos completos:

 string cleanPath = String.Join("", path.Split(Path.GetInvalidPathChars())); 

A melhor maneira de remover caracteres ilegais da input do usuário é replace o caractere ilegal usando a class Regex, criar o método no código por trás ou também validar no lado do cliente usando o controle RegularExpression.

 public string RemoveSpecialCharacters(string str) { return Regex.Replace(str, "[^a-zA-Z0-9_]+", "_", RegexOptions.Compiled); } 

OU

  

Aqui está um trecho de código que deve ajudar no .NET 3 e superior.

 using System.IO; using System.Text.RegularExpressions; public static class PathValidation { private static string pathValidatorExpression = "^[^" + string.Join("", Array.ConvertAll(Path.GetInvalidPathChars(), x => Regex.Escape(x.ToString()))) + "]+$"; private static Regex pathValidator = new Regex(pathValidatorExpression, RegexOptions.Compiled); private static string fileNameValidatorExpression = "^[^" + string.Join("", Array.ConvertAll(Path.GetInvalidFileNameChars(), x => Regex.Escape(x.ToString()))) + "]+$"; private static Regex fileNameValidator = new Regex(fileNameValidatorExpression, RegexOptions.Compiled); private static string pathCleanerExpression = "[" + string.Join("", Array.ConvertAll(Path.GetInvalidPathChars(), x => Regex.Escape(x.ToString()))) + "]"; private static Regex pathCleaner = new Regex(pathCleanerExpression, RegexOptions.Compiled); private static string fileNameCleanerExpression = "[" + string.Join("", Array.ConvertAll(Path.GetInvalidFileNameChars(), x => Regex.Escape(x.ToString()))) + "]"; private static Regex fileNameCleaner = new Regex(fileNameCleanerExpression, RegexOptions.Compiled); public static bool ValidatePath(string path) { return pathValidator.IsMatch(path); } public static bool ValidateFileName(string fileName) { return fileNameValidator.IsMatch(fileName); } public static string CleanPath(string path) { return pathCleaner.Replace(path, ""); } public static string CleanFileName(string fileName) { return fileNameCleaner.Replace(fileName, ""); } } 

A maioria das soluções acima combina caracteres ilegais para o caminho e o nome do arquivo, o que está errado (mesmo quando as duas chamadas retornam atualmente o mesmo conjunto de caracteres). Primeiro, dividiria o caminho + nome do arquivo no caminho e no nome do arquivo, depois aplicaria o conjunto apropriado a qualquer um deles e, em seguida, combinaria os dois novamente.

wvd_vegt

Se você remover ou replace por um único caractere os caracteres inválidos, você poderá ter colisões:

  abc >abc -> abc 

Aqui está um método simples para evitar isso:

 public static string ReplaceInvalidFileNameChars(string s) { char[] invalidFileNameChars = System.IO.Path.GetInvalidFileNameChars(); foreach (char c in invalidFileNameChars) s = s.Replace(c.ToString(), "[" + Array.IndexOf(invalidFileNameChars, c) + "]"); return s; } 

O resultado:

   [1]abc >abc -> [2]abc 

Lance uma exceção.

 if ( fileName.IndexOfAny(Path.GetInvalidFileNameChars()) > -1 ) { throw new ArgumentException(); } 

Eu escrevi esse monstro por diversão, ele permite que você percorra:

 public static class FileUtility { private const char PrefixChar = '%'; private static readonly int MaxLength; private static readonly Dictionary Illegals; static FileUtility() { List illegal = new List { PrefixChar }; illegal.AddRange(Path.GetInvalidFileNameChars()); MaxLength = illegal.Select(x => ((int)x).ToString().Length).Max(); Illegals = illegal.ToDictionary(x => x, x => ((int)x).ToString("D" + MaxLength).ToCharArray()); } public static string FilenameEncode(string s) { var builder = new StringBuilder(); char[] replacement; using (var reader = new StringReader(s)) { while (true) { int read = reader.Read(); if (read == -1) break; char c = (char)read; if(Illegals.TryGetValue(c,out replacement)) { builder.Append(PrefixChar); builder.Append(replacement); } else { builder.Append(c); } } } return builder.ToString(); } public static string FilenameDecode(string s) { var builder = new StringBuilder(); char[] buffer = new char[MaxLength]; using (var reader = new StringReader(s)) { while (true) { int read = reader.Read(); if (read == -1) break; char c = (char)read; if (c == PrefixChar) { reader.Read(buffer, 0, MaxLength); var encoded =(char) ParseCharArray(buffer); builder.Append(encoded); } else { builder.Append(c); } } } return builder.ToString(); } public static int ParseCharArray(char[] buffer) { int result = 0; foreach (char t in buffer) { int digit = t - '0'; if ((digit < 0) || (digit > 9)) { throw new ArgumentException("Input string was not in the correct format"); } result *= 10; result += digit; } return result; } } 

Eu acho que é muito mais fácil validar usando um regex e especificar quais caracteres são permitidos, em vez de tentar verificar todos os caracteres ruins. Veja estes links: http://www.c-sharpcorner.com/UploadFile/prasad_1/RegExpPSD12062005021717AM/RegExpPSD.aspx http://www.windowsdevcenter.com/pub/a/oreilly/windows/news/csharp_0101.html

Além disso, faça uma busca por “editor de expressões regulares”, eles ajudam muito. Há alguns em torno dos quais até mesmo o código de saída em c # para você.

Isso parece ser O (n) e não gasta muita memory em seqüências de caracteres:

  private static readonly HashSet invalidFileNameChars = new HashSet(Path.GetInvalidFileNameChars()); public static string RemoveInvalidFileNameChars(string name) { if (!name.Any(c => invalidFileNameChars.Contains(c))) { return name; } return new string(name.Where(c => !invalidFileNameChars.Contains(c)).ToArray()); } 

Analisando as respostas aqui, todas ** parecem envolver o uso de uma matriz char de caracteres de nome de arquivo inválidos.

Concedido, isso pode ser micro-otimização – mas para o benefício de quem pode estar olhando para verificar um grande número de valores para ser nomes de arquivos válidos, é importante notar que a criação de um hashset de caracteres inválidos trará desempenho notavelmente melhor.

Eu fiquei muito surpreso (chocado) no passado com a rapidez com que um hashset (ou dictionary) supera a iteração de uma lista. Com strings, é um número ridiculamente baixo (cerca de 5-7 itens da memory). Com a maioria dos outros dados simples (referências a objects, números, etc.), o cruzamento mágico parece estar em torno de 20 itens.

Existem 40 caracteres inválidos na “lista” Path.InvalidFileNameChars. Fiz uma pesquisa hoje e há uma boa referência aqui no StackOverflow que mostra que o hashset levará um pouco mais da metade do tempo de uma matriz / lista para 40 itens: https://stackoverflow.com/a/10762995/949129

Aqui está a class auxiliar que uso para sanear caminhos. Eu esqueci agora porque eu tinha a opção de substituição fantasia, mas está lá como um bônus bonito.

Método de bônus adicional “IsValidLocalPath” também 🙂

(** aqueles que não usam expressões regulares)

 public static class PathExtensions { private static HashSet _invalidFilenameChars; private static HashSet InvalidFilenameChars { get { return _invalidFilenameChars ?? (_invalidFilenameChars = new HashSet(Path.GetInvalidFileNameChars())); } } /// Replaces characters in text that are not allowed in file names with the /// specified replacement character. /// Text to make into a valid filename. The same string is returned if /// it is valid already. /// Replacement character, or NULL to remove bad characters. /// TRUE to replace quotes and slashes with the non-ASCII characters ” and ⁄. /// A string that can be used as a filename. If the output string would otherwise be empty, "_" is returned. public static string ToValidFilename(this string text, char? replacement = '_', bool fancyReplacements = false) { StringBuilder sb = new StringBuilder(text.Length); HashSet invalids = InvalidFilenameChars; bool changed = false; for (int i = 0; i < text.Length; i++) { char c = text[i]; if (invalids.Contains(c)) { changed = true; char repl = replacement ?? '\0'; if (fancyReplacements) { 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; } ///  /// Returns TRUE if the specified path is a valid, local filesystem path. ///  ///  ///  public static bool IsValidLocalPath(this string pathString) { // From solution at https://stackoverflow.com/a/11636052/949129 Uri pathUri; Boolean isValidUri = Uri.TryCreate(pathString, UriKind.Absolute, out pathUri); return isValidUri && pathUri != null && pathUri.IsLoopback; } } 
 public static class StringExtensions { public static string RemoveUnnecessary(this string source) { string result = string.Empty; string regex = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars()); Regex reg = new Regex(string.Format("[{0}]", Regex.Escape(regex))); result = reg.Replace(source, ""); return result; } } 

Você pode usar o método claramente.

 public static bool IsValidFilename(string testName) { return !new Regex("[" + Regex.Escape(new String(System.IO.Path.GetInvalidFileNameChars())) + "]").IsMatch(testName); } 

O nome do arquivo não pode conter caracteres de Path.GetInvalidPathChars() , + e # símbolos e outros nomes específicos. Nós combinamos todas as verificações em uma class:

 public static class FileNameExtensions { private static readonly Lazy InvalidFileNameChars = new Lazy(() => Path.GetInvalidPathChars() .Union(Path.GetInvalidFileNameChars() .Union(new[] { '+', '#' })).Select(c => c.ToString(CultureInfo.InvariantCulture)).ToArray()); private static readonly HashSet ProhibitedNames = new HashSet { @"aux", @"con", @"clock$", @"nul", @"prn", @"com1", @"com2", @"com3", @"com4", @"com5", @"com6", @"com7", @"com8", @"com9", @"lpt1", @"lpt2", @"lpt3", @"lpt4", @"lpt5", @"lpt6", @"lpt7", @"lpt8", @"lpt9" }; public static bool IsValidFileName(string fileName) { return !string.IsNullOrWhiteSpace(fileName) && fileName.All(o => !IsInvalidFileNameChar(o)) && !IsProhibitedName(fileName); } public static bool IsProhibitedName(string fileName) { return ProhibitedNames.Contains(fileName.ToLower(CultureInfo.InvariantCulture)); } private static string ReplaceInvalidFileNameSymbols([CanBeNull] this string value, string replacementValue) { if (value == null) { return null; } return InvalidFileNameChars.Value.Aggregate(new StringBuilder(value), (sb, currentChar) => sb.Replace(currentChar, replacementValue)).ToString(); } public static bool IsInvalidFileNameChar(char value) { return InvalidFileNameChars.Value.Contains(value.ToString(CultureInfo.InvariantCulture)); } public static string GetValidFileName([NotNull] this string value) { return GetValidFileName(value, @"_"); } public static string GetValidFileName([NotNull] this string value, string replacementValue) { if (string.IsNullOrWhiteSpace(value)) { throw new ArgumentException(@"value should be non empty", nameof(value)); } if (IsProhibitedName(value)) { return (string.IsNullOrWhiteSpace(replacementValue) ? @"_" : replacementValue) + value; } return ReplaceInvalidFileNameSymbols(value, replacementValue); } public static string GetFileNameError(string fileName) { if (string.IsNullOrWhiteSpace(fileName)) { return CommonResources.SelectReportNameError; } if (IsProhibitedName(fileName)) { return CommonResources.FileNameIsProhibited; } var invalidChars = fileName.Where(IsInvalidFileNameChar).Distinct().ToArray(); if(invalidChars.Length > 0) { return string.Format(CultureInfo.CurrentCulture, invalidChars.Length == 1 ? CommonResources.InvalidCharacter : CommonResources.InvalidCharacters, StringExtensions.JoinQuoted(@",", @"'", invalidChars.Select(c => c.ToString(CultureInfo.CurrentCulture)))); } return string.Empty; } } 

O método GetValidFileName substitui todos os dados incorretos para _ .

Isso vai querer que você queira e evite colisões

  static string SanitiseFilename(string key) { var invalidChars = Path.GetInvalidFileNameChars(); var sb = new StringBuilder(); foreach (var c in key) { var invalidCharIndex = -1; for (var i = 0; i < invalidChars.Length; i++) { if (c == invalidChars[i]) { invalidCharIndex = i; } } if (invalidCharIndex > -1) { sb.Append("_").Append(invalidCharIndex); continue; } if (c == '_') { sb.Append("__"); continue; } sb.Append(c); } return sb.ToString(); } 

Acho que a pergunta já não está completa respondida … As respostas só descrevem nome limpo do arquivo OU caminho … não ambos. Aqui está a minha solução:

 private static string CleanPath(string path) { string regexSearch = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars()); Regex r = new Regex(string.Format("[{0}]", Regex.Escape(regexSearch))); List split = path.Split('\\').ToList(); string returnValue = split.Aggregate(string.Empty, (current, s) => current + (r.Replace(s, "") + @"\")); returnValue = returnValue.TrimEnd('\\'); return returnValue; } 

Eu criei um método de extensão que combina várias sugestões:

  1. Segurando caracteres ilegais em um conjunto de hash
  2. Filtrando os caracteres abaixo de ascii 127. Como Path.GetInvalidFileNameChars não inclui todos os caracteres inválidos possíveis com códigos ascii de 0 a 255. Veja aqui e MSDN
  3. Possibilidade de definir o caractere de substituição

Fonte:

 public static class FileNameCorrector { private static HashSet invalid = new HashSet(Path.GetInvalidFileNameChars()); public static string ToValidFileName(this string name, char replacement = '\0') { var builder = new StringBuilder(); foreach (var cur in name) { if (cur > 31 && cur < 128 && !invalid.Contains(cur)) { builder.Append(cur); } else if (replacement != '\0') { builder.Append(replacement); } } return builder.ToString(); } } 

Ou você pode apenas fazer

 [YOUR STRING].Replace('\\', ' ').Replace('/', ' ').Replace('"', ' ').Replace('*', ' ').Replace(':', ' ').Replace('?', ' ').Replace('< ', ' ').Replace('>', ' ').Replace('|', ' ').Trim();