Как проверить, является ли данная строка законным / допустимым именем файла под Windows?


Я хочу включить функцию переименования пакетного файла в мое приложение. Пользователь может ввести шаблон имени файла назначения и (после замены некоторых подстановочных знаков в шаблоне) мне нужно проверить, будет ли это законное имя файла под Windows. Я пытался использовать регулярное выражение типа [a-zA-Z0-9_]+ но он не включает в себя много национальных символов из разных языков (например, umlauts и так далее). Каков наилучший способ сделать такую проверку?

25 149

25 ответов:

Вы можете получить список недопустимых символов Path.GetInvalidPathChars и GetInvalidFileNameChars.

UPD: посмотреть предложение Стива Купера о том, как использовать их в регулярное выражение.

UPD2: обратите внимание, что в соответствии с разделом Примечаний в MSDN "массив, возвращаемый этим методом, не гарантированно содержит полный набор символов, недопустимых в именах файлов и каталогов."ответ предоставлено sixlettervaliables более подробно.

С MSDN "именование файла или каталога", вот общие соглашения для того, что юридическое имя файла под Windows:

вы можете использовать любой символ на текущей кодовой странице (Unicode / ANSI выше 127), за исключением:

  • <>:"/\|?*
  • символы, целочисленные представления которых 0-31 (меньше, чем пространство ASCII)
  • любой другой символ что целевая файловая система не позволяет (скажем, конечные точки или пробелы)
  • любой из Дос названия: кон, ПРН, вспомогательный, нуль, COM0, СОМ1, СОМ2, СОМ3, порт com4, COM5, СОМ6, резолюцию com7, COM8, порт com9, LPT0, порт lpt1, lpt2 в, подключен к порту lpt3, LPT4, LPT5, LPT6, LPT7, LPT8, LPT9 (а не ОКС.txt, etc)
  • имя файла-все периоды

некоторые дополнительные вещи, чтобы проверить:

  • пути к файлам (включая имя файла) могут содержать не более 260 символов (что не используйте \?\ префикс)
  • Unicode пути к файлам (включая имя файла) с более чем 32 000 символов при использовании \?\ (обратите внимание, что префикс может расширить компоненты каталога и вызвать его переполнение предела 32,000)

для.Net Framework до 3.5 это должно работать:

соответствие регулярным выражениям должно помочь вам в этом. Вот фрагмент с помощью 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;
}

для.Net Framework после 3.0 это должно работать:

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

соответствие регулярным выражениям должно помочь вам в этом. Это место фрагмент кода с помощью 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;
}

как только вы это узнаете, вы также должны проверить различные форматы, например c:\my\drive и \server\share\dir\file.ext

попробуйте использовать его, и ловушка для ошибки. Допустимый набор может изменяться в разных файловых системах или в разных версиях Windows. Другими словами, если вы хотите знать, нравится ли Windows имя, дайте ему имя и пусть он скажет вам.

этот класс очищает имена файлов и пути; используйте его как

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

вот код;

/// <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();
    }

}

вот что я использую:

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

первый шаблон создает регулярное выражение, содержащее недопустимые / незаконные имена файлов и символы только для платформ Windows. Второй делает то же самое, но гарантирует, что имя является законным для любой платформы.

один угловой случай, чтобы иметь в виду, что удивило меня, когда я впервые узнал об этом: Windows позволяет приводить пробелы в именах файлов! Например, ниже приведены все законные и различные имена файлов в Windows (за вычетом кавычек):

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

один вынос из этого: будьте осторожны при написании кода, который обрезает ведущие/конечные пробелы из строки имени файла.

Microsoft Windows: ядро Windows запрещает использование символов в диапазоне 1-31 (т. е. 0x01-0x1F) и символов " * : ? \ |. Хотя NTFS позволяет каждому компоненту пути (каталогу или имени файла) иметь длину 255 символов и пути длиной до 32767 символов, ядро Windows поддерживает только пути длиной до 259 символов. Кроме того, Windows запрещает использование MS-DOS с именами вспомогательный прибор, часы$, СОМ1, СОМ2, СОМ3, порт com4, COM5, СОМ6, резолюцию com7, COM8, com9, то зэк, порт lpt1, lpt2 в, подключен к порту lpt3, LPT4, LPT5, LPT6, LPT7, LPT8, LPT9, NUL и PRN, а также эти имена с любым расширением (например, AUX.txt), за исключением случаев использования длинных UNC-путей (например. \.\C:\nul.txt или \?\D:\aux\con). (Фактически, CLOCK$ может использоваться, если предоставляется расширение.) Эти ограничения применимы только к Windows - Linux, например, позволяет использовать " * : ? \ | даже в NTFS.

Источник:http://en.wikipedia.org/wiki/Filename

вместо того, чтобы явно включать все возможные символы, вы можете сделать регулярное выражение для проверки наличия незаконных символов и сообщить об ошибке. В идеале ваше приложение должно называть файлы точно так, как хочет пользователь, и только кричать фол, если он натыкается на ошибку.

упрощая ответ Евгения Каца:

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

или

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

Я использую это, чтобы избавиться от недопустимых символов в именах файлов без исключений:

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

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

