it-swarm-vi.tech

Làm cách nào để kiểm tra xem một chuỗi đã cho có phải là tên tệp hợp pháp/hợp lệ trong Windows không?

Tôi muốn bao gồm một chức năng đổi tên tập tin hàng loạt trong ứng dụng của tôi. Người dùng có thể nhập mẫu tên tệp đích và (sau khi thay thế một số ký tự đại diện trong mẫu) Tôi cần kiểm tra xem đó có phải là tên tệp hợp pháp trong Windows không. Tôi đã cố gắng sử dụng biểu thức chính quy như [a-zA-Z0-9_]+ nhưng nó không bao gồm nhiều ký tự cụ thể theo quốc gia từ nhiều ngôn ngữ khác nhau (ví dụ: âm sắc và vv). Cách tốt nhất để làm một kiểm tra như vậy là gì?

150
tomash

Bạn có thể nhận danh sách các ký tự không hợp lệ từ Path.GetInvalidPathCharsGetInvalidFileNameChars .

CẬP NHẬT: Xem Gợi ý của Steve Cooper về cách sử dụng những từ này trong biểu thức chính quy.

UPD2: Lưu ý rằng theo phần Ghi chú trong MSDN "Mảng được trả về từ phương thức này không được đảm bảo chứa toàn bộ các ký tự không hợp lệ trong tên tệp và thư mục." Câu trả lời được cung cấp bởi sixlettervaliabled đi vào chi tiết hơn.

96
Eugene Katz

Từ "Đặt tên tệp hoặc thư mục" của MSDN - đây là các quy ước chung cho tên tệp hợp pháp trong Windows:

Bạn có thể sử dụng bất kỳ ký tự nào trong trang mã hiện tại (Unicode/ANSI trên 127), ngoại trừ:

  • <>:"/\|?*
  • Các ký tự có biểu diễn số nguyên là 0-31 (nhỏ hơn ASCII dấu cách
  • Bất kỳ ký tự nào khác mà hệ thống tệp đích không cho phép (giả sử, dấu chấm hoặc dấu cách)
  • Bất kỳ tên nào trong số DOS LPT8, LPT9 (và tránh AUX.txt, v.v.)
  • Tên tệp là tất cả các dấu chấm

Một số điều tùy chọn để kiểm tra:

  • Đường dẫn tệp (bao gồm tên tệp) có thể không có nhiều hơn 260 ký tự (không sử dụng tiền tố \?\)
  • Đường dẫn tệp Unicode (bao gồm tên tệp) có hơn 32.000 ký tự khi sử dụng \?\ (lưu ý rằng tiền tố có thể mở rộng các thành phần thư mục và khiến nó vượt quá giới hạn 32.000)
116
user7116

Đối với .Net Framework trước 3.5, điều này sẽ hoạt động:

Kết hợp biểu thức chính quy sẽ giúp bạn có một số cách. Đây là đoạn trích sử dụng hằng số 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;
}

Đối với .Net Framework sau 3.0, điều này sẽ hoạt động:

http://msdn.Microsoft.com/en-us/l Library/system.io.path.getinvalidpathchars (v = vs.90) .aspx

Kết hợp biểu thức chính quy sẽ giúp bạn có một số cách. Đây là đoạn trích sử dụng hằng số 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;
}

Khi bạn biết điều đó, bạn cũng nên kiểm tra các định dạng khác nhau, ví dụ: c:\my\drive\\server\share\dir\file.ext

62
Steve Cooper

Cố gắng sử dụng nó, và bẫy cho lỗi. Bộ được phép có thể thay đổi trên các hệ thống tệp hoặc trên các phiên bản Windows khác nhau. Nói cách khác, nếu bạn muốn biết nếu Windows thích tên đó, hãy đưa tên đó và để cho nó biết.

25
Dewayne Christensen

Lớp này làm sạch tên tập tin và đường dẫn; sử dụng nó như 

var myCleanPath = PathSanitizer.SanitizeFilename(myBadPath, ' ');

Đây là mã;

/// <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

Đây là những gì tôi sử dụng:

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

Mẫu đầu tiên tạo một biểu thức chính quy chỉ chứa các tên và ký tự tệp không hợp lệ/không hợp lệ cho các nền tảng Windows. Cái thứ hai làm tương tự nhưng đảm bảo rằng tên đó là hợp pháp cho bất kỳ nền tảng nào.

22
Scott Dorman

Một trường hợp cần lưu ý, điều làm tôi ngạc nhiên khi lần đầu tiên tôi phát hiện ra điều đó: Windows cho phép các ký tự không gian hàng đầu trong tên tệp! Ví dụ: sau đây là tất cả các tên tệp hợp pháp và riêng biệt trên Windows (trừ dấu ngoặc kép):

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

