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

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

Я предлагаю использовать статическое скомпилированное Regex, если вы собираетесь использовать какой-либо из ответов с Regex ..

AMissico 06.01.2015 22:41
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
169
1
165 097
27
Перейти к ответу Данный вопрос помечен как решенный

Ответы 27

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

Вот список недопустимых символов из MSDN:

Use almost any character in the current code page for a name, including Unicode characters and characters in the extended character set (128–255), except for the following:

  • The following reserved characters are not allowed: < > : " / \ | ? *
  • Characters whose integer representations are in the range from zero through 31 are not allowed.
  • Any other character that the target file system does not allow.
Ответ принят как подходящий

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

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

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

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

Dour High Arch 21.07.2013 21:57

Кто-нибудь еще разочарован тем, что MS не предоставляет функцию / API системного уровня для этой возможности, вместо того, чтобы каждый разработчик готовил свое собственное решение? Интересно, есть ли для этого веская причина или просто недосмотр со стороны РС.

Thomas Nguyen 21.03.2014 21:29

@High Arch: см. Ответ на вопрос «В C# проверьте, что имя файла является допустимым возможно (а не то, что оно существует)». (Хотя некоторые умники закрыли этот вопрос в пользу этого ...)

mmmmmmmm 20.10.2015 22:02

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

\ / : * ? " < > |

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

Также запрещены символы <= 31.

Antimony 01.10.2012 18:52

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

Это только половина правды. Вы можете создавать файлы с этими именами, если вызываете unicode-версию CreateFile (добавляя к имени файла префикс «\\? \»).

Werner Henze 30.04.2013 13:12

Это заявление является неполным и пропускает LPT #

Thomas Weller 30.10.2018 12:49

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

Я могу создать файл с именем «CLOCK $». Windows 7.

rory.ap 25.08.2017 20:18

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

Разве это не проверяет только путь, а не имя файла?

Eugene Katz 17.09.2008 16:57

строка strTheseAreInvalidFileNameChars = новая строка (System.IO.Path.GetInvalidFileNameChars ()); Регулярное выражение regFixFileName = новое регулярное выражение ("[" + Regex.Escape (strTheseAreInvalidFileNameChars) + "]");

rao 19.10.2010 18:36

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

Erik Philips 22.12.2013 10:03

2-й фрагмент кода не компилируется. "Невозможно преобразовать из char [] в строку

Paul Hunt 25.04.2014 16:03

+1 для кода, но замените Path.GetInvalidPathChars() на Path.GetInvalidFileNameChars(), так как Path.GetInvalidPathChars() сейчас устарел

Ashkan Mobayen Khiabani 02.11.2017 11:44

@AshkanMobayenKhiabani: InvalidPathChars устарел, а GetInvalidPathChars - нет.

IvanH 31.03.2020 16:27

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

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

Из 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 (включая имя файла) с более чем 32000 символов при использовании \?\ (обратите внимание, что префикс может расширить компоненты каталога и привести к превышению лимита в 32000)

+1 за включение зарезервированных имен файлов - они были пропущены в предыдущих ответах.

SqlRyan 20.04.2009 18:41

«AUX» - это имя файла, которое можно использовать, если вы используете синтаксис «\\? \». Конечно, программы, которые не используют этот синтаксис, имеют реальные проблемы с этим ... (Проверено на XP)

user9876 02.12.2009 16:19

Правильное регулярное выражение для всех этих условий, упомянутых выше, выглядит следующим образом: Regex unspupportedRegex = new Regex("(^(PRN|AUX|NUL|CON|COM[1-9]|LPT[1-9]|(\.+)$)(\..*)?‌​$)|(([\x00-\x1f\\‌​\?*:\";|/<>])+)|(([\‌​\. ]+)", RegexOptions.IgnoreCase);

whywhywhy 19.02.2015 08:58

@whywhywhy Я думаю, у вас есть дополнительная открывающая скобка в этом регулярном выражении. "(^ (PRN | AUX | NUL | CON | COM [1-9] | LPT [1-9] | (\\. +) $) (\\ .. *)? $) | (([‌ \\ x00 - \\ x1f \\\\? *: \ "‌; ‌ | / <>]) +) | ([\\.] +)" работал у меня.

Wilky 21.08.2015 00:00

Уилки: ваше регулярное выражение также удалит "." в пределах имени файла, которые совершенно верны.

Hyndrix 08.11.2015 18:12

