Como remover caracteres ilegais de caminho e nomes de arquivos?
Pergunta
Eu preciso de uma maneira robusta e simples de remover caminho e arquivo ilegal caracteres de uma cadeia simples. Eu usei o código abaixo, mas isso não parece fazer nada, o que eu estou ausente?
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();
}
}
}
Solução
Tente algo parecido com isso em vez;
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 tentar lidar com a fonte dos caminhos ilegais, em vez de tentar mangle um caminho ilegal em uma legítima mas provavelmente não intencional.
Edit: Ou um 'melhor' solução potencialmente, usando de 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, os implora pergunta a ser feita, por que você está fazendo isso em primeiro lugar.
Outras dicas
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.
Eu uso o Linq para limpar nomes de arquivos. Você pode facilmente estender isso para verificar se há caminhos válidos também.
private static string CleanFileName(string fileName)
{
return Path.GetInvalidFileNameChars().Aggregate(fileName, (current, c) => current.Replace(c.ToString(), string.Empty));
}
Atualização
Algumas observações indicam este método não está trabalhando para eles por isso incluímos um link para um DotNetFiddle trecho de modo que você pode validar o método.
Você pode remover caracteres ilegais usando Linq como esta:
var invalidChars = Path.GetInvalidFileNameChars();
var invalidCharsRemoved = stringWithInvalidChars
.Where(x => !invalidChars.Contains(x))
.ToArray();
Editar
Esta é a forma como ele se parece com a edição requerida mencionada nos comentários:
var invalidChars = Path.GetInvalidFileNameChars();
string invalidCharsRemoved = new string(stringWithInvalidChars
.Where(x => !invalidChars.Contains(x))
.ToArray());
Estas são todas as grandes soluções, mas todos eles dependem de Path.GetInvalidFileNameChars
, que pode não ser tão confiável quanto você pensa. Observe a seguinte observação na documentação do MSDN em Path.GetInvalidFileNameChars
:
A matriz retornada a partir deste método é não garantida para conter o conjunto completo de caracteres que são inválidos em nomes de arquivos e diretórios. O conjunto completo de caracteres inválidos pode variar por sistema de arquivos. Por exemplo, em plataformas de desktop baseados no Windows, caracteres de caminho inválido podem incluir caracteres ASCII / Unicode de 1 a 31, bem como aspas ( "), menor que (<), maior que (>), pipe (|), de retrocesso ( \ b), null (\ 0) e separador (\ t).
Não é qualquer melhor com Path.GetInvalidPathChars
método . Ele contém exatamente a mesma observação.
Para nomes de arquivos:
string cleanFileName = String.Join("", fileName.Split(Path.GetInvalidFileNameChars()));
Por caminhos completos:
string cleanPath = String.Join("", path.Split(Path.GetInvalidPathChars()));
Note que, se você pretende usar isso como um recurso de segurança, uma abordagem mais robusta seria expandir todos os caminhos e, em seguida, verifique se o caminho fornecido pelo usuário é de fato um filho de um diretório que o usuário deve ter acesso.
Para começar, guarnição só remove caracteres desde o início ou extremidade da corda . Em segundo lugar, você deve avaliar se você realmente deseja remover os personagens ofensivas, ou falhar rapidamente e que o usuário saiba o seu nome do arquivo é inválido. Minha escolha é o último, mas a minha resposta deve pelo menos mostrar-lhe como fazer as coisas o caminho certo e errado:
StackOverflow questão mostrando como verificar se uma determinada string é um nome de arquivo válido. Note que você pode usar o regex desta questão para remover caracteres com uma substituição de expressão regular (se você realmente precisa para fazer isso).
I usar expressões regulares para alcançar este objectivo. Primeiro, eu construir dinamicamente a 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 chamar removeInvalidChars.Replace para fazer a localizar e substituir. Isso pode, obviamente, ser estendido para caracteres de caminho de cobertura também.
A melhor maneira de remover carácter ilegal da entrada do usuário é substituir caráter ilegal usando a classe Regex, criar método no código por trás ou também que 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
<asp:RegularExpressionValidator ID="regxFolderName"
runat="server"
ErrorMessage="Enter folder name with a-z A-Z0-9_"
ControlToValidate="txtFolderName"
Display="Dynamic"
ValidationExpression="^[a-zA-Z0-9_]*$"
ForeColor="Red">
Eu absolutamente preferem a idéia de Jeff Yates. Ele irá funcionar perfeitamente, se você modificá-lo um pouco:
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 escapar da regex automaticially gerado.
Aqui está um trecho de código que deve ajudar para .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 combinar caracteres ilegais para ambos caminho eo nome que é errado (mesmo quando ambas as chamadas atualmente devolver o mesmo conjunto de caracteres). Gostaria em primeiro lugar dividir o caminho + nome do arquivo no caminho e nome do arquivo, em seguida, aplicar o conjunto apropriado para tanto, se eles e, em seguida, combinar os dois novamente.
wvd_vegt
Se você remover ou substituir com um único caractere os caracteres inválidos, você pode ter colisões:
<abc -> abc
>abc -> abc
Aqui está um método simples para evitar este:
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:
<abc -> [1]abc
>abc -> [2]abc
lançar uma exceção.
if ( fileName.IndexOfAny(Path.GetInvalidFileNameChars()) > -1 )
{
throw new ArgumentException();
}
Eu escrevi este monstro para se divertir, ele permite que você ida e volta:
public static class FileUtility
{
private const char PrefixChar = '%';
private static readonly int MaxLength;
private static readonly Dictionary<char,char[]> Illegals;
static FileUtility()
{
List<char> illegal = new List<char> { 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 para validar usando um regex e specifiing quais caracteres são permitidos, em vez de tentar buscar por todos os personagens maus. 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, fazer uma pesquisa para s "editor de expressões regulares", eles ajudam muito. Há alguns em torno do qual a saída até o código em C # para você.
Este parece ser O (n) e não gastar muita memória em strings:
private static readonly HashSet<char> invalidFileNameChars = new HashSet<char>(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());
}
A varredura sobre as respostas aqui, todos eles ** parecem envolver a utilização de uma matriz de char de caracteres de nome de arquivo inválidos.
Com certeza, isso pode ser micro-otimização - mas para o benefício de qualquer pessoa que possa estar olhando para verificar um grande número de valores por ser nomes de arquivos válidos, é importante notar que a construção de um hashset de caracteres inválidos trará nomeadamente um melhor desempenho .
I foram muito surpreso (chocado) no passado o quão rapidamente um HashSet (ou dicionário) Supera iteração sobre uma lista. Com cordas, é um número ridiculamente baixo (cerca de 5-7 itens de memória). Com a maioria dos outros dados simples (referências a objetos, números etc) o crossover magia parece ser cerca de 20 itens.
Existem 40 caracteres inválidos no "lista" Path.InvalidFileNameChars. Fiz uma pesquisa hoje e há muito uma boa referência aqui na StackOverflow, que mostra a hashset vai demorar um pouco mais de metade do tempo de uma matriz / lista para 40 itens: https://stackoverflow.com/a/10762995/949129
Aqui está o uso I classe auxiliar para higienização caminhos. Eu esqueço agora porque eu tinha a opção de substituição fantasia nele, mas está lá como um bônus bonito.
método bônus adicional "IsValidLocalPath" demasiado:)
(** aqueles que não usam expressões regulares)
public static class PathExtensions
{
private static HashSet<char> _invalidFilenameChars;
private static HashSet<char> InvalidFilenameChars
{
get { return _invalidFilenameChars ?? (_invalidFilenameChars = new HashSet<char>(Path.GetInvalidFileNameChars())); }
}
/// <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 remove bad characters.</param>
/// <param name="fancyReplacements">TRUE 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, "_" is returned.</returns>
public static string ToValidFilename(this string text, char? replacement = '_', bool fancyReplacements = false)
{
StringBuilder sb = new StringBuilder(text.Length);
HashSet<char> 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;
}
/// <summary>
/// Returns TRUE if the specified path is a valid, local filesystem path.
/// </summary>
/// <param name="pathString"></param>
/// <returns></returns>
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.
O arquivo não pode conter caracteres de Path.GetInvalidPathChars()
, +
e símbolos #
, e outros nomes específicos. Nós combinamos todos os cheques em uma classe:
public static class FileNameExtensions
{
private static readonly Lazy<string[]> InvalidFileNameChars =
new Lazy<string[]>(() => Path.GetInvalidPathChars()
.Union(Path.GetInvalidFileNameChars()
.Union(new[] { '+', '#' })).Select(c => c.ToString(CultureInfo.InvariantCulture)).ToArray());
private static readonly HashSet<string> ProhibitedNames = new HashSet<string>
{
@"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;
}
}
Método GetValidFileName
substitui todos os dados incorretos para _
.
Um forro para string limpeza de quaisquer caracteres ilegais para nomeação de arquivos do Windows:
public static string CleanIllegalName(string p_testName) => new Regex(string.Format("[{0}]", Regex.Escape(new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars())))).Replace(p_testName, "");
public static bool IsValidFilename(string testName)
{
return !new Regex("[" + Regex.Escape(new String(System.IO.Path.GetInvalidFileNameChars())) + "]").IsMatch(testName);
}
Isso vai fazer o que quer, e colisões Evitar
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();
}
Eu acho que a questão já não cheio respondeu ... As respostas só descrever nome limpo 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<string> 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:
- Dadas caracteres ilegais em um conjunto de hash
- A filtragem de caracteres abaixo ASCII 127. Desde Path.GetInvalidFileNameChars não inclui todos os caracteres inválidos possíveis com códigos ASCII de 0 a 255. Veja aqui e MSDN
- possibilidade de se definir o caráter de substituição
Fonte:
public static class FileNameCorrector
{
private static HashSet<char> invalid = new HashSet<char>(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();