Как определить, соответствует ли файл маске файла? - PullRequest
25 голосов
/ 07 апреля 2009

Мне нужно решить, подходит ли имя файла к маске файла. Маска файла может содержать * или? персонажи. Есть ли простое решение для этого?

bool bFits = Fits("myfile.txt", "my*.txt");

private bool Fits(string sFileName, string sFileMask)
    {
        ??? anything simple here ???
    }

Ответы [ 11 ]

25 голосов
/ 03 ноября 2009

Я ценю нахождение ответа Джоэла - сэкономил мне время! Однако мне пришлось внести несколько изменений, чтобы метод сделал то, что ожидал бы большинство пользователей:

  • Я удалил ключевое слово this, предшествующее первому аргументу. Здесь он ничего не делает (хотя это может быть полезно, если метод предназначен для использования в качестве метода расширения, в этом случае он должен быть общедоступным и содержаться в статическом классе, а сам по себе быть статическим методом).
  • Я сделал регулярное выражение независимым от регистра, чтобы соответствовать стандартному поведению подстановочных знаков Windows (например, "c *. *" И "C *. *" Оба возвращают один и тот же результат).
  • Я добавил начальные и конечные якоря к регулярному выражению, опять же, чтобы соответствовать стандартному поведению подстановочных знаков Windows (например, «stuff.txt» будет соответствовать «stuff *» или «s *» или «s *. *», Но не просто "с").

private bool FitsMask(string fileName, string fileMask)
{
    Regex mask = new Regex(
        '^' + 
        fileMask
            .Replace(".", "[.]")
            .Replace("*", ".*")
            .Replace("?", ".")
        + '$',
        RegexOptions.IgnoreCase);
    return mask.IsMatch(fileName);
}

2009.11.04 Обновление: соответствует одной из нескольких масок

Для еще большей гибкости, здесь используется метод, совместимый со штекерами, поверх оригинала. Эта версия позволяет передавать несколько масок (отсюда и множественное число в имени второго параметра fileMasks ), разделенных строками, запятыми, вертикальными чертами или пробелами. Я хотел, чтобы я мог позволить пользователю разместить столько вариантов, сколько нужно, в ListBox, а затем выбрать все файлы, соответствующие любым из них. Обратите внимание, что некоторые элементы управления (например, ListBox) используют CR-LF для разрыва строки, в то время как другие (например, RichTextBox) используют только LF - поэтому в списке «Разделение» отображаются и «\ r \ n», и «\ n».

private bool FitsOneOfMultipleMasks(string fileName, string fileMasks)
{
    return fileMasks
        .Split(new string[] {"\r\n", "\n", ",", "|", " "},
            StringSplitOptions.RemoveEmptyEntries)
        .Any(fileMask => FitsMask(fileName, fileMask));
}

2009.11.17 Обновление: более изящная обработка ввода fileMask

Более ранняя версия FitsMask (которую я оставил для сравнения) справляется со своей задачей, но, поскольку мы рассматриваем ее как регулярное выражение, она выдаст исключение, если оно не является допустимым регулярным выражением, когда оно входит. Решение состоит в том, что мы действительно хотим, чтобы любые метасимволы регулярных выражений во входной fileMask считались литералами, а не метасимволами. Но нам все еще нужно обращаться с точкой, звездочкой и знаком вопроса. Таким образом, эта улучшенная версия FitsMask безопасно удаляет эти три символа, преобразует все оставшиеся метасимволы в литералы, а затем возвращает три интересных символа обратно в их форму "regex'ed".

Еще одно незначительное улучшение - это независимость от регистра для стандартного поведения Windows.

private bool FitsMask(string fileName, string fileMask)
{
    string pattern =
         '^' + 
         Regex.Escape(fileMask.Replace(".", "__DOT__")
                         .Replace("*", "__STAR__")
                         .Replace("?", "__QM__"))
             .Replace("__DOT__", "[.]")
             .Replace("__STAR__", ".*")
             .Replace("__QM__", ".")
         + '$';
    return new Regex(pattern, RegexOptions.IgnoreCase).IsMatch(fileName);
}