Một điểm trừ từ điều này: Hãy thận trọng khi viết mã cắt các khoảng trắng hàng đầu/theo dõi từ một chuỗi tên tệp.

18
Jon Schneider

Đơn giản hóa câu trả lời của Eugene Katz:

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

Hoặc là

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

Microsoft Windows: Windows kernel cấm sử dụng các ký tự trong phạm vi 1-31 (nghĩa là 0x01-0x1F) và các ký tự "*: <>?\|. Mặc dù NTFS cho phép mỗi thành phần đường dẫn (thư mục hoặc tên tệp) dài 255 ký tự và đường dẫn dài tới khoảng 32767 ký tự, nhân Windows chỉ hỗ trợ các đường dẫn dài tối đa 259 ký tự. Ngoài ra, Windows cấm sử dụng các tên thiết bị MS-DOS AUX, CLOCK $, COM1, COM2, COM3, COM4, ​​COM5, COM6, COM7, COM8, COM9, CON, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, LPT9, NUL và PRN, cũng như các tên này với bất kỳ tiện ích mở rộng nào (ví dụ: AUX.txt), ngoại trừ khi sử dụng Đường dẫn UNC dài (ví dụ: \.\C:\nul.txt hoặc \?\D:\aux\con). (Trên thực tế, CLOCK $ có thể được sử dụng nếu tiện ích mở rộng được cung cấp.) Những hạn chế này chỉ áp dụng cho Windows - Linux, ví dụ, cho phép sử dụng "*: <>?\| ngay cả trong NTFS.

Nguồn: http://en.wikipedia.org/wiki/Filename

8
Martin Faartoft

Thay vì bao gồm rõ ràng tất cả các ký tự có thể, bạn có thể thực hiện một regex để kiểm tra sự hiện diện của các ký tự không hợp lệ và sau đó báo cáo lỗi. Lý tưởng nhất là ứng dụng của bạn nên đặt tên cho các tệp chính xác như người dùng mong muốn và chỉ khóc khi gặp phải lỗi.

7
ConroyP

Tôi sử dụng điều này để loại bỏ các ký tự không hợp lệ trong tên tệp mà không ném ngoại lệ:

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

Ngoài ra CON, PRN, AUX, NUL, COM # và một số tên khác không bao giờ là tên tệp hợp pháp trong bất kỳ thư mục có bất kỳ tiện ích mở rộng nào.

5
Roland Rabien

Câu hỏi đặt ra là bạn đang cố xác định xem tên đường dẫn có phải là đường dẫn cửa sổ hợp pháp hay không, nếu nó hợp pháp trên hệ thống nơi mã đang chạy. ? Tôi nghĩ rằng cái sau quan trọng hơn, vì vậy cá nhân tôi có thể phân tách đường dẫn đầy đủ và thử sử dụng _mkdir để tạo thư mục mà tệp thuộc về, sau đó thử tạo tệp.

Bằng cách này, bạn biết không chỉ nếu đường dẫn chỉ chứa các ký tự cửa sổ hợp lệ, mà nếu nó thực sự đại diện cho một đường dẫn có thể được viết bởi quá trình này.

5
kfh

Để bổ sung cho các câu trả lời khác, đây là một vài trường hợp Edge bổ sung mà bạn có thể muốn xem xét.

4
Joe

Từ MSDN , đây là danh sách các ký tự không được phép:

Sử dụng hầu hết mọi ký tự trong trang mã hiện tại cho một tên, bao gồm các ký tự và ký tự Unicode trong bộ ký tự mở rộng (128 nhiệt255), ngoại trừ:

  • Các ký tự dành riêng sau đây không được phép: <>: "/\|? *
  • Các ký tự có biểu diễn số nguyên nằm trong phạm vi từ 0 đến 31 không được phép.
  • Bất kỳ ký tự nào khác mà hệ thống tệp đích không cho phép.
3
Mark Biek

Biểu thức thông thường là quá mức cần thiết cho tình huống này. Bạn có thể sử dụng phương thức String.IndexOfAny() kết hợp với Path.GetInvalidPathChars()Path.GetInvalidFileNameChars().

Cũng lưu ý rằng cả hai phương thức Path.GetInvalidXXX() đều sao chép một mảng bên trong và trả về bản sao. Vì vậy, nếu bạn sẽ làm điều này rất nhiều (hàng nghìn và hàng nghìn lần), bạn có thể lưu trữ một bản sao của mảng ký tự không hợp lệ để sử dụng lại.

2
s n

Ngoài ra hệ thống tập tin đích là quan trọng.

Trong NTFS, một số tệp không thể được tạo trong các thư mục cụ thể . E.G. $ Khởi động trong root 

2
Dominik Weber