также CON, PRN, AUX, NUL, COM# и некоторые другие никогда не являются законными именами файлов в любом каталоге с любым расширением.

вопрос в том, пытаетесь ли вы определить, является ли имя пути законным путем windows, или если это законно в системе, где выполняется код.? Я думаю, что последнее более важно, поэтому лично я, вероятно, разложу полный путь и попытаюсь использовать _mkdir для создания каталога, в котором находится файл, а затем попытаюсь создать файл.

таким образом, вы знаете не только, если путь содержит только допустимые символы windows, но если он на самом деле представляет путь, который может будьте написаны этим процессом.

в дополнение к другим ответам, вот несколько дополнительных крайних случаев, которые вы, возможно, захотите рассмотреть.

  • Excel может иметь проблемы при сохранении книги в файле, имя которого содержит символы '[' или']'. См.http://support.microsoft.com/kb/215205 для деталей.

  • Sharepoint имеет целый дополнительный набор ограничений. Смотрите http://support.microsoft.com/kb/905231 ибо подробности.

с MSDN, вот список символов, которые не допускаются:

используйте почти любой символ на текущей кодовой странице для имени, включая символы Юникода и символы в расширенном наборе символов (128-255), за исключением следующих:

  • следующие зарезервированные символы не допускаются: :"/ \/? *
  • персонажей, чьи целочисленные представления в диапазоне от нуля до 31 не допускаются.
  • любой другой символ, который не позволяет целевая файловая система.

также важна целевая файловая система.

в NTFS некоторые файлы не могут быть созданы в определенных каталогах. Например $ Boot in root

это уже ответ на вопрос, но только ради "других вариантов", вот неидеальный:

(неидеально, потому что использование исключений в качестве управления потоком-это "плохо", как правило)

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

регулярные выражения являются излишними для этой ситуации. Вы можете использовать String.IndexOfAny() метод в сочетании с Path.GetInvalidPathChars() и Path.GetInvalidFileNameChars().

также обратите внимание, что оба Path.GetInvalidXXX() методы клонируют внутренний массив и возвращают клон. Поэтому, если вы собираетесь делать это много (тысячи и тысячи раз), вы можете кэшировать копию недопустимого массива символов для повторного использования.

многие из этих ответов не будут работать, если имя файла слишком длинное и работает в среде pre Windows 10. Точно так же подумайте о том, что вы хотите сделать с периодами - разрешение лидирования или трейлинга технически допустимо, но может создать проблемы, если вы не хотите, чтобы файл был трудным для просмотра или удаления соответственно.

это атрибут проверки, который я создал для проверки допустимого имени файла.

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

и тесты

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

если вы только пытаетесь проверить, имеет ли строка, содержащая имя файла / путь, какие-либо недопустимые символы, самый быстрый метод, который я нашел, - использовать Split() чтобы разбить имя файла на массив частей, где есть недопустимый символ. Если результатом является только массив 1, недопустимых символов не существует. : -)

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;

Я попытался запустить этот и другие методы, упомянутые выше, на имя файла/пути 1,000,000 раз в LinqPad.

используя Split() только ~850ms.

используя Regex("[" + Regex.Escape(new string(System.IO.Path.GetInvalidPathChars())) + "]") - около 6 секунд.

более сложные регулярные выражения справедливы намного хуже, как и некоторые другие варианты, например, использование различных методов на Path класс, чтобы получить имя файла и позволить их внутренней проверке выполнить эту работу (скорее всего, из-за накладных расходов на обработку исключений).

конечно, это не очень часто вам нужно проверить 1 миллион имен файлов, поэтому одна итерация подходит для большинства из этих методов в любом случае. Но это все еще довольно эффективно и эффективно, если вы ищете только недопустимые символы.

моя попытка:

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

Это не идеально, потому что Path.GetInvalidPathChars не возвращает полный набор символов, которые недопустимы в именах файлов и каталогов и, конечно, есть много тонкостей.

поэтому я использую этот метод в качестве дополнения:

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

он пытается создать файл и возвращает false, если есть исключение. Конечно, мне нужно создать файл, но я думаю, что это безопасный способ сделать это. Обратите внимание, что я не исключение каталоги, которые были созданы.

вы также можете использовать первый метод для выполнения базовой проверки, а затем тщательно обрабатывать исключения при использовании пути.

имена файлов Windows довольно неограниченны, так что на самом деле это может быть даже не это большой проблемой. Символы, которые запрещены Windows являются:

\ / : * ? " < > |

вы можете легко написать выражение, чтобы проверить, если эти символы присутствуют. Лучшим решением было бы попытаться назвать файлы так, как хочет пользователь, и предупредить их, когда имя файла не прилипает.

Я предлагаю просто использовать путь.GetFullPath()

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

Я получил эту идею от кого-то. - не знаю, кто. Пусть ОС делает тяжелую работу.

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

чек

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

отфильтровывает имена с недопустимыми символами (<>:"/\|?* и ASCII 0-31), а также зарезервированные устройства DOS (CON,NUL,COMx). Это позволяет лидирующие пробелы и все-точечные имена, в соответствии с Path.GetFullPath. (Создание файла с ведущими пробелами успешно выполняется в моей системе).


используется .NET Framework 4.7.1, протестирован на Windows 7.