fnmatch

Sometimes you may wish to know if one or more files simply exist by a given pathname.

This is usually trivial, for example:

if [ -e /some/dir/foo ]; then
    echo "Found foo in /some/dir"
fi

Returns success if a file, directory, or other filesystem component exists at /some/dir/foo.

However, if you want to test for existence by pattern, it becomes a little more difficult. Since pathname expansion only occurs with one or more matches (leaving the original pattern unexpanded for zero matches) you have to retest existence after expansion. This is commonly done by placing the pattern in the argument space of a for-loop and testing at the onset that the first path given exists.

found=
for foo in /some/dir/*foo*; do
    #
    # foo is /some/dir/*foo* if no matches found
    # foo is expanded to matches if found
    #
    if [ ! -e "$foo" ]; then
        #
        # The pattern was not expanded, skip remaining lines
        # Since only one pattern was given above,
        # this continue is analogous to a break before found=1
        #
        continue
    fi
    # If we reach here, at least one match was expanded
    found=1
    break
done
if [ "$found" ]; then
    echo "Found *foo* in /some/dir"
fi

Another method involves passing the pattern as an argument to the set built-in to generate expansions, however this is not recommended because you can overflow the Operating System's maximum number of arguments leading to an error of "Too many arguments" upon pattern expansion.

set -- /some/dir/*foo*
    # WARNING: Potential "Too many arguments" if expansion is many
if [ -e "$1" ]; then
    echo "Found *foo* in /some/dir"
fi

A solution exists that is both simple and elegant. An fnmatch function:

1 #!/bin/sh
2 fnmatch()
3 {
4     [ -e "$1" ]
5 }

The pattern is expanded before passing arguments to fnmatch which means it need only test if the first argument exists. If the first argument exists, an expansion was performed, otherwise the first argument remains a path to a non-existent, un-expandable pattern.

We can now use fnmatch as a method of testing whether a pathname containing patterns can successfully be expanded to one or more existing filesystem element(s):

if fnmatch /some/dir/*foo*; then
    echo "Found *foo* in /some/dir"
fi

This method can be used to improve the efficiency of loops when you expect many items to be expanded. For example, instead of testing for existence inside a loop for cases where the pattern is unexpanded, use fnmatch as a condition for looping.

fnmatch /some/dir/*foo* && for foo in /some/dir/*foo*; do
    echo "foo=[$foo]"
done

Last updated