|
Преамбула
Когда-то уже говорил, что стандартная функция 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
|