13

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:

  1. [[ "$string" = $pattern ]]

    This almost works, except that $pattern is interpreted as a string pattern and not as a glob pattern.

  2. [ "$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.

  3. [[ "$(find $pattern -print0 -maxdepth 0 2>/dev/null)" =~ "$string" ]]

    This one works, but only if $string contains a file that exists.

  4. [[ $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.

  • 2
    The 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 and baz. – Patrick Nov 4 '14 at 20:25
  • If the shell can expand parameters, then surely it can tell if a string is a potential expansion of a glob. – jayhendren Nov 4 '14 at 20:26
  • give this a try a=ls /foo/* now you can match in a – Hackaholic Nov 4 '14 at 20:32
  • 1
    @Patrick: after reading through the bash man page, I have learned that foo/{bar,baz} is actually a brace expansion (not a parameter expansion) while foo/* is pathname expansion. $string is parameter expansion. These are all done at different times and by different mechanisms. – jayhendren Nov 4 '14 at 20:45
  • @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. – Mike S Aug 16 '16 at 20:17
8

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.

7

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
  • 1
    Hi @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 Nov 5 '14 at 0:09
  • 3
    +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. – Mike S Aug 16 '16 at 20:29
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.

  • Ok, 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. – jayhendren Nov 4 '14 at 20:54
  • @jayhendren Then you probably have to first convert the incoming pattern to those bash accepts. – Hauke Laging Nov 4 '14 at 21:02
  • So 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." – jayhendren Nov 4 '14 at 21:06
  • @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. – Hauke Laging Nov 4 '14 at 21:32
  • 1
    @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 Aug 16 '16 at 20:22
1

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/*
-2

in zsh:

[[ $string = $~pattern ]] && print true
  • 1
    The question states that the bash shell will be used. – jayhendren Nov 5 '14 at 20:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.