Como posso verificar se uma determinada string é um nome de arquivo legal/válido no Windows?

StackOverflow https://stackoverflow.com/questions/62771

  •  09-06-2019
  •  | 
  •  

Pergunta

Quero incluir uma funcionalidade de renomeação de arquivo em lote em meu aplicativo.Um usuário pode digitar um padrão de nome de arquivo de destino e (depois de substituir alguns curingas no padrão) preciso verificar se será um nome de arquivo legal no Windows.Eu tentei usar expressões regulares como [a-zA-Z0-9_]+ mas não inclui muitos caracteres específicos de cada país de vários idiomas (por exemplo,tremas e assim por diante).Qual é a melhor maneira de fazer essa verificação?

Foi útil?

Solução

Você pode obter uma lista de caracteres inválidos em Path.GetInvalidPathChars e GetInvalidFileNameChars.

Atualização: Ver Sugestão de Steve Cooper sobre como usá-los em uma expressão regular.

UPD2: Observe que, de acordo com a seção Comentários no MSDN "Não é garantido que a matriz retornada deste método contenha o conjunto completo de caracteres inválidos em nomes de arquivos e diretórios." A resposta fornecida por sixlettervaliables entra em mais detalhes.

Outras dicas

De "Nomeando um arquivo ou diretório" do MSDN aqui estão as convenções gerais sobre o que é um nome de arquivo legal no Windows:

Você pode usar qualquer caractere na página de código atual (Unicode/ANSI acima de 127), exceto:

  • < > : " / \ | ? *
  • Caracteres cujas representações inteiras são 0-31 (menos que espaço ASCII)
  • Qualquer outro caractere que o sistema de arquivos de destino não permita (por exemplo, pontos finais ou espaços)
  • Qualquer um dos nomes DOS:CON, PRN, AUX, NUL, COM0, COM1, COM2, COM3, COM4, ​​COM5, COM6, COM7, COM8, COM9, LPT0, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, LPT9 (e evitar AUX.txt, etc.)
  • O nome do arquivo é todos os pontos

Algumas coisas opcionais para verificar:

  • Os caminhos dos arquivos (incluindo o nome do arquivo) não podem ter mais de 260 caracteres (que não usam o \?\ prefixo)
  • Caminhos de arquivo Unicode (incluindo o nome do arquivo) com mais de 32.000 caracteres ao usar \?\ (observe que o prefixo pode expandir os componentes do diretório e fazer com que ultrapasse o limite de 32.000)

Para .Net Frameworks anteriores a 3.5 isso deve funcionar:

A correspondência de expressões regulares deve ajudar você.Aqui está um trecho usando o System.IO.Path.InvalidPathChars constante;

bool IsValidFilename(string testName)
{
    Regex containsABadCharacter = new Regex("[" 
          + Regex.Escape(System.IO.Path.InvalidPathChars) + "]");
    if (containsABadCharacter.IsMatch(testName)) { return false; };

    // other checks for UNC, drive-path format, etc

    return true;
}

Para .Net Frameworks após 3.0 isso deve funcionar:

http://msdn.microsoft.com/en-us/library/system.io.path.getinvalidpathchars(v=vs.90).aspx

A correspondência de expressões regulares deve ajudar você.Aqui está um trecho usando o System.IO.Path.GetInvalidPathChars() constante;

bool IsValidFilename(string testName)
{
    Regex containsABadCharacter = new Regex("["
          + Regex.Escape(new string(System.IO.Path.GetInvalidPathChars())) + "]");
    if (containsABadCharacter.IsMatch(testName)) { return false; };

    // other checks for UNC, drive-path format, etc

    return true;
}

Depois de saber disso, você também deve verificar os diferentes formatos, por exemplo c:\my\drive e \\server\share\dir\file.ext

Tente usá-lo e prenda o erro.O conjunto permitido pode mudar entre sistemas de arquivos ou entre diferentes versões do Windows.Em outras palavras, se você quiser saber se o Windows gosta do nome, entregue-lhe o nome e deixe-o avisar.

Esta classe limpa nomes de arquivos e caminhos;use-o como

var myCleanPath = PathSanitizer.SanitizeFilename(myBadPath, ' ');

Aqui está o código;

