it-swarm-ru.tech

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

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

150
tomash

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

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

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

96
Eugene Katz

От 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 символов (без префикса \?\)
  • Пути к файлам в Юникоде (включая имя файла), содержащие более 32 000 символов при использовании \?\ (обратите внимание, что префикс может расширять компоненты каталога и приводить к переполнению лимита 32 000)
116
user7116

Для .Net Frameworks до 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 Frameworks после 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

62
Steve Cooper

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

25
Dewayne Christensen

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

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

}
23
Steve Cooper

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

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

22
Scott Dorman

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

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

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

18
Jon Schneider

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

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

Или же

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

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

8
Martin Faartoft

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

7
ConroyP

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

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

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

5
Roland Rabien

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

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

5
kfh

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

  • В Excel могут возникнуть проблемы, если вы сохраните книгу в файл, имя которого содержит символы «[» или «]». См. Http://support.Microsoft.com/kb/215205 для получения подробной информации.

  • Sharepoint имеет целый дополнительный набор ограничений. См. Http://support.Microsoft.com/kb/905231 для получения подробной информации.

4
Joe

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

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

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

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

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

2
s n

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

Под NTFS некоторые файлы не могут быть созданы в определенных каталогах . E.G. $ Boot в корне 

2
Dominik Weber

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

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

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

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

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

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

1
Nick Albrecht

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

Моя попытка:

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

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

1
Maxence

Эта проверка

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.

0
Vlad

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

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

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

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

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

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