programing tip

주어진 문자열이 Windows에서 유효한 / 유효한 파일 이름인지 어떻게 확인합니까?

itbloger 2020. 6. 5. 20:23
반응형

주어진 문자열이 Windows에서 유효한 / 유효한 파일 이름인지 어떻게 확인합니까?


응용 프로그램에 배치 파일 이름 바꾸기 기능을 포함하고 싶습니다. 사용자는 대상 파일 이름 패턴을 입력 할 수 있으며 (패턴에서 일부 와일드 카드를 바꾼 후) Windows에서 올바른 파일 이름인지 확인해야합니다. 정규 표현식을 사용하려고했지만 [a-zA-Z0-9_]+다양한 언어 (예 : 움라우트 등)의 국가 별 문자가 많이 포함되어 있지 않습니다. 그러한 점검을 수행하는 가장 좋은 방법은 무엇입니까?


Path.GetInvalidPathChars에서 잘못된 문자 목록을 얻을 수 있습니다 GetInvalidFileNameChars.

UPD : 정규 표현식에서이를 사용하는 방법에 대한 Steve Cooper의 제안참조하십시오 .

UPD2 : MSDN의 비고 섹션에 따르면 "이 방법에서 반환 된 배열에 파일 및 디렉터리 이름에 유효하지 않은 전체 문자 집합이 포함되어 있지는 않습니다." sixlettervaliables제공하는 답변 은 더 자세히 설명되어 있습니다.


에서 MSDN의 "이름 지정 파일 또는 디렉터리," 여기 합법적 인 파일 이름은 Windows에서 무엇을위한 일반적인 규칙입니다 :

다음을 제외하고 현재 코드 페이지 (유니 코드 / 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 한계를 초과 할 수 있음)

들어 닷넷 프레임 워크 이전 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;
}

들어 닷넷 프레임 워크 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


그것을 사용하고 오류를 잡으십시오. 허용되는 집합은 파일 시스템이나 다른 버전의 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"

이것에서 한 가지 탈취 : 파일 이름 문자열에서 선행 / 후행 공백을 자르는 코드를 작성할 때주의하십시오.


Eugene Katz의 답변 단순화 :

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 : \ nul.txt 또는 \? \ D : \ aux \ con) (확장자가 제공되는 경우 CLOCK $이 사용될 수 있습니다.) 이러한 제한은 Windows에만 적용됩니다. 예를 들어 리눅스는 "* : <>? \ | NTFS에서도.

출처 : http://en.wikipedia.org/wiki/Filename


가능한 모든 문자를 명시 적으로 포함하는 대신 정규식을 사용하여 잘못된 문자가 있는지 확인한 다음 오류를보고 할 수 있습니다. 이상적으로 응용 프로그램은 사용자가 원하는대로 정확하게 파일 이름을 지정해야하며 오류가 발생하면 파울 링 만합니다.


문제는 경로 이름이 유효한 Windows 경로인지 또는 코드가 실행중인 시스템에서 유효한지 확인하려고 하는 것입니다. ? 후자는 더 중요하다고 생각하므로 개인적으로 전체 경로를 분해하고 _mkdir을 사용하여 파일이 속한 디렉토리를 만든 다음 파일을 만들려고합니다.

이렇게하면 경로에 유효한 윈도우 문자 만 포함되어있을뿐만 아니라 실제로이 프로세스에서 쓸 수있는 경로를 나타내는 지 알 수 있습니다.


예외를 발생시키지 않고 파일 이름에서 유효하지 않은 문자를 제거하기 위해 이것을 사용합니다.

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 # 및 기타 일부는 확장명을 가진 디렉토리의 법적 파일 이름이 아닙니다.


다른 답변을 보완하기 위해 고려해야 할 몇 가지 추가 사례가 있습니다.


에서 MSDN , 여기에 허용되지 않는 문자의 목록은 다음과 같습니다

다음을 제외하고 확장 코드 세트 (128–255)의 유니 코드 문자 및 문자를 포함하여 이름에 현재 코드 페이지의 거의 모든 문자를 사용하십시오.

  • 다음 예약 문자는 허용되지 않습니다 : <> : "/ \ |? *
  • 정수 표현이 0에서 31 사이의 문자는 허용되지 않습니다.
  • 대상 파일 시스템이 허용하지 않는 다른 문자

또한 대상 파일 시스템이 중요합니다.

NTFS에서는 특정 파일에서 일부 파일을 만들 수 없습니다. 루트에서 EG $ Boot


이것은 이미 답변 된 질문이지만 "기타 옵션"을 위해서만 여기에 비 이상적인 질문이 있습니다.

(흐름 제어로 예외를 사용하는 것이 일반적으로 "나쁜 것"이기 때문에 비 이상적임)

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 배열의 사본을 캐시 할 수 있습니다.


파일 이름 / 경로를 포함하는 문자열에 유효하지 않은 문자가 있는지 확인하려는 경우 가장 빠른 방법 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클래스 의 다양한 메소드를 사용하여 파일 이름을 얻고 내부 유효성 검사가 작업을 수행하도록하는 것과 같이 다른 옵션 중 일부와 마찬가지로 공정이 훨씬 나쁩니다 (대부분 예외 처리 오버 헤드로 인해).

백만 개의 파일 이름을 유효성 검사해야하는 경우가 많지 않으므로 대부분의 이러한 방법에는 단일 반복이 적합합니다. 그러나 유효하지 않은 문자 만 찾는 경우에도 여전히 효율적이고 효과적입니다.


파일 이름이 너무 길고 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"));
}

내 시도 :

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

It tries to create the file and return false if there is an exception. Of course, I need to create the file but I think it's the safest way to do that. Please also note that I am not deleting directories that have been created.

You can also use the first method to do basic validation, and then handle carefully the exceptions when the path is used.


I suggest just use the Path.GetFullPath()

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

I got this idea from someone. - don't know who. Let the OS do the heavy lifting.

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

This check

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

filters out names with invalid chars (<>:"/\|?* and ASCII 0-31), as well as reserved DOS devices (CON, NUL, COMx). It allows leading spaces and all-dot-names, consistent with Path.GetFullPath. (Creating file with leading spaces succeeds on my system).


Used .NET Framework 4.7.1, tested on Windows 7.


One liner for verifying illigal chars in the string:

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

In my opinion, the only proper answer to this question is to try to use the path and let the OS and filesystem validate it. Otherwise you are just reimplementing (and probably poorly) all the validation rules that the OS and filesystem already use and if those rules are changed in the future you will have to change your code to match them.


Windows filenames are pretty unrestrictive, so really it might not even be that much of an issue. The characters that are disallowed by Windows are:

\ / : * ? " < > |

You could easily write an expression to check if those characters are present. A better solution though would be to try and name the files as the user wants, and alert them when a filename doesn't stick.

참고 URL : https://stackoverflow.com/questions/62771/how-do-i-check-if-a-given-string-is-a-legal-valid-file-name-under-windows

반응형