Need to perform Wildcard (*,?, etc) search on a string using Regex
Solution 1
From http://www.codeproject.com/KB/recipes/wildcardtoregex.aspx:
public static string WildcardToRegex(string pattern)
{
return "^" + Regex.Escape(pattern)
.Replace(@"\*", ".*")
.Replace(@"\?", ".")
+ "$";
}
So something like foo*.xls?
will get transformed to ^foo.*\.xls.$
.
Solution 2
You can do a simple wildcard mach without RegEx using a Visual Basic function called LikeString.
using Microsoft.VisualBasic;
using Microsoft.VisualBasic.CompilerServices;
if (Operators.LikeString("This is just a test", "*just*", CompareMethod.Text))
{
Console.WriteLine("This matched!");
}
If you use CompareMethod.Text
it will compare case-insensitive. For case-sensitive comparison, you can use CompareMethod.Binary
.
More info here: http://www.henrikbrinch.dk/Blog/2012/02/14/Wildcard-matching-in-C
Solution 3
The correct regular expression formulation of the glob expression d*
is ^d
, which means match anything that starts with d
.
string input = "Message";
string pattern = @"^d";
Regex regex = new Regex(pattern, RegexOptions.IgnoreCase);
(The @
quoting is not necessary in this case, but good practice since many regexes use backslash escapes that need to be left alone, and it also indicates to the reader that this string is special).
Solution 4
Windows and *nux treat wildcards differently. *
, ?
and .
are processed in a very complex way by Windows, one's presence or position would change another's meaning. While *nux keeps it simple, all it does is just one simple pattern match. Besides that, Windows matches ?
for 0 or 1 chars, Linux matches it for exactly 1 chars.
I didn't find authoritative documents on this matter, here is just my conclusion based on days of tests on Windows 8/XP (command line, dir
command to be specific, and the Directory.GetFiles
method uses the same rules too) and Ubuntu Server 12.04.1 (ls
command). I made tens of common and uncommon cases work, although there'are many failed cases too.
The current answer by Gabe, works like *nux. If you also want a Windows style one, and are willing to accept the imperfection, then here it is:
/// <summary>
/// <para>Tests if a file name matches the given wildcard pattern, uses the same rule as shell commands.</para>
/// </summary>
/// <param name="fileName">The file name to test, without folder.</param>
/// <param name="pattern">A wildcard pattern which can use char * to match any amount of characters; or char ? to match one character.</param>
/// <param name="unixStyle">If true, use the *nix style wildcard rules; otherwise use windows style rules.</param>
/// <returns>true if the file name matches the pattern, false otherwise.</returns>
public static bool MatchesWildcard(this string fileName, string pattern, bool unixStyle)
{
if (fileName == null)
throw new ArgumentNullException("fileName");
if (pattern == null)
throw new ArgumentNullException("pattern");
if (unixStyle)
return WildcardMatchesUnixStyle(pattern, fileName);
return WildcardMatchesWindowsStyle(fileName, pattern);
}
private static bool WildcardMatchesWindowsStyle(string fileName, string pattern)
{
var dotdot = pattern.IndexOf("..", StringComparison.Ordinal);
if (dotdot >= 0)
{
for (var i = dotdot; i < pattern.Length; i++)
if (pattern[i] != '.')
return false;
}
var normalized = Regex.Replace(pattern, @"\.+$", "");
var endsWithDot = normalized.Length != pattern.Length;
var endWeight = 0;
if (endsWithDot)
{
var lastNonWildcard = normalized.Length - 1;
for (; lastNonWildcard >= 0; lastNonWildcard--)
{
var c = normalized[lastNonWildcard];
if (c == '*')
endWeight += short.MaxValue;
else if (c == '?')
endWeight += 1;
else
break;
}
if (endWeight > 0)
normalized = normalized.Substring(0, lastNonWildcard + 1);
}
var endsWithWildcardDot = endWeight > 0;
var endsWithDotWildcardDot = endsWithWildcardDot && normalized.EndsWith(".");
if (endsWithDotWildcardDot)
normalized = normalized.Substring(0, normalized.Length - 1);
normalized = Regex.Replace(normalized, @"(?!^)(\.\*)+$", @".*");
var escaped = Regex.Escape(normalized);
string head, tail;
if (endsWithDotWildcardDot)
{
head = "^" + escaped;
tail = @"(\.[^.]{0," + endWeight + "})?$";
}
else if (endsWithWildcardDot)
{
head = "^" + escaped;
tail = "[^.]{0," + endWeight + "}$";
}
else
{
head = "^" + escaped;
tail = "$";
}
if (head.EndsWith(@"\.\*") && head.Length > 5)
{
head = head.Substring(0, head.Length - 4);
tail = @"(\..*)?" + tail;
}
var regex = head.Replace(@"\*", ".*").Replace(@"\?", "[^.]?") + tail;
return Regex.IsMatch(fileName, regex, RegexOptions.IgnoreCase);
}
private static bool WildcardMatchesUnixStyle(string pattern, string text)
{
var regex = "^" + Regex.Escape(pattern)
.Replace("\\*", ".*")
.Replace("\\?", ".")
+ "$";
return Regex.IsMatch(text, regex);
}
There's a funny thing, even the Windows API PathMatchSpec does not agree with FindFirstFile. Just try a1*.
, FindFirstFile
says it matches a1
, PathMatchSpec
says not.
Solution 5
d*
means that it should match zero or more "d
" characters. So any string is a valid match. Try d+
instead!
In order to have support for wildcard patterns I would replace the wildcards with the RegEx equivalents. Like *
becomes .*
and ?
becomes .?
. Then your expression above becomes d.*
Admin
Updated on January 22, 2021Comments
-
Admin over 3 years
I need to perform Wildcard (
*
,?
, etc.) search on a string. This is what I have done:string input = "Message"; string pattern = "d*"; Regex regex = new Regex(pattern, RegexOptions.IgnoreCase); if (regex.IsMatch(input)) { MessageBox.Show("Found"); } else { MessageBox.Show("Not Found"); }
With the above code "Found" block is hitting but actually it should not!
If my pattern is "e*" then only "Found" should hit.
My understanding or requirement is d* search should find the text containing "d" followed by any characters.
Should I change my pattern as "d.*" and "e.*"? Is there any support in .NET for Wild Card which internally does it while using Regex class?
-
Mark Lakata almost 11 yearsWhy was this downvoted? This is the correct solution to the OP's example.
^d.*
does the same thing as^d
but without superfluous characters. -
deerchao almost 11 yearsFor those who really want to have the exact behavior of Windows, I can only give you a hint:
FsRtlIsNameInExpression
andFindFirstFile
. -
Marcel Popescu almost 11 years+1 for the research, especially the discovery that the two APIs disagree
-
Gabe almost 11 yearsMy answer is complete. The
Regex.Escape
does exactly what you suggested. -
Camarada over 10 yearsSorry, Dude. I've made mistake
-
Disillusioned over 10 years+1 for being the only person to explain why
d*
gave OP unexpected results. But two concerns: (1) Isn't the regexd.*
equivalent to simplyd
? (2) Wildcardd*
implies the input should start withd
, whereas regexd.*
permitsd
anywhere in the input. -
scrat.squirrel about 10 yearsI did not downvote, but I can see your mistake: ^ char in regex means match at beginning of line... which is not equivalent to wildcard pattern such as "d*". So this is misleading.
-
Mark Lakata about 10 years@woohoo - The glob pattern
d*
will only match file names that begin withd
. The regular expression/^d/
will only match strings that begin withd
. They are the same. The^
pattern in regexs can mean start of string or start of line (i.e. after\n
) depending on the regex options but that's not applicable here, since the inputs are just strings. -
Wim Coenen almost 10 yearsI think there will be a problem here if escaping is allowed in the wildcard pattern, e.g. if you want to match the
*
character, then the wildcard pattern for that would be\*
. This would then be transformed into the regex\\.*
which does something different. -
Stéphane Gourichon over 9 yearsThe question was not really explicit whether it was about just matching a string against a pattern (probably) or against current directory. And indeed the latter case is awful because Windows silently matches against the pattern not only the visible file names but also the hidden DOS 8.3 file names, which sometime makes unexpected matches!
-
user425678 over 9 yearsYou need to add a reference to "Microsoft.VisualBasic"
-
dbkk about 9 yearsA major problem is that this does not escape other regex special characters, so something like
a+.b*
(a valid filename) would match improperly. -
dprothero over 8 years@dbkk Tthe code is calling Regex.Escape first, so it will match your example fine.
-
MrZweistein almost 3 yearsI think the translation of the wildcard "?" should be different as for example
*.txt?
would produce no matches on any file with.txt
extensions. I suggest the following code instead that would catch those cases:string regex = Regex.Escape(wildcard).Replace(@"\*", ".*").Replace(@"\?", ".?"); return "^" + regex + "$";