web-dev-qa-db-ja.com

.NETでのグロブパターンマッチング

正規表現以外のパターンに一致する.NETの組み込みメカニズムはありますか? UNIXスタイル(グロブ)のワイルドカード(* =任意の数の任意の文字)を使用して一致させたい。

これをエンドユーザー向けのコントロールに使用したいと思います。すべての正規表現機能を許可すると、非常に混乱するのではないかと心配しています。

46
dmo

私はあなたのための実際のコードを見つけました:

Regex.Escape( wildcardExpression ).Replace( @"\*", ".*" ).Replace( @"\?", "." );
35

私は自分のコードがもう少しセマンティックであることが好きなので、次の拡張メソッドを作成しました。

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"))
{
   ....
}

あなたのコードをもう少し読みやすくするために砂糖だけ:-)

63
mindplay.dk

完全を期すために。 2016年以降、dotnet coreには、高度なグロビンパスをサポートするMicrosoft.Extensions.FileSystemGlobbingという新しいnugetパッケージがあります。 ( Nugetパッケージ

いくつかの例としては、Web開発シナリオで非常に一般的なワイルドカードのネストされたフォルダー構造とファイルの検索があります。

  • wwwroot/app/**/*.module.js
  • wwwroot/app/**/*.js

これは、.gitignoreファイルがソース管理から除外するファイルを決定するために使用するものと多少似ています。

24
cleftheris

GetFiles()EnumerateDirectories()などのリストメソッドの2引数および3引数のバリアントは、*?の両方を使用して、検索文字列を2番目の引数としてファイル名のグロビングをサポートします。

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バリアントもこれをサポートしています。

10
Dan Mangiarelli

VB.Netを使用する場合は、Globのような構文を持つLikeステートメントを使用できます。

http://www.getdotnetcode.com/gdncstore/free/Articles/Intoduction%20to%20the%20 VB%20NET%20Like%20 Operator.htm

5
torial

ファイル名に基づいてファイルを選択する FileSelector クラスを作成しました。また、時間、サイズ、および属性に基づいてファイルを選択します。ファイル名のグロブだけが必要な場合は、「*。txt」などの形式で名前を表現します。他のパラメーターが必要な場合は、「name = * .xls and ctime <2009-01-01」のようなブール論理ステートメントを指定します。これは、2009年1月1日より前に作成された.xlsファイルを意味します。ネガティブに基づいて選択することもできます。 「name!= * .xls」は、xls以外のすべてのファイルを意味します。

見てみな。オープンソース。リベラルライセンス。他の場所で無料で使用できます。

4
Cheeso

テストとベンチマークを使用して、.NETStandard用のグロブライブラリを作成しました。私の目標は、最小限の依存関係で、正規表現を使用せず、正規表現よりも優れた.NET用のライブラリを作成することでした。

あなたはここでそれを見つけることができます:

3
Darrell

正規表現を避けたい場合、これは基本的なグロブ実装です:

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"));
3
Tony Edgecombe

https://www.nuget.org/packages/Glob.cs

https://github.com/mganss/Glob.cs

A GNU Glob for.NET。

インストール後にパッケージ参照を取り除き、単一のGlob.csソースファイルをコンパイルするだけです。

そして、それはGNU Globの実装なので、別の同様の実装を楽しんだら、クロスプラットフォームとクロス言語になります!

2
Matthew Sheeran

以前の投稿に基づいて、私は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);
    }
}

それを使用すると、次のようになります。

FileWildcard w = new FileWildcard("*.txt");
if (w.IsMatch("Doug.Txt"))
   Console.WriteLine("We have a match");

マッチングはSystem.IO.Directory.GetFiles()メソッドと同じではないため、一緒に使用しないでください。

1
Doug Clutter

.NET Frameworkにglobマッチングがあるかどうかはわかりませんが、*を。*に置き換えることはできませんか?正規表現を使用しますか?

1
Ferruccio

私はそれを行うソリューションを書きました。どのライブラリにも依存せず、「!」をサポートしていません。または「[]」演算子。次の検索パターンをサポートしています。

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;
    }
0
Jon

C#から、.NETの LikeOperator.LikeString メソッドを使用できます。これがVBの LIKE演算子 のバッキング実装です。 *、?、#、[charlist]、および[!charlist]を使用したパターンをサポートします。

.NET Frameworkのすべてのバージョンに含まれているMicrosoft.VisualBasic.dllアセンブリへの参照を追加することにより、C#からLikeStringメソッドを使用できます。次に、他の静的.NETメソッドと同じようにLikeStringメソッドを呼び出します。

using Microsoft.VisualBasic;
using Microsoft.VisualBasic.CompilerServices;
...
bool isMatch = LikeOperator.LikeString("I love .NET!", "I love *", CompareMethod.Text);
// isMatch should be true.
0
Bill Menees

好奇心から、Microsoft.Extensions.FileSystemGlobbingをちらっと見ました-そしてそれは非常に多くのライブラリへの非常に大きな依存関係を引きずっていました-なぜ私は似たようなものを書こうとできないのかを決めましたか?

まあ-言うのは簡単ですが、結局のところそれほど些細な機能ではないことにすぐに気付きました-たとえば、「*。txt」は現在のファイルにのみ一致する必要がありますが、「**。txt」はサブも収集する必要がありますフォルダー。

Microsoftは、「。/*。txt」のような奇妙な一致パターンシーケンスもテストします。これらは処理中に削除されるため、実際に「./」の種類の文字列が必要なのは誰かわかりません。 ( https://github.com/aspnet/FileSystem/blob/dev/test/Microsoft.Extensions.FileSystemGlobbing.Tests/PatternMatchingTests.cs

とにかく、私は自分の関数をコーディングしました-そしてそれの2つのコピーがあります-1つはsvnにあります(後でバグ修正するかもしれません)-そしてデモの目的でここにも1つのサンプルをコピーします。 svnリンクからコピーして貼り付けることをお勧めします。

SVNリンク:

https://sourceforge.net/p/syncproj/code/HEAD/tree/SolutionProjectBuilder.cs#l8 (正しくジャンプしない場合は、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'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

バグを見つけたら、私はそれらを修正するために卒業します。

0
TarmoPikaro