Đây là một câu hỏi đã được trả lời, nhưng chỉ vì "Các lựa chọn khác", đây là một câu hỏi không lý tưởng:

(không lý tưởng vì sử dụng Ngoại lệ làm kiểm soát luồng là "Điều xấu", nói chung)

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

Nếu bạn chỉ cố kiểm tra xem một chuỗi giữ tên/đường dẫn tệp của bạn có bất kỳ ký tự không hợp lệ nào không, thì phương pháp nhanh nhất tôi tìm thấy là sử dụng Split() để chia tên tệp thành một mảng các phần bất cứ nơi nào có ký tự không hợp lệ. Nếu kết quả chỉ là một mảng 1, không có ký tự không hợp lệ. :-)

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;

Tôi đã thử chạy phương thức này và các phương pháp khác được đề cập ở trên trên tên tệp/đường dẫn 1.000.000 lần trong LinqPad.

Sử dụng Split() chỉ ~ 850ms.

Sử dụng Regex("[" + Regex.Escape(new string(System.IO.Path.GetInvalidPathChars())) + "]") là khoảng 6 giây.

Các biểu thức chính quy phức tạp hơn NHIỀU tệ hơn, cũng như một số tùy chọn khác, như sử dụng các phương thức khác nhau trên lớp Path để lấy tên tệp và để xác thực nội bộ của chúng thực hiện công việc (rất có thể do xử lý ngoại lệ).

Cấp cho nó không thường xuyên bạn cần xác thực 1 triệu tên tệp, do đó, một lần lặp duy nhất là tốt cho hầu hết các phương thức này. Nhưng nó vẫn khá hiệu quả và hiệu quả nếu bạn chỉ tìm kiếm các ký tự không hợp lệ.

1
Nick Albrecht

nhiều câu trả lời trong số này sẽ không hoạt động nếu tên tệp quá dài và chạy trên môi trường Windows 10 trước. Tương tự, hãy suy nghĩ về những gì bạn muốn làm với các khoảng thời gian - cho phép dẫn đầu hoặc theo dõi là hợp lệ về mặt kỹ thuật, nhưng có thể tạo ra vấn đề nếu bạn không muốn tệp khó nhìn thấy hoặc xóa tương ứng.

Đây là thuộc tính xác thực tôi đã tạo để kiểm tra tên tệp hợp lệ. 

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

và các bài kiểm tra

[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

Nỗ lực của tôi:

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

Điều này không hoàn hảo vì Path.GetInvalidPathChars không trả về bộ ký tự hoàn chỉnh không hợp lệ trong tên tệp và thư mục và tất nhiên có nhiều sự tinh tế hơn.

Vì vậy, tôi sử dụng phương pháp này như là một bổ sung:

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

Nó cố gắng tạo tập tin và trả về false nếu có ngoại lệ. Tất nhiên, tôi cần tạo tập tin nhưng tôi nghĩ đó là cách an toàn nhất để làm điều đó. Cũng xin lưu ý rằng tôi không xóa các thư mục đã được tạo.

Bạn cũng có thể sử dụng phương thức đầu tiên để thực hiện xác nhận cơ bản và sau đó xử lý cẩn thận các ngoại lệ khi đường dẫn được sử dụng.

1
Maxence

Theo tôi, câu trả lời thích hợp duy nhất cho câu hỏi này là cố gắng sử dụng đường dẫn và để hệ điều hành và hệ thống tập tin xác nhận nó. Mặt khác, bạn chỉ thực hiện lại (và có thể là kém) tất cả các quy tắc xác thực mà HĐH và hệ thống tệp đã sử dụng và nếu các quy tắc đó được thay đổi trong tương lai, bạn sẽ phải thay đổi mã của mình để khớp với chúng.

0
Igor Levicki

Kiểm tra này

static bool IsValidFileName(string name)
{
    return
        !string.IsNullOrWhiteSpace(name) &&
        name.IndexOfAny(Path.GetInvalidFileNameChars()) < 0 &&
        !Path.GetFullPath(name).StartsWith(@"\\.\");
}

lọc các tên có ký tự không hợp lệ (<>:"/\|?* và ASCII 0-31), cũng như các thiết bị DOS dành riêng (CON, NUL, COMx). Nó cho phép các không gian hàng đầu và tất cả các tên chấm, phù hợp với Path.GetFullPath. (Tạo tập tin với không gian hàng đầu thành công trên hệ thống của tôi).


Đã sử dụng .NET Framework 4.7.1, đã thử nghiệm trên Windows 7.

0
Vlad

Tôi có ý tưởng này từ một người nào đó. - không biết ai. Hãy để hệ điều hành làm việc nặng.

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

Một lớp lót để xác minh ký tự ảo trong chuỗi:

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

Tôi đề nghị chỉ sử dụng Path.GetFullPath ()

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