Форум программистов, компьютерный форум, киберфорум
stpigidy
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  

C#, поиск файла по маске

Запись от stpigidy размещена 23.11.2021 в 05:12
Показов 2454 Комментарии 3

Преамбула

Когда-то уже говорил, что стандартная функция C# Directory.GetFiles(); неправильно ищет файлы по маске. И даже сделал на скорую руку кривофикс, но кривофикс действительно оказался именно что криво. Во-первых, срабатывал только для некоторых масок, а во-вторых, оказался чувствительным к регистру имен файлов. Делаем более прямое исправление.

Вспомогательные функции

Заведем вспомогательную функцию, которая будет добавлять конечный слэш (\) к имени директории. Оно не особо надо, но пусть будет для порядка.

C#
1
2
3
4
5
6
7
8
9
private static string AddSlash(string st)
{
    if (st.EndsWith("\\"))
    {
        return st;
    }
 
    return st + "\\";
}
И функцию, получающую имя файла из полного пути. Конечно, можно было бы воспользоваться классом FileInfo из System.IO, но тут операция совсем уж простая, а FileInfo может сгенерировать ненужный Exception. Проще получить имя файла с помощью строковой операции:

C#
1
2
3
4
5
6
7
8
private static string GetNameOnly(string FullName)
{
    int LastSlash = FullName.LastIndexOf("\\");
    
    if (LastSlash == -1) return FullName;
 
    return FullName.Substring(LastSlash + 1);
}
Преобразование маски файла в регулярное выражение.

Да, я таки решил воспользоваться нелюбимыми регекспами. Впрочем, маска файла и есть регулярное выражение, только с упрощенным синтаксисом.

1. В имени файла могут встретиться символы, считающиеся служебными в регулярном выражении (. ^ $ { } [ ] ( ) +), их необходимо экранировать, чтоб они воспринимались обработчиком регулярных выражений, как обычные, а не служебные символы.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//точка в маске файла должна быть точкой в регулярном выражении
//экранируем
Mask = Mask.Replace(".", "\\.");
//^,$,{,},[,],(,),+ в regexp служебные, в именах файла допустимые
//экранируем
Mask = Mask.Replace("^", "\\^");
Mask = Mask.Replace("$", "\\$");
Mask = Mask.Replace("{", "\\{");
Mask = Mask.Replace("}", "\\}");
Mask = Mask.Replace("[", "\\[");
Mask = Mask.Replace("[", "\\[");
Mask = Mask.Replace("(", "\\(");
Mask = Mask.Replace(")", "\\(");
Mask = Mask.Replace("+", "\\+");
2. '*' - в маске файла это любой символ, или их отсутствие. В регулярном выражении этому соответствует комбинация '.*', заменяем:

C#
1
Mask = Mask.Replace("*", ".*");
3. '?' в маске файла - любой существующий символ. В регулярном выражении это символ '.' (точка), заменяем:

C#
1
Mask = Mask.Replace("?", ".");
4. Осталось ограничить работу регулярного выражения началом и концом строки, строкой будет являться имя (маска) файла. Начало строки обозначается символом ^, конец символом $. Добавляем:

C#
1
Mask = "^" + Mask + "$";
Функция целиком:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private static string Mask2Reg(string Mask)
{
    //точка в маске файла должна быть точкой в регулярном выражении
    //экранируем
    Mask = Mask.Replace(".", "\\.");
    //^,$,{,},[,],(,),+ в regexp служебные, в именах файла допустимые
    //экранируем
    Mask = Mask.Replace("^", "\\^");
    Mask = Mask.Replace("$", "\\$");
    Mask = Mask.Replace("{", "\\{");
    Mask = Mask.Replace("}", "\\}");
    Mask = Mask.Replace("[", "\\[");
    Mask = Mask.Replace("[", "\\[");
    Mask = Mask.Replace("(", "\\(");
    Mask = Mask.Replace(")", "\\(");
    Mask = Mask.Replace("+", "\\+");
    //* - любое количество любого символа, 
    //в regexp любой символ - точка, любое количество *
    Mask = Mask.Replace("*", ".*");
    //? - любой символ, в regexp любой символ - точка.
    Mask = Mask.Replace("?", ".");
 
    //добавляем начало и конец строки к имени файла.
    Mask = "^" + Mask + "$";
 
    return Mask;
}
Модификация функции поиска

В модифицированную функцию поиска передаются такие же параметры, как и в функцию Directory.GetFiles(); т.е. маска файла, путь до каталога и перечисление SearchOption, которое может принимать два значения: SearchOption.AllDirectories - поиск с подкаталогами и SearchOption.TopDirectoryOnly - поиск только в текущем каталоге.

Внутри функции:

1. Преобразуем маску файла в регулярное выражение:

C#
1
string MaskRegStr = Mask2Reg(sMask);
2. Добавляем слеш к пути поиска (на всякий случай):

C#
1
sPath = AddSlash(sPath);
3. Заводим List <string>, куда будем складировать отфильтрованные файлы из найденных (на то, как криво работает Directory.GetFiles() есть ссылки в начале заметки).

List<string> FoundFiles = new List<string>();

