如何检查给定字符串是否是 Windows 下合法/有效的文件名?
-
09-06-2019 - |
题
我想在我的应用程序中包含批处理文件重命名功能。用户可以键入目标文件名模式(在替换模式中的一些通配符后),我需要检查它是否是 Windows 下的合法文件名。我尝试使用正则表达式,例如 [a-zA-Z0-9_]+
但它不包括来自不同语言的许多国家特定字符(例如变音符号等)。进行此类检查的最佳方法是什么?
解决方案
您可以从以下位置获取无效字符列表 Path.GetInvalidPathChars
和 GetInvalidFileNameChars
.
更新: 看 史蒂夫·库珀的建议 关于如何在正则表达式中使用它们。
更新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 个字符的 Unicode 文件路径(包括文件名)
\?\
(请注意,前缀可能会扩展目录组件并导致其超出 32,000 个限制)
为了 3.5 之前的 .Net 框架 这应该有效:
正则表达式匹配应该可以帮助您找到一些方法。这是使用的片段 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;
}
为了 3.0之后的.Net框架 这应该有效:
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"
从中得出的一点是:编写从文件名字符串中删除前导/尾随空格的代码时请务必小心。
简化尤金·卡茨的答案:
bool IsFileNameCorrect(string fileName){
return !fileName.Any(f=>Path.GetInvalidFileNameChars().Contains(f))
}
或者
bool IsFileNameCorrect(string fileName){
return fileName.All(f=>!Path.GetInvalidFileNameChars().Contains(f))
}
微软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 路径(例如,AUX.txt)时除外。\.\C: ul.txt 或 \?\D:\aux\con)。(事实上,如果提供了扩展,则可以使用 CLOCK$。)这些限制仅适用于 Windows - 例如,Linux 允许使用“ * :< >?|即使在NTF中。
您可以执行正则表达式来检查是否存在非法字符,然后报告错误,而不是显式包含所有可能的字符。理想情况下,您的应用程序应该完全按照用户的意愿命名文件,并且只有在偶然发现错误时才会抱怨。
我用它来删除文件名中的无效字符而不引发异常:
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 了解详情。
从 微软软件定义网络, ,以下是不允许使用的字符列表:
使用当前代码页中的几乎所有字符作为名称,包括 Unicode 字符和扩展字符集中的字符 (128–255),但以下字符除外:
- 不允许使用以下保留字符:< >:” / \ | ?*
- 不允许使用整数表示形式在 0 到 31 范围内的字符。
- 目标文件系统不允许的任何其他字符。
目标文件系统也很重要。
在NTFS下,某些文件无法在特定目录中创建。例如。$在根目录下启动
这是一个已经回答的问题,但只是为了“其他选项”,这是一个不理想的问题:
(不理想,因为通常使用异常作为流量控制是一件“坏事”)
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()
方法克隆内部数组并返回克隆。因此,如果您要经常这样做(成千上万次),您可以缓存无效字符数组的副本以供重用。
如果文件名太长并且在 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;
我尝试在 LinqPad 中对文件/路径名运行此方法和上述其他方法 1,000,000 次。
使用 Split()
仅~850ms。
使用 Regex("[" + Regex.Escape(new string(System.IO.Path.GetInvalidPathChars())) + "]")
大约是6秒。
更复杂的正则表达式会更糟糕,其他一些选项也是如此,比如使用 Path
类来获取文件名并让其内部验证完成工作(很可能是由于异常处理的开销)。
当然,您并不经常需要验证 100 万个文件名,因此对于大多数这些方法来说,一次迭代就足够了。但如果您只是寻找无效字符,它仍然非常高效。
我的尝试:
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。当然,我需要创建该文件,但我认为这是最安全的方法。另请注意,我不会删除已创建的目录。
您也可以使用第一种方法进行基本验证,然后仔细处理路径使用时的异常。
我建议只使用 Path.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 不允许使用的字符有:
\ / : * ? " < > |
您可以轻松编写一个表达式来检查这些字符是否存在。不过,更好的解决方案是尝试根据用户的需要命名文件,并在文件名不固定时提醒他们。