How can I programmatically tell if a filename matches one of a set of brace-expanded glob patterns?
Solution 1
There is no general solution for this problem. The reason is that, in bash, brace expansion (i.e., {pattern1,pattern2,...}
and filename expansion (a.k.a. glob patterns) are considered separate things and expanded under different conditions and at different times. Here is the full list of expansions that bash performs:
- brace expansion
- tilde expansion
- parameter and variable expansion
- command substitution
- arithmetic expansion
- word splitting
- pathname expansion
Since we only care about a subset of these (perhaps brace, tilde, and pathname expansion), it's possible to use certain patterns and mechanisms to restrict expansion in a controllable fashion. For instance:
#!/bin/bash
set -f
string=/foo/bar
for pattern in /foo/{*,foo*,bar*,**,**/*}; do
[[ $string == $pattern ]] && echo "$pattern matches $string"
done
Running this script generates the following output:
/foo/* matches /foo/bar
/foo/bar* matches /foo/bar
/foo/** matches /foo/bar
This works because set -f
disables pathname expansion, so only brace expansion and tilde expansion occur in the statement for pattern in /foo/{*,foo*,bar*,**,**/*}
. We can then use the test operation [[ $string == $pattern ]]
to test against pathname expansion after the brace expansion has already been performed.
Solution 2
I don't believe that {bar,baz}
is a shell glob pattern (though certainly /foo/ba[rz]
is) but if you want to know if $string
matches $pattern
you can do:
case "$string" in
($pattern) put your successful execution statement here;;
(*) this is where your failure case should be ;;
esac
You can do as many as you like:
case "$string" in
($pattern1) do something;;
($pattern2) do differently;;
(*) still no match;;
esac
Solution 3
As Patrick pointed out you need a "different type" of pattern:
[[ /foo/bar == /foo/@(bar|baz) ]]
string="/foo/bar"
pattern="/foo/@(bar|baz)"
[[ $string == $pattern ]]
Quotes are not necessary there.
Solution 4
I would use grep thusly:
#!/bin/bash
string="/foo/bar"
pattern1="/foo/*"
pattern2="/foo/{bar,baz}"
if echo $string | grep -e "$pattern1" > /dev/null; then
echo $string matches $pattern1
fi
if echo $string | grep -e "$pattern2" > /dev/null; then
echo $string matches $pattern2
fi
Which gives the output:
./test2.bsh
/foo/bar matches /foo/*
Related videos on Youtube
jayhendren
Updated on September 18, 2022Comments
-
jayhendren almost 2 years
I would like to tell if a string
$string
would be matched by a glob pattern$pattern
.$string
may or may not be the name of an existing file. How can I do this?Assume the following formats for my input strings:
string="/foo/bar" pattern1="/foo/*" pattern2="/foo/{bar,baz}"
I would like to find a bash idiom that determines if
$string
would be matched by$pattern1
,$pattern2
, or any other arbitrary glob pattern. Here is what I have tried so far:[[ "$string" = $pattern ]]
This almost works, except that
$pattern
is interpreted as a string pattern and not as a glob pattern.[ "$string" = $pattern ]
The problem with this approach is that
$pattern
is expanded and then string comparison is performed between$string
and the expansion of$pattern
.[[ "$(find $pattern -print0 -maxdepth 0 2>/dev/null)" =~ "$string" ]]
This one works, but only if
$string
contains a file that exists.[[ $string =~ $pattern ]]
This does not work because the
=~
operator causes$pattern
to be interpreted as an extended regular expression, not a glob or wildcard pattern.
-
phemmer over 9 yearsThe issue you're going to run into is that
{bar,baz}
isn't a pattern. It's parameter expansion. Subtle but critical difference in that{bar,baz}
is expanded very early on into multiple arguments,bar
andbaz
. -
jayhendren over 9 yearsIf the shell can expand parameters, then surely it can tell if a string is a potential expansion of a glob.
-
Hackaholic over 9 yearsgive this a try a=
ls /foo/*
now you can match in a -
jayhendren over 9 years@Patrick: after reading through the bash man page, I have learned that
foo/{bar,baz}
is actually a brace expansion (not a parameter expansion) whilefoo/*
is pathname expansion.$string
is parameter expansion. These are all done at different times and by different mechanisms. -
Mike S almost 8 years@jayhendren @Patrick is right, and then you learned that your question ultimately is not what the title leads one to believe. Rather, you want to match a string against various kinds of patterns. If you wanted to strictly match versus a glob pattern, the
case
statement performs Pathname Expansion ("globbing") as per the Bash manual. -
Wtower over 7 yearsAlso there is
compgen -G "<glob-pattern>"
for bash.
-
jayhendren over 9 yearsOk, this works, but strictly speaking, it doesn't answer my question. For instance, I would like to consider patterns that are coming from another source, i.e., the patterns are out of my control.
-
Hauke Laging over 9 years@jayhendren Then you probably have to first convert the incoming pattern to those bash accepts.
-
jayhendren over 9 yearsSo all you've really done is transformed my question from "how do I tell if a filename is a potential expansion of an expression" to "how do I convert normal bash-style filename patterns to bash-style extended glob patterns."
-
Hauke Laging over 9 years@jayhendren Considering that what you want seems impossible "all you've really done" sounds a bit strange to me but maybe that's just a foreign language thing. If you want to ask this new question then you must explain what the input patterns look like. Maybe it's a simple
sed
operation. -
jayhendren over 9 yearsWhat I mean is this: your proposed solution works, but it introduces a new problem: how to transpose patterns from one form of expression to another. I have figured out the answer to my question and posted it here as an answer.
-
jayhendren over 9 yearsHi @mikeserv, as indicated in the comments and the answer that I provided above, I have already learned that what you say is true -
{bar,baz}
is not a glob pattern. I have already come up with a solution to my question that takes this into account. -
jayhendren over 9 yearsThe question states that the bash shell will be used.
-
Mike S almost 8 years@HaukeLaging is correct. People coming here to figure out how to match against glob patterns (aka "Pathname Expansion") are liable to get confused, as I did, because the title says "shell glob pattern" but the contents of his question use a non-glob pattern. At some point, jayhendren learned about the difference but his initial confusion is what caused Hauke to answer the way he did.
-
Mike S almost 8 years+1 This answers the question exactly as given in the title, and the first sentence. although the question later conflates other patterns with shell glob patterns.
-
Admin about 2 years
grep
matches regular expressions, not shell glob wildcard patterns (and as other have said,{foo,bar}
is not a glob operator, not is it a regexp operator). The ast-open implementation ofgrep
does support a-K
option to match against ksh wildcards though. -
Admin about 2 yearsTry
match "*" hello.txt
, orf='foo bar.txt'; match "$f" "$f"
. Orpat='$(uname -a >&2)'; match "$pat" hello.txt
. The first two break because of word splitting and globbing in thatexp=($(...))
assignment (Why does my shell script choke on whitespace or other special characters?).