2010.09.30 Обновление: где-то на этом пути началась страсть ...

Я был упущен, когда не обновлял это ранее, но эти ссылки, вероятно, будут интересны читателям, которые добрались до этой точки:

22 голосов
/ 07 апреля 2009

Попробуйте это:

private bool FitsMask(string sFileName, string sFileMask)
{
    Regex mask = new Regex(sFileMask.Replace(".", "[.]").Replace("*", ".*").Replace("?", "."));
    return mask.IsMatch(sFileName);
}
12 голосов
/ 17 февраля 2010

Многие люди этого не знают, но .NET включает внутренний класс, называемый PatternMatcher (в пространстве имен System.IO).

Этот статический класс содержит только 1 метод: public static bool StrictMatchPattern(string expression, string name)

Этот метод используется .net всякий раз, когда ему нужно сравнить файлы с подстановочными знаками (FileSystemWatcher, GetFiles () и т. Д.)

Используя рефлектор, я выставил здесь код. На самом деле не прошел через это, чтобы понять, как это работает, но это прекрасно работает,

Так что это код для тех, кто не хочет работать с неэффективным способом RegEx:

public static class PatternMatcher
{
    // Fields
    private const char ANSI_DOS_QM = '<';
    private const char ANSI_DOS_STAR = '>';
    private const char DOS_DOT = '"';
    private const int MATCHES_ARRAY_SIZE = 16;

    // Methods
    public static bool StrictMatchPattern(string expression, string name)
    {
        expression = expression.ToLowerInvariant();
        name = name.ToLowerInvariant();
        int num9;
        char ch = '\0';
        char ch2 = '\0';
        int[] sourceArray = new int[16];
        int[] numArray2 = new int[16];
        bool flag = false;
        if (((name == null) || (name.Length == 0)) || ((expression == null) || (expression.Length == 0)))
        {
            return false;
        }
        if (expression.Equals("*") || expression.Equals("*.*"))
        {
            return true;
        }
        if ((expression[0] == '*') && (expression.IndexOf('*', 1) == -1))
        {
            int length = expression.Length - 1;
            if ((name.Length >= length) && (string.Compare(expression, 1, name, name.Length - length, length, StringComparison.OrdinalIgnoreCase) == 0))
            {
                return true;
            }
        }
        sourceArray[0] = 0;
        int num7 = 1;
        int num = 0;
        int num8 = expression.Length * 2;
        while (!flag)
        {
            int num3;
            if (num < name.Length)
            {
                ch = name[num];
                num3 = 1;
                num++;
            }
            else
            {
                flag = true;
                if (sourceArray[num7 - 1] == num8)
                {
                    break;
                }
            }
            int index = 0;
            int num5 = 0;
            int num6 = 0;
            while (index < num7)
            {
                int num2 = (sourceArray[index++] + 1) / 2;
                num3 = 0;
            Label_00F2:
                if (num2 != expression.Length)
                {
                    num2 += num3;
                    num9 = num2 * 2;
                    if (num2 == expression.Length)
                    {
                        numArray2[num5++] = num8;
                    }
                    else
                    {
                        ch2 = expression[num2];
                        num3 = 1;
                        if (num5 >= 14)
                        {
                            int num11 = numArray2.Length * 2;
                            int[] destinationArray = new int[num11];
                            Array.Copy(numArray2, destinationArray, numArray2.Length);
                            numArray2 = destinationArray;
                            destinationArray = new int[num11];
                            Array.Copy(sourceArray, destinationArray, sourceArray.Length);
                            sourceArray = destinationArray;
                        }
                        if (ch2 == '*')
                        {
                            numArray2[num5++] = num9;
                            numArray2[num5++] = num9 + 1;
                            goto Label_00F2;
                        }
                        if (ch2 == '>')
                        {
                            bool flag2 = false;
                            if (!flag && (ch == '.'))
                            {
                                int num13 = name.Length;
                                for (int i = num; i < num13; i++)
                                {
                                    char ch3 = name[i];
                                    num3 = 1;
                                    if (ch3 == '.')
                                    {
                                        flag2 = true;
                                        break;
                                    }
                                }
                            }
                            if ((flag || (ch != '.')) || flag2)
                            {
                                numArray2[num5++] = num9;
                                numArray2[num5++] = num9 + 1;
                            }
                            else
                            {
                                numArray2[num5++] = num9 + 1;
                            }
                            goto Label_00F2;
                        }
                        num9 += num3 * 2;
                        switch (ch2)
                        {
                            case '<':
                                if (flag || (ch == '.'))
                                {
                                    goto Label_00F2;
                                }
                                numArray2[num5++] = num9;
                                goto Label_028D;

                            case '"':
                                if (flag)
                                {
                                    goto Label_00F2;
                                }
                                if (ch == '.')
                                {
                                    numArray2[num5++] = num9;
                                    goto Label_028D;
                                }
                                break;
                        }
                        if (!flag)
                        {
                            if (ch2 == '?')
                            {
                                numArray2[num5++] = num9;
                            }
                            else if (ch2 == ch)
                            {
                                numArray2[num5++] = num9;
                            }
                        }
                    }
                }
            Label_028D:
                if ((index < num7) && (num6 < num5))
                {
                    while (num6 < num5)
                    {
                        int num14 = sourceArray.Length;
                        while ((index < num14) && (sourceArray[index] < numArray2[num6]))
                        {
                            index++;
                        }
                        num6++;
                    }
                }
            }
            if (num5 == 0)
            {
                return false;
            }
            int[] numArray4 = sourceArray;
            sourceArray = numArray2;
            numArray2 = numArray4;
            num7 = num5;
        }
        num9 = sourceArray[num7 - 1];
        return (num9 == num8);
    }
}
10 голосов
/ 29 октября 2013