/// <summary>
/// Cleans paths of invalid characters.
/// </summary>
public static class PathSanitizer
{
    /// <summary>
    /// The set of invalid filename characters, kept sorted for fast binary search
    /// </summary>
    private readonly static char[] invalidFilenameChars;
    /// <summary>
    /// The set of invalid path characters, kept sorted for fast binary search
    /// </summary>
    private readonly static char[] invalidPathChars;

    static PathSanitizer()
    {
        // set up the two arrays -- sorted once for speed.
        invalidFilenameChars = System.IO.Path.GetInvalidFileNameChars();
        invalidPathChars = System.IO.Path.GetInvalidPathChars();
        Array.Sort(invalidFilenameChars);
        Array.Sort(invalidPathChars);

    }

    /// <summary>
    /// Cleans a filename of invalid characters
    /// </summary>
    /// <param name="input">the string to clean</param>
    /// <param name="errorChar">the character which replaces bad characters</param>
    /// <returns></returns>
    public static string SanitizeFilename(string input, char errorChar)
    {
        return Sanitize(input, invalidFilenameChars, errorChar);
    }

    /// <summary>
    /// Cleans a path of invalid characters
    /// </summary>
    /// <param name="input">the string to clean</param>
    /// <param name="errorChar">the character which replaces bad characters</param>
    /// <returns></returns>
    public static string SanitizePath(string input, char errorChar)
    {
        return Sanitize(input, invalidPathChars, errorChar);
    }

    /// <summary>
    /// Cleans a string of invalid characters.
    /// </summary>
    /// <param name="input"></param>
    /// <param name="invalidChars"></param>
    /// <param name="errorChar"></param>
    /// <returns></returns>
    private static string Sanitize(string input, char[] invalidChars, char errorChar)
    {
        // null always sanitizes to null
        if (input == null) { return null; }
        StringBuilder result = new StringBuilder();
        foreach (var characterToTest in input)
        {
            // we binary search for the character in the invalid set. This should be lightning fast.
            if (Array.BinarySearch(invalidChars, characterToTest) >= 0)
            {
                // we found the character in the array of 
                result.Append(errorChar);
            }
            else
            {
                // the character was not found in invalid, so it is valid.
                result.Append(characterToTest);
            }
        }

        // we're done.
        return result.ToString();
    }

}

Isto é o que eu uso:

    public static bool IsValidFileName(this string expression, bool platformIndependent)
    {
        string sPattern = @"^(?!^(PRN|AUX|CLOCK\$|NUL|CON|COM\d|LPT\d|\..*)(\..+)?$)[^\x00-\x1f\\?*:\"";|/]+$";
        if (platformIndependent)
        {
           sPattern = @"^(([a-zA-Z]:|\\)\\)?(((\.)|(\.\.)|([^\\/:\*\?""\|<>\. ](([^\\/:\*\?""\|<>\. ])|([^\\/:\*\?""\|<>]*[^\\/:\*\?""\|<>\. ]))?))\\)*[^\\/:\*\?""\|<>\. ](([^\\/:\*\?""\|<>\. ])|([^\\/:\*\?""\|<>]*[^\\/:\*\?""\|<>\. ]))?$";
        }
        return (Regex.IsMatch(expression, sPattern, RegexOptions.CultureInvariant));
    }

O primeiro padrão cria uma expressão regular contendo nomes de arquivos e caracteres inválidos/ilegais apenas para plataformas Windows.O segundo faz o mesmo, mas garante que o nome seja legal para qualquer plataforma.

Um caso estranho para ter em mente, que me surpreendeu quando descobri sobre isso:O Windows permite caracteres de espaço iniciais em nomes de arquivos!Por exemplo, a seguir estão todos nomes de arquivos legais e distintos no Windows (sem as aspas):

"file.txt"
" file.txt"
"  file.txt"

Uma lição disso:Tenha cuidado ao escrever código que corta espaços em branco iniciais/finais de uma string de nome de arquivo.

Simplificando a resposta de Eugene Katz:

bool IsFileNameCorrect(string fileName){
    return !fileName.Any(f=>Path.GetInvalidFileNameChars().Contains(f))
}

Ou

bool IsFileNameCorrect(string fileName){
    return fileName.All(f=>!Path.GetInvalidFileNameChars().Contains(f))
}

