Ответ 1
Я нашел для вас реальный код:
Regex.Escape( wildcardExpression ).Replace( @"\*", ".*" ).Replace( @"\?", "." );
Есть ли встроенный механизм в .NET для сопоставления шаблонов, отличных от регулярных выражений? Я хотел бы сопоставлять шаблоны UNIX style (glob) (* = любое количество символов).
Я хотел бы использовать это для контроля над конечным пользователем. Я боюсь, что разрешение всех возможностей RegEx будет очень запутанным.
Я нашел для вас реальный код:
Regex.Escape( wildcardExpression ).Replace( @"\*", ".*" ).Replace( @"\?", "." );
Мне нравится мой код немного более семантический, поэтому я написал этот метод расширения:
using System.Text.RegularExpressions;
namespace Whatever
{
public static class StringExtensions
{
/// <summary>
/// Compares the string against a given pattern.
/// </summary>
/// <param name="str">The string.</param>
/// <param name="pattern">The pattern to match, where "*" means any sequence of characters, and "?" means any single character.</param>
/// <returns><c>true</c> if the string matches the given pattern; otherwise <c>false</c>.</returns>
public static bool Like(this string str, string pattern)
{
return new Regex(
"^" + Regex.Escape(pattern).Replace(@"\*", ".*").Replace(@"\?", ".") + "$",
RegexOptions.IgnoreCase | RegexOptions.Singleline
).IsMatch(str);
}
}
}
(изменить пространство имен и/или скопировать метод расширения в собственный класс расширений строк)
Используя это расширение, вы можете писать такие инструкции:
if (File.Name.Like("*.jpg"))
{
....
}
Просто сахара, чтобы сделать код более понятным: -)
Просто ради полноты. С 2016 года в dotnet core
появился новый пакет nuget с именем Microsoft.Extensions.FileSystemGlobbing
, который поддерживает расширенные пути глобуса. (Пакет Nuget)
может быть несколько примеров: поиск шаблонов вложенных папок и файлов, которые очень распространены в сценариях веб-разработки.
wwwroot/app/**/*.module.js
wwwroot/app/**/*.js
Это несколько похоже на то, что используются .gitignore
файлы, чтобы определить, какие файлы следует исключать из исходного элемента управления.
Варианты выбора из 2-х и 3-х аргументов, такие как GetFiles()
и EnumerateDirectories()
, принимают в качестве второго аргумента строку поиска, поддерживающую флешивание имени файла, как с *
, так и ?
.
class GlobTestMain
{
static void Main(string[] args)
{
string[] exes = Directory.GetFiles(Environment.CurrentDirectory, "*.exe");
foreach (string file in exes)
{
Console.WriteLine(Path.GetFileName(file));
}
}
}
даст
GlobTest.exe
GlobTest.vshost.exe
Документы утверждают, что есть некоторые предостережения с соответствующими расширениями. В нем также указывается, что имена файлов в формате 8.3 совпадают (которые могут генерироваться автоматически за кулисами), что может привести к совпадению "дубликатов" при заданных шаблонах.
Методы, поддерживающие это, GetFiles()
, GetDirectories()
и GetFileSystemEntries()
. Варианты Enumerate
также поддерживают это.
Если вы используете VB.Net, вы можете использовать оператор Like, который имеет синтаксис типа Glob.
Я написал класс FileSelector, который делает выбор файлов на основе имен файлов. Он также выбирает файлы на основе времени, размера и атрибутов. Если вы просто хотите, чтобы имя файла было перевернуто, вы выражаете имя в виде "*.txt" и тому подобное. Если вам нужны другие параметры, то вы указываете логическую инструкцию типа "name = *.xls и ctime < 2009-01-01" - подразумевая файл .xls, созданный до 1 января 2009 года. Вы также можете выбрать на основе отрицательного: "name!= *.xls" означает все файлы, которые не являются xls.
Проверьте это. Открытый исходный код. Либеральная лицензия. Бесплатно использовать в других местах.
Если вы хотите избежать регулярных выражений, это базовая реализация glob:
public static class Globber
{
public static bool Glob(this string value, string pattern)
{
int pos = 0;
while (pattern.Length != pos)
{
switch (pattern[pos])
{
case '?':
break;
case '*':
for (int i = value.Length; i >= pos; i--)
{
if (Glob(value.Substring(i), pattern.Substring(pos + 1)))
{
return true;
}
}
return false;
default:
if (value.Length == pos || char.ToUpper(pattern[pos]) != char.ToUpper(value[pos]))
{
return false;
}
break;
}
pos++;
}
return value.Length == pos;
}
}
Используйте его следующим образом:
Assert.IsTrue("text.txt".Glob("*.txt"));
Я не знаю, имеет ли платформа .NET глобальное сопоставление, но вы не могли бы заменить * на. *? и использовать регулярные выражения?
Основываясь на предыдущих сообщениях, я собрал класс С#:
using System;
using System.Text.RegularExpressions;
public class FileWildcard
{
Regex mRegex;
public FileWildcard(string wildcard)
{
string pattern = string.Format("^{0}$", Regex.Escape(wildcard)
.Replace(@"\*", ".*").Replace(@"\?", "."));
mRegex = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Singleline);
}
public bool IsMatch(string filenameToCompare)
{
return mRegex.IsMatch(filenameToCompare);
}
}
Использование этого будет выглядеть примерно так:
FileWildcard w = new FileWildcard("*.txt");
if (w.IsMatch("Doug.Txt"))
Console.WriteLine("We have a match");
Соответствие не совпадает с методом System.IO.Directory.GetFiles(), поэтому не используйте их вместе.
В С# вы можете использовать метод .NET LikeOperator.LikeString. Это реализация поддержки для VB LIKE operator. Он поддерживает шаблоны, используя *,?, #, [Charlist] и [! Charlist].
Вы можете использовать метод LikeString из С#, добавив ссылку на сборку Microsoft.VisualBasic.dll, которая включена в каждую версию .NET Framework. Затем вы вызываете метод LikeString так же, как и любой другой статический метод .NET:
using Microsoft.VisualBasic;
using Microsoft.VisualBasic.CompilerServices;
...
bool isMatch = LikeOperator.LikeString("I love .NET!", "I love *", CompareMethod.Text);
// isMatch should be true.
Просто из любопытства я заглянул в Microsoft.Extensions.FileSystemGlobbing - и он затягивал довольно большие зависимости от довольно многих библиотек - я решил, почему я не могу попытаться написать что-то подобное?
Хорошо - легко сказать, чем сделать, я быстро заметил, что в конце концов это была не такая тривиальная функция - например, "*.txt" должен соответствовать файлам только в текущем напрямую, а "**. txt" должен также собирать подпапки.
Microsoft также тестирует некоторые нечетные последовательности совпадений, такие как "./*.txt". Я не уверен, кто на самом деле нуждается в "./" виде строки, поскольку они все равно удаляются при обработке. (https://github.com/aspnet/FileSystem/blob/dev/test/Microsoft.Extensions.FileSystemGlobbing.Tests/PatternMatchingTests.cs)
В любом случае, я закодировал свою собственную функцию - и будет две ее копии - одна в svn (я могу позже ее исправить), и я также скопирую один образец для демонстрационных целей. Я рекомендую скопировать пасту из ссылки svn.
Ссылка SVN:
https://sourceforge.net/p/syncproj/code/HEAD/tree/SolutionProjectBuilder.cs#l800 (Искать функцию matchFiles, если она не подпрыгивала правильно).
И здесь также копия локальной функции:
/// <summary>
/// Matches files from folder _dir using glob file pattern.
/// In glob file pattern matching * reflects to any file or folder name, ** refers to any path (including sub-folders).
/// ? refers to any character.
///
/// There exists also 3-rd party library for performing similar matching - 'Microsoft.Extensions.FileSystemGlobbing'
/// but it was dragging a lot of dependencies, I've decided to survive without it.
/// </summary>
/// <returns>List of files matches your selection</returns>
static public String[] matchFiles( String _dir, String filePattern )
{
if (filePattern.IndexOfAny(new char[] { '*', '?' }) == -1) // Speed up matching, if no asterisk / widlcard, then it can be simply file path.
{
String path = Path.Combine(_dir, filePattern);
if (File.Exists(path))
return new String[] { filePattern };
return new String[] { };
}
String dir = Path.GetFullPath(_dir); // Make it absolute, just so we can extract relative path'es later on.
String[] pattParts = filePattern.Replace("/", "\\").Split('\\');
List<String> scanDirs = new List<string>();
scanDirs.Add(dir);
//
// By default glob pattern matching specifies "*" to any file / folder name,
// which corresponds to any character except folder separator - in regex that "[^\\]*"
// glob matching also allow double astrisk "**" which also recurses into subfolders.
// We split here each part of match pattern and match it separately.
//
for (int iPatt = 0; iPatt < pattParts.Length; iPatt++)
{
bool bIsLast = iPatt == (pattParts.Length - 1);
bool bRecurse = false;
String regex1 = Regex.Escape(pattParts[iPatt]); // Escape special regex control characters ("*" => "\*", "." => "\.")
String pattern = Regex.Replace(regex1, @"\\\*(\\\*)?", delegate (Match m)
{
if (m.ToString().Length == 4) // "**" => "\*\*" (escaped) - we need to recurse into sub-folders.
{
bRecurse = true;
return ".*";
}
else
return @"[^\\]*";
}).Replace(@"\?", ".");
if (pattParts[iPatt] == "..") // Special kind of control, just to scan upper folder.
{
for (int i = 0; i < scanDirs.Count; i++)
scanDirs[i] = scanDirs[i] + "\\..";
continue;
}
Regex re = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
int nScanItems = scanDirs.Count;
for (int i = 0; i < nScanItems; i++)
{
String[] items;
if (!bIsLast)
items = Directory.GetDirectories(scanDirs[i], "*", (bRecurse) ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
else
items = Directory.GetFiles(scanDirs[i], "*", (bRecurse) ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
foreach (String path in items)
{
String matchSubPath = path.Substring(scanDirs[i].Length + 1);
if (re.Match(matchSubPath).Success)
scanDirs.Add(path);
}
}
scanDirs.RemoveRange(0, nScanItems); // Remove items what we have just scanned.
} //for
// Make relative and return.
return scanDirs.Select( x => x.Substring(dir.Length + 1) ).ToArray();
} //matchFiles
Если вы найдете какие-либо ошибки, я буду градиентом, чтобы их исправить.
Я написал решение, которое это делает. Это не зависит от какой-либо библиотеки, и она не поддерживает "!". или "[]". Он поддерживает следующие шаблоны поиска:
C:\Logs\*. txt
C:\Logs\**\* P1?\**\asd *.pdf
/// <summary>
/// Finds files for the given glob path. It supports ** * and ? operators. It does not support !, [] or ![] operators
/// </summary>
/// <param name="path">the path</param>
/// <returns>The files that match de glob</returns>
private ICollection<FileInfo> FindFiles(string path)
{
List<FileInfo> result = new List<FileInfo>();
//The name of the file can be any but the following chars '<','>',':','/','\','|','?','*','"'
const string folderNameCharRegExp = @"[^\<\>:/\\\|\?\*" + "\"]";
const string folderNameRegExp = folderNameCharRegExp + "+";
//We obtain the file pattern
string filePattern = Path.GetFileName(path);
List<string> pathTokens = new List<string>(Path.GetDirectoryName(path).Split('\\', '/'));
//We obtain the root path from where the rest of files will obtained
string rootPath = null;
bool containsWildcardsInDirectories = false;
for (int i = 0; i < pathTokens.Count; i++)
{
if (!pathTokens[i].Contains("*")
&& !pathTokens[i].Contains("?"))
{
if (rootPath != null)
rootPath += "\\" + pathTokens[i];
else
rootPath = pathTokens[i];
pathTokens.RemoveAt(0);
i--;
}
else
{
containsWildcardsInDirectories = true;
break;
}
}
if (Directory.Exists(rootPath))
{
//We build the regular expression that the folders should match
string regularExpression = rootPath.Replace("\\", "\\\\").Replace(":", "\\:").Replace(" ", "\\s");
foreach (string pathToken in pathTokens)
{
if (pathToken == "**")
{
regularExpression += string.Format(CultureInfo.InvariantCulture, @"(\\{0})*", folderNameRegExp);
}
else
{
regularExpression += @"\\" + pathToken.Replace("*", folderNameCharRegExp + "*").Replace(" ", "\\s").Replace("?", folderNameCharRegExp);
}
}
Regex globRegEx = new Regex(regularExpression, RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
string[] directories = Directory.GetDirectories(rootPath, "*", containsWildcardsInDirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
foreach (string directory in directories)
{
if (globRegEx.Matches(directory).Count > 0)
{
DirectoryInfo directoryInfo = new DirectoryInfo(directory);
result.AddRange(directoryInfo.GetFiles(filePattern));
}
}
}
return result;
}
https://www.nuget.org/packages/Glob.cs
https://github.com/mganss/Glob.cs
GNU Glob для .NET.
Вы можете избавиться от ссылки на пакет после установки и просто скомпилировать единственный исходный файл Glob.cs.
И поскольку это реализация GNU Glob, это перекрестная платформа и перекрестный язык, как только вы найдете другую подобную реализацию!