views:

104

answers:

3

Is there any bash command to do something similar to:

if [[ $string =~ $pattern ]]

but that it works with simple wild cards (?,*) and not complex regular expressions ??


More info:

I have a config file (a sort of .ini-like file) where each line is composed of a wild card pattern and some other data.
For any given input string that my script receives, I have to find the first line in the config file where the wild card pattern matches the input string and then return the rest of the data in that line.
It's simple. I just need a way to match a string against wild card patterns and not RegExps since the patterns may contain dots, brackets, dashes, etc. and I don't want those to be interpreted as special characters.

+2  A: 

There's several ways of doing this.

In bash >= 3, you have regex matching like you describe, e.g.

$ foo=foobar
$ if [[ $foo =~ f.ob.r ]]; then echo "ok"; fi
   ok

Note that this syntax uses regex patterns, so it uses . instead of ? to match a single character.

If what you want to do is just test that the string contains a substring, there's more classic ways of doing that, e.g.

# ${foo/b?r/} replaces "b?r" with the empty string in $foo
# So we're testing if $foo does not contain "b?r" one time
$ if [[ ${foo/b?r/} = $foo ]]; then echo "ok"; fi

You can also test if a string begins or ends with an expression this way:

# ${foo%b?r} removes "bar" in the end of $foo
# So we're testing if $foo does not end with "b?r"
$ if [[ ${foo%b?r} = $foo ]]; then echo "ok"; fi

# ${foo#b?r} removes "b?r" in the beginning of $foo
# So we're testing if $foo does not begin with "b?r"
$ if [[ ${foo#b?r} = $foo ]]; then echo "ok"; fi
     ok

See the Parameter Expansion paragraph of man bash for more info on these syntaxes. Using ## or %% instead of # and % respectively will achieve a longest matching instead of a simple matching.

Another very classic way of dealing with wildcards is to use case:

case $foo in 
   *bar)
       echo "Foo matches *bar"
       ;;
   bar?)
       echo "Foo matches bar?"
       ;;
   *)
       echo "Foo didn't match any known rule"
       ;;
esac
Raphink
I think if[-z ${string/$pattern}] will do the trick. Thanks.
GetFree
Ah, that's yet another option indeed :-)
Raphink
A: 

John T's answer was deleted, but I actually think he was on the right track. Here it is:

Another portable method which will work in most versions of bash is to echo your string then pipe to grep. If no match is found, it will evaluate to false as the result will be blank. If something is returned, it will evaluate to true.

[john@awesome]$string="Hello World"
[john@awesome]$if [[ `echo $string | grep Hello` ]];then echo "match";fi
match

What John didn't consider is the wildcard requested by the answer. For that, use egrep, a.k.a. grep -E, and use the regex wildcard .*. Here, . is the wildcard, and * is a multiplier meaning "any number of these". So, John's example becomes:

$ string="Hello World"
$ if [[ `echo $string | egrep "Hel.*"` ]]; then echo "match"; fi

The . wildcard notation is fairly standard regex, so it should work with any command that speaks regex's.

It does get nasty if you need to escape the special characters, so this may be sub-optimal:

$ if [[ `echo $string | egrep "\.\-\$.*"` ]]; then echo "match"; fi
quack quixote
Yeah, wasn't gonna bother. But thanks for resurrecting my answer I guess! :)
John T
you deleted it as i was crafting this suggestion as a comment.... it does break down into the suggestion to "use regex wildcards", but oh well. not a great answer, just an answer.
quack quixote
But that wont work for the '?' wild card, right?
GetFree
sure it will. `"?"` wildcard means "match one character, doesn't matter what". that's exactly what `"."` means in a regex. it's not the same character for the wildcard, but it has the same meaning.
quack quixote
GetFree: the '?' wildcard would be replaced by '.' just as when using '=~'.
Raphink
so in regex, `"."` is the same as `"?"` wildcard; `".+"` means "match one or more any-characters"; `".*"` means "match zero or more any-characters". `foo.` matches "foob" but not "foobar"; `foo.+` matches "foob" and "foobar" but not "foo"; and `foo.*` matches all 3.
quack quixote
+1  A: 

The [ -z ${string/$pattern} ] trick has some pretty serious problems: if string is blank, it'll match all possible patterns; if it contains spaces, the test command will parse it as part of an expression (try string="x -o 1 -eq 1" for amusement). bash's [[ expressions do glob-style wildcard matching natively with the == operator, so there's no need for all these elaborate (and trouble-prone) tricks. Just use:

if [[ $string == $pattern ]]
Gordon Davisson
Sure it's with two equal signs?
GetFree
Yes, for `[[ $foo == $bar ]]`, `bash` matches the contents of `foo` against the pattern in `bar`. Note, that this is note the same as `[[ $foo == "$bar" ]]`, which will not treat the contents of `bar` as a pattern.
Chris Johnsen
@GetFree: Two equal signs seems to be preferred, but you can use either `=` or `==` interchangeably in this particular case.
Gordon Davisson
@Chris Johnsen: It's actually even cooler than that, as you can mix quoted (literal) and unquoted (pattern-matched) strings, for example `[[ $string == "$foo"$bar ]]` will require that the `$foo` portion match exactly, while the `$bar` part must match as a pattern.
Gordon Davisson