Это лучше: (^(PRN|AUX|NUL|CON|COM[1-9]|LPT[1-9]|(\.+)$)(\..*)?$)|(([\‌​\x00-\x1f\\?*:\"​‌​|/<>‌​])+)|(^([\.]+‌​))

Hyndrix 08.11.2015 18:16

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

dlf 05.04.2016 20:12

Зависит от того, как вы определяете «разрешено». Окна разрешает имена файлов, начинающиеся с точки, но Исследователь не позволяет вам называть файл как таковой, если он также не имеет расширения. Например, .foo не разрешен, но .foo.bar разрешен.

Rich Jenks 12.04.2016 22:57

Я прочитал ту же статью, упомянутую в этом ответе, и путем экспериментов обнаружил, что COM0 и LPT0 также не разрешены. @dlf работает с именами файлов, начинающимися с '.': ^(?!^(?:PRN|AUX|CLOCK$|NUL|CON|COM\d|LPT\d)(?:\..+)?$)(?:\.‌​*?(?!\.))[^\x00-\x1f‌​\?*:\";|/<>]+(?<![‌​\s.])$

mjohnsonengr 14.05.2016 19:43

Есть ли библиотека, которая обрабатывает все эти случаи?

nawfal 03.04.2017 17:41

@papaiatis - "ЧАСЫ $" мне подходят. Windows 7.

rory.ap 25.08.2017 20:22

Кстати, правило «имя файла - все точки» уже содержится в «правиле конечных точек или пробелов»

Oleg Savelyev 13.08.2018 17:24

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

Кажется, это единственный, который проверяет все ограничения. Почему вместо этого выбираются другие ответы?

gap 07.03.2012 18:53

@gap, потому что это не всегда работает. Например, попытка доступа к CON часто бывает успешной, даже если это не настоящий файл.

Antimony 01.10.2012 18:51

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

Owen Blacker 02.10.2012 19:40

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

CodeLurker 25.07.2017 13:00

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

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

Регулярное выражение sPattern не разрешает файлы, начинающиеся с символа точки. Но MSDN говорит "допустимо указывать точку в качестве первого символа имени. Например," .temp "". Я бы удалил "\ .. *", чтобы имя файла .gitignore было правильным :)

yar_shukan 10.09.2014 17:51

(Я постепенно улучшал это и удалял предыдущие комментарии, которые я оставил) Этот лучше, чем регулярное выражение ответа, потому что он позволяет ".gitignore", "..asdf", не позволяет '<' и '>' или иену знак и не допускает пробелов или точек в конце (что запрещает имена, состоящие только из точек): @"^(?!(?:PRN|AUX|CLOCK$|NUL|CON|COM\d|LPT\d)(?:\..+)?$)[^\x‌​00-\x1F\xA5\?*:\"";‌​|/<>]+(?<![\s.])$"

mjohnsonengr 14.05.2016 22:40

это не подходит для всех файлов, которые я тестировал. запуск его для C: \ Windows \ System32 \ msxml6.dll сообщает false.

magicandre1981 14.06.2016 11:10

@ magicandre1981 Вам нужно указать только имя файла, а не полный путь.

Scott Dorman 15.06.2016 05:39

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

magicandre1981 15.06.2016 07:05

Ваш шаблон не работает на .foo.bar.

rory.ap 25.08.2017 20:43

Это также позволяет < и >.

rory.ap 25.08.2017 20:52

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

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

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

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

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

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

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

}

ваш ответ мог бы быть лучше здесь: stackoverflow.com/questions/146134/…

nawfal 12.06.2013 16:37

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

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

Конечно, это не из-за правила именования NTFS, а просто потому, что файл с именем $Boot уже существует в каталоге?

Christian Hayter 24.08.2010 00:24

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

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

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

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

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

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

Ваш пример не работал с файлом CON (C: \ temp \ CON).

tcbrazil 20.01.2015 15:06

Но разве 'C: \ temp \ CON' не является допустимым именем файла? Почему бы и нет?

Mark A. Donohoe 31.10.2015 19:20

@MarqueIV - нет, не действует. Прочтите все ответы и комментарии выше или попробуйте сами и убедитесь.

rory.ap 25.08.2017 20:20

@Jer, "/ example" недопустимы, но ваш метод возвращает true.

rory.ap 25.08.2017 20:21

Аааа ... Я пропустил часть "ПРОТИВ". Само имя действительно с точки зрения нить (это то, что я имел в виду), но теперь я вижу, что CON - зарезервированное имя, что делает его недействительным с точки зрения Windows. Виноват.

Mark A. Donohoe 25.08.2017 20:25

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

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

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

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

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

Добавьте объяснение с ответом на то, как этот ответ помогает OP в устранении текущей проблемы

ρяσѕρєя K 10.01.2017 11:30

См. Документ в MSDN для AugumentExcpetion, он гласит: path является строкой нулевой длины, содержит только пробелы или содержит один или несколько недопустимых символов, определенных в GetInvalidPathChars. -или- Системе не удалось получить абсолютный путь.

Tony Sun 27.04.2017 14:12

Теоретически (согласно документам) это должно работать, проблема в том, что, по крайней мере, в .NET Core 3.1 это не так.

Michel Jansson 15.04.2020 14:11

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

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

Или же

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

Возможно, вы имели в виду: "return! FileName.Any (f => Path.GetInvalidFileNameChars (). Contains (f)) ‌;" ?

Jack Griffin 23.05.2018 20:27

@JackGriffin Конечно! Спасибо за внимание.

tmt 24.05.2018 13:53

Хотя этот код очень приятно читать, мы должны принять во внимание жалкие внутренние детали Path.GetInvalidFileNameChars. Взгляните сюда: linksource.microsoft.com/#mscorlib/system/io/path.cs,28‌ 9 - для каждого символа вашего fileName создается клон массива.

Piotr Zierhoffer 12.03.2020 14:45

«ДД: \\\\\ AAA ..... AAAA». Недействительно, но для вашего кода это так.

Ciccio Pasticcio 02.07.2020 23:04

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

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

Использование Split() составляет всего ~ 850 мс.

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

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

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

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

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

Igor Levicki 11.08.2020 16:11

Эта проверка

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.

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

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

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

Другие вопросы по теме