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

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

Ответ 1

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

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

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

Ответ 2

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

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

  • < > : " / \ | ? *
  • Символы, целые представления которых равны 0-31 (меньше пространства ASCII)
  • Любой другой символ, который целевая файловая система не разрешает (скажем, периоды или пробелы)
  • Любое из имен 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 (и избегать AUX.txt и т.д.),
  • Имя файла - это все периоды

Некоторые дополнительные вещи для проверки:

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

Ответ 3

Для .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

Ответ 4

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

Ответ 5

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

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

}

Ответ 6

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

    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. Второй делает то же самое, но гарантирует, что имя является законным для любой платформы.

Ответ 7

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

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

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

Ответ 8

Упрощение ответа Юджина Каца:

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

Или же

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

Ответ 9

Microsoft Windows: ядро Windows запрещает использование символов в диапазоне 1-31 (то есть 0x01-0x1F) и символов "*: <>?\|. Хотя NTFS позволяет каждому компоненту пути (каталог или имя файла) быть 255 символов и длиной до 32767 символов, ядро Windows поддерживает только пути длиной до 259 символов. Кроме того, Windows запрещает использование имен устройств MS-DOS AUX, CLOCK $, COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9, CON, 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

Ответ 10

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

Ответ 11

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

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

Ответ 12

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

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

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

Ответ 13

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

Ответ 14

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

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

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

Ответ 15

Из MSDN, здесь список символов, которые не разрешены:

  
    

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

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

Ответ 16

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

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

Ответ 17

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

В NTFS некоторые файлы не могут быть созданы в определенных каталогах. НАПРИМЕР. $Boot in root

Ответ 18

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

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

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

Ответ 19

Если вы только пытаетесь проверить, имеет ли строка, содержащая ваше имя/путь файла какие-либо недопустимые символы, самый быстрый метод, который я нашел, это использовать 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() - всего ~ 850 мс.

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

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

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

Ответ 20

многие из этих ответов не будут работать, если имя файла слишком длинное и работает в среде предварительного 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"));
}

Ответ 21

Моя попытка:

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, если есть исключение. Конечно, мне нужно создать файл, но я думаю, что это самый безопасный способ сделать это. Также обратите внимание, что я не удаляю созданные каталоги.

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

Ответ 22

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

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

Ответ 23

Я получил эту идею от кого-то. - Не знаю, кто. Пусть ОС делает тяжелый подъем.

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

Ответ 24

Эта проверка

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. (Создание файла с ведущими пробелами успешно Path.GetFullPath в моей системе).


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

Ответ 25

Один вкладыш для проверки нелигальных символов в строке:

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

Ответ 26

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

Ответ 27

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

\ / : * ? " < > |

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