Microsoft Windows:O kernel do Windows proíbe o uso de caracteres no intervalo 1-31 (ou seja, 0x01-0x1F) e caracteres " * :< > ?\|.Embora o NTFS permita que cada componente do caminho (diretório ou nome de arquivo) tenha 255 caracteres e caminhos com até cerca de 32.767 caracteres, o kernel do Windows oferece suporte apenas a caminhos com até 259 caracteres.Além disso, o Windows proíbe o uso dos nomes de dispositivos MS-DOS AUX, CLOCK$, COM1, COM2, COM3, COM4, ​​COM5, COM6, COM7, COM8, COM9, CON, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, LPT9, NUL e PRN, bem como estes nomes com qualquer extensão (por exemplo, AUX.txt), exceto quando utilizar caminhos UNC longos (ex.\.\C: ul.txt ou \?\D:\aux\con).(Na verdade, CLOCK$ pode ser usado se uma extensão for fornecida.) Essas restrições se aplicam apenas ao Windows - o Linux, por exemplo, permite o uso de " * :< > ?| Mesmo em NTFs.

Fonte: http://en.wikipedia.org/wiki/Nome do arquivo

Em vez de incluir explicitamente todos os caracteres possíveis, você poderia fazer um regex para verificar a presença de caracteres ilegais e, em seguida, relatar um erro.Idealmente, seu aplicativo deve nomear os arquivos exatamente como o usuário deseja e só reclamar se encontrar um erro.

Eu uso isso para me livrar de caracteres inválidos em nomes de arquivos sem lançar exceções:

private static readonly Regex InvalidFileRegex = new Regex(
    string.Format("[{0}]", Regex.Escape(@"<>:""/\|?*")));

public static string SanitizeFileName(string fileName)
{
    return InvalidFileRegex.Replace(fileName, string.Empty);
}

Além disso, CON, PRN, AUX, NUL, COM# e alguns outros nunca são nomes de arquivos legais em qualquer diretório com qualquer extensão.

A questão é: você está tentando determinar se um nome de caminho é um caminho legal do Windows ou se é legal no sistema onde o código está sendo executado.?Acho que o último é mais importante, então, pessoalmente, provavelmente decomporia o caminho completo e tentaria usar _mkdir para criar o diretório ao qual o arquivo pertence e, em seguida, tentaria criar o arquivo.

Desta forma você sabe não apenas se o caminho contém apenas caracteres válidos do Windows, mas se ele realmente representa um caminho que pode ser escrito por este processo.

Para complementar as outras respostas, aqui estão alguns casos extremos adicionais que você pode considerar.

De MSDN, aqui está uma lista de caracteres que não são permitidos:

Use quase todos os caracteres da página de código atual para um nome, incluindo caracteres Unicode e caracteres no conjunto de caracteres estendido (128–255), exceto os seguintes:

  • Os seguintes caracteres reservados não são permitidos:<>:" / \ | ?*
  • Caracteres cujas representações inteiras estejam no intervalo de zero a 31 não são permitidos.
  • Qualquer outro caractere que o sistema de arquivos de destino não permita.

Além disso, o sistema de arquivos de destino é importante.

No NTFS, alguns arquivos não podem ser criados em diretórios específicos.POR EXEMPLO.$ Inicializar na raiz

Esta é uma pergunta já respondida, mas apenas por uma questão de "Outras opções", aqui está uma pergunta não ideal:

(não é ideal porque usar exceções como controle de fluxo é uma "coisa ruim", geralmente)

public static bool IsLegalFilename(string name)
{
    try 
    {
        var fileInfo = new FileInfo(name);
        return true;
    }
    catch
    {
        return false;
    }
}

Expressões regulares são um exagero para esta situação.Você pode usar o String.IndexOfAny() método em combinação com Path.GetInvalidPathChars() e Path.GetInvalidFileNameChars().

Observe também que ambos Path.GetInvalidXXX() métodos clonam um array interno e retornam o clone.Portanto, se você fizer isso muito (milhares e milhares de vezes), poderá armazenar em cache uma cópia da matriz de caracteres inválidos para reutilização.