Кажется, что ни один из этих ответов не сработает, и у msorens слишком сложный. Это должно работать просто отлично:

public static Boolean Fits(string sFileName, string sFileMask)
{
    String convertedMask = "^" + Regex.Escape(sFileMask).Replace("\\*", ".*").Replace("\\?", ".") + "$";
    Regex regexMask = new Regex(convertedMask, RegexOptions.IgnoreCase);
    return regexMask.IsMatch(sFileName)
}

Это гарантирует, что возможные символы регулярных выражений в маске экранируются, заменяет \ * и \? И окружает их всеми символами ^ и $, чтобы обозначить границы.

Конечно, в большинстве ситуаций гораздо полезнее просто превратить это в инструментальную функцию FileMaskToRegex, которая возвращает объект Regex, так что вы только что получили его один раз, а затем можете сделать цикл, в котором вы проверяете все строки из Ваш список файлов на нем.

public static Regex FileMaskToRegex(string sFileMask)
{
    String convertedMask = "^" + Regex.Escape(sFileMask).Replace("\\*", ".*").Replace("\\?", ".") + "$";
    return new Regex(convertedMask, RegexOptions.IgnoreCase);
}
4 голосов
/ 08 апреля 2015

Ниссим упомянул класс PatternMatcher в своем ответе ...

Здесь доступно объяснение:

http://referencesource.microsoft.com/#System/services/io/system/io/PatternMatcher.cs

Так что вам не нужно использовать отраженный код и угадать, как он работает.

Кроме того, я думаю, что использование этого кода, вероятно, является лучшим решением, поскольку оно гарантирует согласованное поведение при использовании одного и того же шаблона в ваших сравнениях и в методах Framework, таких как GetFiles().

3 голосов
/ 08 апреля 2015

Использование WildCardPattern класса из System.Management.Automation доступно как Пакет NuGet или в Windows PowerShell SDK.

WildcardPattern pattern = new WildcardPattern("my*.txt");
bool fits = pattern.IsMatch("myfile.txt");
3 голосов
/ 24 августа 2010

