c# - pattern - getfiles filter vb net
coincidencia de patrón global en.NET (13)
¿Hay un mecanismo incorporado en .NET para unir patrones distintos de las expresiones regulares? Me gustaría hacer coincidir el uso de comodines de estilo UNIX (glob) (* = cualquier número de cualquier carácter).
Me gustaría usar esto para un control de usuario final. Me temo que permitir todas las capacidades de RegEx será muy confuso.
Desde C # puedes usar el método LikeOperator.LikeString de .NET. Esa es la implementación de respaldo para el operador LIKE de VB. Admite patrones que usan *,?, #, [Charlist] y [! Charlist].
Puede usar el método LikeString desde C # agregando una referencia al ensamblado Microsoft.VisualBasic.dll, que se incluye con cada versión de .NET Framework. A continuación, invoque el método LikeString como cualquier otro método .NET estático:
using Microsoft.VisualBasic;
using Microsoft.VisualBasic.CompilerServices;
...
bool isMatch = LikeOperator.LikeString("I love .NET!", "I love *", CompareMethod.Text);
// isMatch should be true.
No sé si .NET Framework tiene una coincidencia global, pero ¿no podría reemplazar el * con. *? y usar expresiones regulares?
Si usa VB.Net, puede usar la declaración Like, que tiene una sintaxis tipo Glob.
Encontré el código real para ti:
Regex.Escape( wildcardExpression ).Replace( @"/*", ".*" ).Replace( @"/?", "." );
Escribí una clase FileSelector que hace la selección de archivos basados en nombres de archivos. También selecciona archivos según el tiempo, el tamaño y los atributos. Si solo quiere que el nombre del archivo sea globbing, entonces exprese el nombre en formularios como "* .txt" y similares. Si desea los demás parámetros, especifique una declaración lógica booleana como "nombre = * .xls y ctime <2009-01-01", lo que implica un archivo .xls creado antes del 1 de enero de 2009. También puede seleccionar en función de lo negativo: "name! = * .xls" significa todos los archivos que no son xls.
Echale un vistazo. Fuente abierta. Licencia liberal. Gratis para usar en otro lugar.
Basado en publicaciones anteriores, armé una clase C #:
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);
}
}
Usarlo sería algo como esto:
FileWildcard w = new FileWildcard("*.txt");
if (w.IsMatch("Doug.Txt"))
Console.WriteLine("We have a match");
La coincidencia NO es la misma que el método System.IO.Directory.GetFiles (), por lo tanto, no los use juntos.
Si desea evitar expresiones regulares, esta es una implementación global básica:
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;
}
}
Úselo así:
Assert.IsTrue("text.txt".Glob("*.txt"));
Las variantes de 2 y 3 argumentos de los métodos de listado como GetFiles()
y EnumerateDirectories()
toman una cadena de búsqueda como su segundo argumento que admite el nombre de archivo globbing, con *
y ?
.
class GlobTestMain
{
static void Main(string[] args)
{
string[] exes = Directory.GetFiles(Environment.CurrentDirectory, "*.exe");
foreach (string file in exes)
{
Console.WriteLine(Path.GetFileName(file));
}
}
}
rendiría
GlobTest.exe
GlobTest.vshost.exe
Los documentos indican que hay algunas advertencias con extensiones coincidentes. También establece que los nombres de los archivos 8.3 coinciden (que pueden generarse automáticamente detrás de las escenas), lo que puede dar como resultado coincidencias "duplicadas" en ciertos patrones.
Los métodos que lo soportan son GetFiles()
, GetDirectories()
y GetFileSystemEntries()
. Las variantes Enumerate
también son compatibles con esto.
Me gusta mi código un poco más semántico, así que escribí este método de extensión:
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);
}
}
}
(cambie el espacio de nombres y / o copie el método de extensión a su propia clase de extensiones de cadenas)
Usando esta extensión, puedes escribir declaraciones como esta:
if (File.Name.Like("*.jpg"))
{
....
}
Solo azúcar para que tu código sea un poco más legible :-)
Solo por el bien de la compleción. Desde 2016 en dotnet core
hay un nuevo paquete nuget llamado Microsoft.Extensions.FileSystemGlobbing
que admite rutas avanzadas de globings. ( Paquete Nuget )
algunos ejemplos pueden ser, la búsqueda de estructuras y archivos de carpetas anidadas comodín, que es muy común en los escenarios de desarrollo web.
-
wwwroot/app/**/*.module.js
-
wwwroot/app/**/*.js
Esto funciona de manera similar a lo que .gitignore
archivos .gitignore
para determinar qué archivos excluir del control de origen.
Solo por curiosidad he echado un vistazo a Microsoft.Extensions.FileSystemGlobbing, y estaba arrastrando dependencias bastante grandes en bastantes bibliotecas. ¿He decidido por qué no puedo intentar escribir algo similar?
Bueno, fácil de decir que de hacer, rápidamente noté que no era una función tan trivial después de todo, por ejemplo, "* .txt" debería coincidir con los archivos solo en la corriente directamente, mientras que "** .txt" también debería recolectar sub carpetas.
Microsoft también prueba algunas secuencias de patrones de coincidencia extraña como "./*.txt" - No estoy seguro de quién realmente necesita "./" tipo de cadena, ya que se eliminan de todos modos durante el procesamiento. ( https://github.com/aspnet/FileSystem/blob/dev/test/Microsoft.Extensions.FileSystemGlobbing.Tests/PatternMatchingTests.cs )
De todos modos, he codificado mi propia función, y habrá dos copias de la misma, una en svn (podría corregirlo más adelante) y copiaré una muestra aquí también para fines de demostración. Recomiendo copiar pegar desde el enlace svn.
Enlace SVN:
https://sourceforge.net/p/syncproj/code/HEAD/tree/SolutionProjectBuilder.cs#l800 (Busque la función matchFiles si no saltó correctamente).
Y aquí también está la copia de la función local:
/// <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''s "[^//]*"
// 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
Si encuentras algún error, me graduaré para solucionarlo.
Escribí una solución que lo hace. ¡No depende de ninguna biblioteca y no es compatible con "!" o operadores "[]". Es compatible con los siguientes patrones de búsqueda:
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
Un Glob de GNU para .NET.
Puede deshacerse de la referencia del paquete después de la instalación y simplemente compilar el único archivo fuente Glob.cs.
Y como es una implementación de GNU Glob, su plataforma cruzada y lenguaje cruzado una vez que encuentre otra implementación similar ¡disfrute!