Как мне проверить, является ли данная строка законным именем файла в Windows?
-
09-06-2019 - |
Вопрос
Я хочу включить функцию переименования пакетного файла в свое приложение.Пользователь может ввести шаблон имени файла назначения и (после замены некоторых подстановочных знаков в шаблоне) Мне нужно проверить, будет ли это законное имя файла в Windows.Я пытался использовать регулярное выражение типа [a-zA-Z0-9_]+
но он не включает в себя множество национальных символов из разных языков (например,умляуты и так далее).Каков наилучший способ провести такую проверку?
Решение
Вы можете получить список недопустимых символов из Path.GetInvalidPathChars
и GetInvalidFileNameChars
.
UPD: Видишь Предложение Стива Купера о том, как использовать их в регулярном выражении.
ОБНОВЛЕНИЕ 2: Обратите внимание, что согласно разделу замечаний в MSDN "Массив, возвращаемый этим методом, не гарантированно содержит полный набор символов, которые недопустимы в именах файлов и каталогов". Ответ предоставлен sixlettervaliables переходим к более подробным сведениям.
Другие советы
От 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)
Для Платформы .Net до версии 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 после версии 3.0 это должно сработать:
http://msdn.microsoft.com/en-us/library/system.io.path.getinvalidpathchars (v=против 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"
Один вывод из этого:Будьте осторожны при написании кода, который удаляет начальные / завершающие пробелы из строки имени файла.
Упрощая ответ Юджина Каца:
bool IsFileNameCorrect(string fileName){
return !fileName.Any(f=>Path.GetInvalidFileNameChars().Contains(f))
}
Или
bool IsFileNameCorrect(string fileName){
return fileName.All(f=>!Path.GetInvalidFileNameChars().Contains(f))
}
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: ul.txt или \?\D:\aux\con).(Фактически, CLOCK$ может использоваться, если указано расширение.) Эти ограничения применимы только к Windows - Linux, например, позволяет использовать " * :< > ?\ | даже в NTFS.
Источник: http://en.wikipedia.org/wiki/Filename
Вместо того чтобы явно включать все возможные символы, вы могли бы использовать регулярное выражение для проверки наличия недопустимых символов и затем сообщить об ошибке.В идеале ваше приложение должно называть файлы точно так, как желает пользователь, и ругаться только в том случае, если оно натыкается на ошибку.
Я использую это, чтобы избавиться от недопустимых символов в именах файлов без создания исключений:
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 в 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()
методы клонируют внутренний массив и возвращают клон.Поэтому, если вы собираетесь делать это часто (тысячи и тысячи раз), вы можете кэшировать копию недопустимого массива chars для повторного использования.
многие из этих ответов не будут работать, если имя файла слишком длинное и запущено в среде до 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()
составляет всего ~ 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, если возникает исключение.Конечно, мне нужно создать файл, но я думаю, что это самый безопасный способ сделать это.Пожалуйста, также обратите внимание, что я не удаляю созданные каталоги.
Вы также можете использовать первый метод для выполнения базовой проверки, а затем тщательно обрабатывать исключения при использовании пути.
Я предлагаю просто использовать Путь.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.
Одна строка для проверки некорректных символов в строке:
public static bool IsValidFilename(string testName) => !Regex.IsMatch(testName, "[" + Regex.Escape(new string(System.IO.Path.InvalidPathChars)) + "]");
Имена файлов Windows довольно неограниченны, так что на самом деле это может быть даже не это большая проблема.К символам, которые запрещены Windows, относятся:
\ / : * ? " < > |
Вы могли бы легко написать выражение, чтобы проверить, присутствуют ли эти символы.Однако лучшим решением было бы попытаться назвать файлы так, как хочет пользователь, и предупредить их, когда имя файла не сохраняется.