How can I programmatically tell if a filename matches a shell glob pattern?
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.
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
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.