muitas dessas respostas não funcionarão se o nome do arquivo for muito longo e estiver em execução em um ambiente anterior ao Windows 10.Da mesma forma, pense no que você deseja fazer com os pontos - permitir o início ou o final é tecnicamente válido, mas pode criar problemas se você não quiser que o arquivo seja difícil de ver ou excluir, respectivamente.

Este é um atributo de validação que criei para verificar um nome de arquivo válido.

public class ValidFileNameAttribute : ValidationAttribute
{
    public ValidFileNameAttribute()
    {
        RequireExtension = true;
        ErrorMessage = "{0} is an Invalid Filename";
        MaxLength = 255; //superseeded in modern windows environments
    }
    public override bool IsValid(object value)
    {
        //http://stackoverflow.com/questions/422090/in-c-sharp-check-that-filename-is-possibly-valid-not-that-it-exists
        var fileName = (string)value;
        if (string.IsNullOrEmpty(fileName)) { return true;  }
        if (fileName.IndexOfAny(Path.GetInvalidFileNameChars()) > -1 ||
            (!AllowHidden && fileName[0] == '.') ||
            fileName[fileName.Length - 1]== '.' ||
            fileName.Length > MaxLength)
        {
            return false;
        }
        string extension = Path.GetExtension(fileName);
        return (!RequireExtension || extension != string.Empty)
            && (ExtensionList==null || ExtensionList.Contains(extension));
    }
    private const string _sepChar = ",";
    private IEnumerable<string> ExtensionList { get; set; }
    public bool AllowHidden { get; set; }
    public bool RequireExtension { get; set; }
    public int MaxLength { get; set; }
    public string AllowedExtensions {
        get { return string.Join(_sepChar, ExtensionList); } 
        set {
            if (string.IsNullOrEmpty(value))
            { ExtensionList = null; }
            else {
                ExtensionList = value.Split(new char[] { _sepChar[0] })
                    .Select(s => s[0] == '.' ? s : ('.' + s))
                    .ToList();
            }
    } }

    public override bool RequiresValidationContext => false;
}

e os testes

[TestMethod]
public void TestFilenameAttribute()
{
    var rxa = new ValidFileNameAttribute();
    Assert.IsFalse(rxa.IsValid("pptx."));
    Assert.IsFalse(rxa.IsValid("pp.tx."));
    Assert.IsFalse(rxa.IsValid("."));
    Assert.IsFalse(rxa.IsValid(".pp.tx"));
    Assert.IsFalse(rxa.IsValid(".pptx"));
    Assert.IsFalse(rxa.IsValid("pptx"));
    Assert.IsFalse(rxa.IsValid("a/abc.pptx"));
    Assert.IsFalse(rxa.IsValid("a\\abc.pptx"));
    Assert.IsFalse(rxa.IsValid("c:abc.pptx"));
    Assert.IsFalse(rxa.IsValid("c<abc.pptx"));
    Assert.IsTrue(rxa.IsValid("abc.pptx"));
    rxa = new ValidFileNameAttribute { AllowedExtensions = ".pptx" };
    Assert.IsFalse(rxa.IsValid("abc.docx"));
    Assert.IsTrue(rxa.IsValid("abc.pptx"));
}

Se você está apenas tentando verificar se uma string que contém o nome/caminho do arquivo possui caracteres inválidos, o método mais rápido que encontrei é usar Split() para dividir o nome do arquivo em uma série de partes sempre que houver um caractere inválido.Se o resultado for apenas uma matriz de 1, não há caracteres inválidos.:-)

var nameToTest = "Best file name \"ever\".txt";
bool isInvalidName = nameToTest.Split(System.IO.Path.GetInvalidFileNameChars()).Length > 1;

var pathToTest = "C:\\My Folder <secrets>\\";
bool isInvalidPath = pathToTest.Split(System.IO.Path.GetInvalidPathChars()).Length > 1;

Tentei executar este e outros métodos mencionados acima em um nome de arquivo/caminho 1.000.000 de vezes no LinqPad.

Usando Split() é de apenas ~ 850 ms.

Usando Regex("[" + Regex.Escape(new string(System.IO.Path.GetInvalidPathChars())) + "]") é em torno de 6 segundos.

As expressões regulares mais complicadas são MUITO piores, assim como algumas das outras opções, como usar os vários métodos no Path class para obter o nome do arquivo e deixar sua validação interna fazer o trabalho (provavelmente devido à sobrecarga do tratamento de exceções).

