it-swarm-pt.tech

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

Eu quero incluir uma funcionalidade de renomeação de arquivo em lote no 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) eu preciso verificar se ele será um nome de arquivo legal no Windows. Tentei usar expressões regulares como [a-zA-Z0-9_]+, mas ele não inclui muitos caracteres específicos de cada idioma em vários idiomas (por exemplo, umlauts e assim por diante). Qual é a melhor maneira de fazer tal verificação?

150
tomash

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

UPD: Veja 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 "A matriz retornada deste método não é garantida para conter o conjunto completo de caracteres que são inválidos em nomes de arquivo e diretório." A resposta fornecida por sixlettervaliables entra em mais detalhes.

96
Eugene Katz

De MSDN's "Nomeando um arquivo ou diretório" / aqui estão as convenções gerais para 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 ASCII espaço)
  • Qualquer outro caractere que o sistema de arquivos de destino não permite (por exemplo, períodos finais ou espaços)
  • Qualquer um dos nomes DOS: CON, PRN, AUX, NUL, COM0, COM1, COM2, COM3, COM4, ​​COM5, COM7, COM8, COM9, LPT0, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, LPT9 (e evite AUX.txt, etc)
  • O nome do arquivo é todos os períodos

Algumas coisas opcionais para verificar:

  • Caminhos de arquivo (incluindo o nome do arquivo) podem não 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 ele exceda o limite de 32.000)
116
user7116

Para .Net Frameworks anteriores a 3.5 isso deve funcionar:

A correspondência de expressões regulares deve ajudá-lo no caminho. Aqui está um trecho usando a constante System.IO.Path.InvalidPathChars;

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 ajudá-lo no caminho. Aqui está um trecho usando a constante System.IO.Path.GetInvalidPathChars();

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 formatos diferentes, por exemplo, c:\my\drive e \\server\share\dir\file.ext

62
Steve Cooper

Tente usá-lo e interceptar 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, dê-lhe o nome e deixe-o avisar.

25
Dewayne Christensen

Esta classe limpa nomes de arquivos e caminhos; usá-lo 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();
    }

}
23
Steve Cooper

Isso é 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 os nomes e caracteres de arquivo inválidos/ilegais somente para plataformas Windows. O segundo faz o mesmo, mas garante que o nome seja legal para qualquer plataforma.

22
Scott Dorman

Um caso de canto a ter em mente, o que me surpreendeu quando descobri: o Windows permite a introdução de caracteres espaciais em nomes de arquivos! Por exemplo, os seguintes são todos os nomes de arquivos legais e distintos no Windows (menos as aspas):

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

Um takeaway a partir disto: Tenha cuidado ao escrever código que apara espaço em branco inicial/final de uma seqüência de nome de arquivo.

18
Jon Schneider

Simplificando a resposta do 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))
}
8
tmt

Microsoft Windows: O kernel do Windows proíbe o uso de caracteres no intervalo de 1 a 31 (isto é, 0x01-0x1F) e caracteres "*: <>? \. Embora o NTFS permita que cada componente de caminho (diretório ou nome de arquivo) tenha 255 caracteres caminhos até cerca de 32767 caracteres, o kernel do Windows suporta apenas caminhos de até 259 caracteres.Além disso, o Windows proíbe o uso dos nomes de dispositivo 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), excepto quando se utiliza Longos caminhos UNC (ex. \.\C:\nul.txt ou \?\D:\aux\con). (Na verdade, CLOCK $ pode ser usado se uma extensão for fornecida.) Essas restrições se aplicam somente ao Windows - O Linux, por exemplo, permite o uso de "*: <>?\| mesmo em NTFS.

Fonte: http://en.wikipedia.org/wiki/Filename

8
Martin Faartoft

Em vez de incluir explicitamente todos os caracteres possíveis, você pode fazer um regex para verificar a presença de caracteres ilegais e relatar um erro. Idealmente, seu aplicativo deve nomear os arquivos exatamente como o usuário deseja, e apenas chorar se ocorrer um erro.

7
ConroyP

Eu uso isso para se 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);
}
6
JoelFan

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

5
Roland Rabien

A questão é que você está tentando determinar se um nome de caminho é um caminho legal do Windows ou se é legal no sistema em que o código está sendo executado. ? Eu acho que o último é mais importante, então, pessoalmente, eu provavelmente iria decompor o caminho completo e tentar usar o _mkdir para criar o diretório no qual o arquivo pertence, então tente criar o arquivo.

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

5
kfh

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

4
Joe

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

Use quase qualquer caractere na página de código atual para um nome, incluindo caracteres Unicode e caracteres no conjunto de caracteres estendidos (128 a 255), exceto pelo seguinte:

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

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

Observe também que ambos os métodos Path.GetInvalidXXX() clonam uma matriz interna e retornam o clone. Então, se você vai fazer isso muito (milhares e milhares de vezes), você pode armazenar em cache uma cópia da matriz de caracteres inválidos para reutilização.

2
s n

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

Em NTFS, alguns arquivos não podem ser criados em diretórios específicos. E.G. $ Boot na raiz 

2
Dominik Weber

Esta é uma questão já respondida, mas apenas por causa de "Outras opções", aqui está uma 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;
    }
}
2
JerKimball

Se você está apenas tentando verificar se uma string que contém o nome/caminho do seu arquivo possui caracteres inválidos, o método mais rápido que eu encontrei é usar Split() para dividir o nome do arquivo em uma matriz de partes onde 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;

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

Usando Split() é apenas ~ 850ms.

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

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

É bem provável que você não 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ê está apenas procurando por caracteres inválidos.

1
Nick Albrecht

muitas dessas respostas não funcionarão se o nome do arquivo for muito longo e executado em um ambiente anterior ao Windows 10. Da mesma forma, pense sobre o que você deseja fazer com períodos - permitindo que o início ou o final seja 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"));
}
1
Brent

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 "";
  }
}

Isso 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 eu uso 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, eu preciso criar o arquivo, mas acho que é a maneira mais segura de fazer isso. Por favor, note também que eu 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, manipular cuidadosamente as exceções quando o caminho é usado.

1
Maxence

Este cheque

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

filtra os nomes com chars inválidos (<>:"/\|?* e ASCII 0-31), bem como dispositivos DOS reservados (CON, NUL, COMx). Ele permite espaços iniciais e nomes de todos os pontos, consistentes com Path.GetFullPath. (Criar arquivo com espaços principais é bem-sucedido no meu sistema).


Usado o .NET Framework 4.7.1, testado no Windows 7.

0
Vlad

Eu tenho 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;
}
0
KenR

Um forro para verificar caracteres ilegais na string:

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

Eu sugiro apenas usar o Path.GetFullPath ()

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