Самая быстрая версия ранее предложенной функции:

    public static bool FitsMasks(string filePath, params string[] fileMasks)
            // or
    public static Regex FileMasksToRegex(params string[] fileMasks)
    {
        if (!_maskRegexes.ContainsKey(fileMasks))
        {
            StringBuilder sb = new StringBuilder("^");
            bool first = true;
            foreach (string fileMask in fileMasks)
            {
                if(first) first =false; else sb.Append("|");
                sb.Append('(');
                foreach (char c in fileMask)
                {
                    switch (c)
                    {
                        case '*': sb.Append(@".*"); break;
                        case '?': sb.Append(@"."); break;
                        default:
                                sb.Append(Regex.Escape(c.ToString()));
                            break;
                    }
                }
                sb.Append(')');
            }
            sb.Append("$");
            _maskRegexes[fileMasks] = new Regex(sb.ToString(), RegexOptions.IgnoreCase);
        }
        return _maskRegexes[fileMasks].IsMatch(filePath);
                    // or
        return _maskRegexes[fileMasks];
    }
    static readonly Dictionary<string[], Regex> _maskRegexes = new Dictionary<string[], Regex>(/*unordered string[] comparer*/);

Примечания:

  1. Повторное использование объектов Regex.
  2. Использование StringBuilder для оптимизации создания регулярных выражений (несколько вызовов .Replace () выполняются медленно).
  3. Несколько масок в сочетании с ИЛИ.
  4. Еще одна версия, возвращающая регулярное выражение.
2 голосов
/ 22 апреля 2016

Из Windows 7 с использованием P / Invoke (без ограничения количества символов в 260):

// UNICODE_STRING for Rtl... method
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct UNICODE_STRING
{
    public ushort Length;
    public ushort MaximumLength;
    [MarshalAs(UnmanagedType.LPWStr)]
    string Buffer;

    public UNICODE_STRING(string buffer)
    {
        if (buffer == null)
            Length = MaximumLength = 0;
        else
            Length = MaximumLength = unchecked((ushort)(buffer.Length * 2));
        Buffer = buffer;
    }
}

// RtlIsNameInExpression method from NtDll.dll system library
public static class NtDll
{
    [DllImport("NtDll.dll", CharSet=CharSet.Unicode, ExactSpelling=true)]
    [return: MarshalAs(UnmanagedType.U1)]
    public extern static bool RtlIsNameInExpression(
        ref UNICODE_STRING Expression,
        ref UNICODE_STRING Name,
        [MarshalAs(UnmanagedType.U1)]
        bool IgnoreCase,
        IntPtr Zero
        );
}

public bool MatchMask(string mask, string fileName)
{
    // Expression must be uppercase for IgnoreCase == true (see MSDN for RtlIsNameInExpression)
    UNICODE_STRING expr = new UNICODE_STRING(mask.ToUpper());
    UNICODE_STRING name = new UNICODE_STRING(fileName);

    if (NtDll.RtlIsNameInExpression(ref expr, ref name, true, IntPtr.Zero))
    {
        // MATCHES !!!
    }
}
2 голосов
/ 07 апреля 2009

Если доступен PowerShell, он имеет прямую поддержку сопоставления типов подстановочных знаков (а также Regex).

WildcardPattern pat = new WildcardPattern("a*.b*");
if (pat.IsMatch(filename)) { ... }
0 голосов
/ 29 марта 2018

Как насчет использования отражения, чтобы получить доступ к функции в .NET Framework?

Как это:

public class PatternMatcher
{
  public delegate bool StrictMatchPatternDelegate(string expression, string name);
  public StrictMatchPatternDelegate StrictMatchPattern;
  public PatternMatcher()
  {
    Type patternMatcherType = typeof(FileSystemWatcher).Assembly.GetType("System.IO.PatternMatcher");
    MethodInfo patternMatchMethod = patternMatcherType.GetMethod("StrictMatchPattern", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
    StrictMatchPattern = (expression, name) => (bool)patternMatchMethod.Invoke(null, new object[] { expression, name });
  }
}

void Main()
{
  PatternMatcher patternMatcher = new PatternMatcher();
  Console.WriteLine(patternMatcher.StrictMatchPattern("*.txt", "test.txt")); //displays true
  Console.WriteLine(patternMatcher.StrictMatchPattern("*.doc", "test.txt")); //displays false
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...