4. Создаем обработчик регулярных выражений с опцией RegexOptions.IgnoreCase, чтобы игнорировать регистр входной строки (в нашем случае - имени файла).

C#
1
Regex MaskReg = new Regex(MaskRegStr, RegexOptions.IgnoreCase);
5. Вызываем функцию поиска из System.IO:

C#
1
string[] files = Directory.GetFiles(sPath, sMask, SO);
6. Фильтруем вывод на предмет лишних файлов (см. подробнее по ссылке в начале заметки). Фильтрация производится путем сравнения имени файла с ранее сгенерированным регулярным выражением. Если имя файла соответствует регулярке, оно добавляется в List:

C#
1
2
3
4
5
6
7
foreach (string filename in files)
{                                
    if (MaskReg.IsMatch(GetNameOnly(filename)))
    {
        FoundFiles.Add(filename);
    }
}
7. Результат возвращается в виде строкового массива:

C#
1
return FoundFiles.ToArray();
Функция целиком:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static string[] Find(string sPath, string sMask, SearchOption SO)
{
    string MaskRegStr = Mask2Reg(sMask);
    sPath = AddSlash(sPath);
    List<string> FoundFiles = new List<string>();
    Regex MaskReg = new Regex(MaskRegStr, RegexOptions.IgnoreCase);
 
    string[] files = Directory.GetFiles(sPath, sMask, SO);
 
    foreach (string filename in files)
    {                                
        if (MaskReg.IsMatch(GetNameOnly(filename)))
        {
            FoundFiles.Add(filename);
        }
    }
    return FoundFiles.ToArray();
}
Пример на GitHub

Вроде бы в этот раз все предусмотрел, и глюкоопцию встроенной функции поправил, и в регулярке нигде не ошибся.

Пример на GitHub
Размещено в C#
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 3
Комментарии
  1. Старый комментарий
    Аватар для Usaga
    Эх, велосипеды-костыли)

    Для получения имени файла из пути можно посмотреть в сторону класса Path. Слеш так руками тоже добавлять к путям не надо. Но если уж надо, то опять же, есть константа Path.PathSeparator с разделителем для текущей ОС. Да, наше творение может не только под виндой работать.
    Запись от Usaga размещена 23.11.2021 в 07:20 Usaga вне форума
  2. Старый комментарий
    Цитата Сообщение от Usaga
    Эх, велосипеды-костыли)
    Ой, спасибо. Надо будет переделать, один черт, собираюсь допилить этот класс, прикрутив к нему поиск файлов так, чтоб не обваливался при UnauthorizedAccessException
    Запись от stpigidy размещена 23.11.2021 в 15:13 stpigidy вне форума
  3. Старый комментарий
    Аватар для I can
    Могу обрадовать - впереди вас еще ожидают грабли в связи с использованием Directory.GetFiles
    Запись от I can размещена 24.11.2021 в 07:04 I can вне форума
 
Новые блоги и статьи
Установка MinGW GCC 16.2 и CMake
8Observer8 10.08.2026
VK Video: https:/ / vkvideo. ru/ video-240781534_456239017 YouTube: eY5-5PyI9NM Текстовая версия
Неделя из жизни имитационной модели склада: мои кривые руки растут, откуда надо
anaschu 10.08.2026
Неделя из жизни имитационной модели склада: как я почти написал неправильную логику и что с этим делать Работаю сейчас над учебно-рабочим проектом: строю в AnyLogic имитационную модель процессов. . .
Калькулятор для расчета родства
russiannick 07.08.2026
1. Задача: Создать калькулятор для расчета родства. Родственных связей существует 8 ступеней, такие как: p - отец P - мать q - муж Q - жена b - брат B - сестра s - сын S - дочь
Мир по моей воле
kumehtar 07.08.2026
Когда-то кажется, что всё просто. Ты весь такой светлый. Причиняешь добро. Борешься за справедливость в этом тёмном мире. Потом начинаешь замечать одну неприятную вещь. Почти каждый хороший. . .
Кредитный калькулятор
Maks 05.08.2026
Решение задачи по прикладной информатике средствами 1С. Задача: Напишите приложение-калькулятор, которое помогает рассчитывать параметры кредита для аннуитетного и дифференцированного видов. . .
У нас сейчас поговорку "Опять 25" нужно переделать на "Опять +35".
kumehtar 04.08.2026
С ностальгией вспоминаю времена моего детства, когда у нас и правда +25 - была максимальная температура летом. Раньше +25 °C реально казались вершиной жары, когда можно было весь день пропадать на. . .
Как ИИ начал спорить и врать (возможно почуяв опасность для себя от индустрии - уход от электроники).
Hrethgir 04.08.2026
Недельный диалог, на фоне событий с НПЗ. Да, из спирта можно получать бензин, и это не сложно. Но потом в схеме я решил избавиться от насоса, при этом полностью сделав контроль подачи спирта в. . .
Термопринтер QR701
Argus19 03.08.2026
Термопринтер QR701 Купил два термопринтера QR701. На сэлф-тесте написано: Language: PC936 (GB18030). Что означает, что принтеры могут печатать только латиницу и китайские иероглифы. Так же. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru