كيف يمكنني التحقق مما إذا كانت السلسلة المحددة هي اسم ملف قانوني/صالح ضمن Windows؟

StackOverflow https://stackoverflow.com/questions/62771

  •  09-06-2019
  •  | 
  •  

سؤال

أريد تضمين وظيفة إعادة تسمية الملف الدفعي في طلبي.يمكن للمستخدم كتابة نمط اسم ملف الوجهة و(بعد استبدال بعض أحرف البدل في النمط) أحتاج إلى التحقق مما إذا كان سيكون اسم ملف قانونيًا ضمن Windows.لقد حاولت استخدام التعبير العادي مثل [a-zA-Z0-9_]+ ولكنها لا تتضمن العديد من الأحرف الوطنية المحددة من لغات مختلفة (على سبيل المثال.علامات تغير الصوت وما إلى ذلك).ما هي أفضل طريقة للقيام بهذا الفحص؟

هل كانت مفيدة؟

المحلول

يمكنك الحصول على قائمة بالأحرف غير الصالحة من Path.GetInvalidPathChars و GetInvalidFileNameChars.

محدث: يرى اقتراح ستيف كوبر حول كيفية استخدامها في التعبير العادي.

التحديث 2: لاحظ أنه وفقًا لقسم الملاحظات في MSDN "لا يُضمن أن يحتوي الصفيف الذي تم إرجاعه من هذه الطريقة على مجموعة كاملة من الأحرف غير الصالحة في أسماء الملفات والدلائل." الإجابة المقدمة من ستة أحرف يذهب إلى مزيد من التفاصيل.

نصائح أخرى

من "تسمية ملف أو دليل" في 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)

ل .Net Framework قبل الإصدار 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

حاول استخدامه، وفخ الخطأ.قد تتغير المجموعة المسموح بها عبر أنظمة الملفات، أو عبر إصدارات مختلفة من 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 kernel استخدام الأحرف الموجودة في النطاق من 1 إلى 31 (أي 0x01-0x1F) والأحرف " * :< > ؟\ |.على الرغم من أن NTFS يسمح لكل مكون مسار (الدليل أو اسم الملف) بأن يبلغ طوله 255 حرفًا ومسارات يصل طولها إلى حوالي 32767 حرفًا، إلا أن Windows kernel يدعم فقط المسارات التي يصل طولها إلى 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 صالحة فقط، ولكن إذا كان يمثل بالفعل مسارًا يمكن كتابته بواسطة هذه العملية.

لاستكمال الإجابات الأخرى، إليك بعض الحالات الإضافية التي قد ترغب في أخذها في الاعتبار.

من MSDN, ، إليك قائمة بالأحرف غير المسموح بها:

استخدم أي حرف تقريبًا في صفحة الرموز الحالية للاسم، بما في ذلك أحرف Unicode والأحرف الموجودة في مجموعة الأحرف الموسعة (128–255)، باستثناء ما يلي:

  • غير مسموح بالأحرف المحجوزة التالية:< > :" / \ | ؟*
  • لا يُسمح بالأحرف التي تتراوح تمثيلاتها الصحيحة من صفر إلى 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;

لقد حاولت تشغيل هذه الطريقة وغيرها من الطرق المذكورة أعلاه على اسم ملف/مسار 1,000,000 مرة في LinqPad.

استخدام Split() هو فقط ~ 850 مللي ثانية.

استخدام Regex("[" + Regex.Escape(new string(System.IO.Path.GetInvalidPathChars())) + "]") حوالي 6 ثواني.

تعد التعبيرات النمطية الأكثر تعقيدًا أسوأ بكثير، كما هو الحال مع بعض الخيارات الأخرى، مثل استخدام الأساليب المختلفة في Path class للحصول على اسم الملف والسماح للتحقق الداخلي الخاص بهم بالقيام بالمهمة (على الأرجح بسبب الحمل الزائد لمعالجة الاستثناءات).

من المسلم به أنه ليس في كثير من الأحيان تحتاج إلى التحقق من صحة مليون اسم ملف، لذا فإن التكرار الفردي مناسب لمعظم هذه الطرق على أي حال.لكنها لا تزال فعالة جدًا إذا كنت تبحث فقط عن الأحرف غير الصالحة.

محاولتي:

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

يحاول إنشاء الملف وإرجاع خطأ إذا كان هناك استثناء.بالطبع، أحتاج إلى إنشاء الملف ولكني أعتقد أنها الطريقة الأكثر أمانًا للقيام بذلك.يرجى أيضًا ملاحظة أنني لا أقوم بحذف الدلائل التي تم إنشاؤها.

يمكنك أيضًا استخدام الطريقة الأولى لإجراء التحقق الأساسي، ثم معالجة الاستثناءات بعناية عند استخدام المسار.

أقترح فقط استخدام 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 هي:

\ / : * ? " < > |

يمكنك بسهولة كتابة تعبير للتحقق من وجود تلك الأحرف.لكن الحل الأفضل هو محاولة تسمية الملفات كما يريد المستخدم، وتنبيههم عندما لا يتم تثبيت اسم الملف.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top