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.

Tags:

Bash

Wildcards