É verdade que não é muito frequente que você precise validar 1 milhão de nomes de arquivos; portanto, uma única iteração é adequada para a maioria desses métodos.Mas ainda é bastante eficiente e eficaz se você estiver procurando apenas caracteres inválidos.

Minha tentativa:

using System.IO;

static class PathUtils
{
  public static string IsValidFullPath([NotNull] string fullPath)
  {
    if (string.IsNullOrWhiteSpace(fullPath))
      return "Path is null, empty or white space.";

    bool pathContainsInvalidChars = fullPath.IndexOfAny(Path.GetInvalidPathChars()) != -1;
    if (pathContainsInvalidChars)
      return "Path contains invalid characters.";

    string fileName = Path.GetFileName(fullPath);
    if (fileName == "")
      return "Path must contain a file name.";

    bool fileNameContainsInvalidChars = fileName.IndexOfAny(Path.GetInvalidFileNameChars()) != -1;
    if (fileNameContainsInvalidChars)
      return "File name contains invalid characters.";

    if (!Path.IsPathRooted(fullPath))
      return "The path must be absolute.";

    return "";
  }
}

Isto não é perfeito porque Path.GetInvalidPathChars não retorna o conjunto completo de caracteres inválidos em nomes de arquivos e diretórios e, claro, há muito mais sutilezas.

Então utilizo esse método como complemento:

public static bool TestIfFileCanBeCreated([NotNull] string fullPath)
{
  if (string.IsNullOrWhiteSpace(fullPath))
    throw new ArgumentException("Value cannot be null or whitespace.", "fullPath");

  string directoryName = Path.GetDirectoryName(fullPath);
  if (directoryName != null) Directory.CreateDirectory(directoryName);
  try
  {
    using (new FileStream(fullPath, FileMode.CreateNew)) { }
    File.Delete(fullPath);
    return true;
  }
  catch (IOException)
  {
    return false;
  }
}

Ele tenta criar o arquivo e retornar false se houver uma exceção.Claro, preciso criar o arquivo, mas acho que é a maneira mais segura de fazer isso.Observe também que não estou excluindo diretórios que foram criados.

Você também pode usar o primeiro método para fazer a validação básica e, em seguida, tratar cuidadosamente as exceções quando o caminho for usado.

Sugiro apenas usar Path.GetFullPath()

string tagetFileFullNameToBeChecked;
try
{
  Path.GetFullPath(tagetFileFullNameToBeChecked)
}
catch(AugumentException ex)
{
  // invalid chars found
}

Tive essa ideia de alguém.- não sei quem.Deixe o sistema operacional fazer o trabalho pesado.

public bool IsPathFileNameGood(string fname)
{
    bool rc = Constants.Fail;
    try
    {
        this._stream = new StreamWriter(fname, true);
        rc = Constants.Pass;
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Problem opening file");
        rc = Constants.Fail;
    }
    return rc;
}

Esta verificação

static bool IsValidFileName(string name)
{
    return
        !string.IsNullOrWhiteSpace(name) &&
        name.IndexOfAny(Path.GetInvalidFileNameChars()) < 0 &&
        !Path.GetFullPath(name).StartsWith(@"\\.\");
}

filtra nomes com caracteres inválidos (<>:"/\|?* e ASCII 0-31), bem como dispositivos DOS reservados (CON, NUL, COMx).Ele permite espaços iniciais e nomes com todos os pontos, consistentes com Path.GetFullPath.(A criação de arquivo com espaços iniciais foi bem-sucedida em meu sistema).


Utilizado .NET Framework 4.7.1, testado em Windows 7.

Um liner para verificar caracteres ilegais na string:

public static bool IsValidFilename(string testName) => !Regex.IsMatch(testName, "[" + Regex.Escape(new string(System.IO.Path.InvalidPathChars)) + "]");

Os nomes de arquivos do Windows são bastante irrestritos, então talvez nem seja que um grande problema.Os caracteres não permitidos pelo Windows são:

\ / : * ? " < > |

Você poderia facilmente escrever uma expressão para verificar se esses caracteres estão presentes.Uma solução melhor seria tentar nomear os arquivos como o usuário deseja e alertá-los quando o nome do arquivo não funcionar.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top