views:

325

answers:

5

I am trying to match a pattern with a case-statement where the pattern is stored inside a variable. Here is a minimal example:

PATTERN="foo|bar|baz|bla"

case "foo" in
    ${PATTERN})
        printf "matched\n"
        ;;
    *)
        printf "no match\n"
        ;;
esac

Unfortunately the "|" seems to be escaped (interestingly "*" or "?" are not). How do I get this to work, i.e. to match "foo"? I need to store the pattern in a variable because it is constructed dynamically. This needs to work on a POSIX compatible shell.

A: 

Your pattern is in fact a list of patterns, and the separator | must be given literally. Your only option seems to be eval. However, try to avoid that if you can.

Philipp
How would you use `eval`?
hawk64
I agree -- you can use eval but it is the path to madness.
chris
A: 

Some versions of expr (e.g. GNU) allow pattern matching with alternation.

PATTERN="foo\|bar\|baz"
VALUE="bar"
expr "$VALUE" : "$PATTERN" && echo match || echo no match

Otherwise, I'd use a tool like awk:

awk -v value="foo" -v pattern="$PATTERN" '
    BEGIN {
        if (value ~ pattern) {
            exit 0
        } else {
            exit 1
        }
    }'

or more tersely:

awk -v v="foo" -v p="$PATTERN" 'BEGIN {exit !(v~p)}'

You can use it like this:

PATTERN="foo|bar|baz"
VALUE=oops
matches() { awk -v v="$1" -v p="$2" 'BEGIN {exit !(v~p)}'; }
if matches "$VALUE" "$PATTERN"; then
    echo match
else
    echo no match
fi
glenn jackman
+1  A: 

This should work:

PATTERN="foo|bar|baz|bla"

shopt -s extglob

case "foo" in
    @($(echo $PATTERN)))
        printf "matched\n"
        ;;
    *)
        printf "no match\n"
        ;;
esac
dimba
You don't need the `$(echo)` just do `@($PATTERN)`
Dennis Williamson
Is that a posixism or is that a bashism? I looked in the ash man page and didn't see this structure, but I also didn't read every letter.
chris
+1  A: 

You can use regex matching in Bash:

PATTERN="foo|bar|baz|bla"

if [[ "foo" =~ $PATTERN ]]
then
    printf "matched\n"
elif . . .
    . . .
elif . . .
    . . .
else
    printf "no match\n"
fi
Dennis Williamson
A: 

"You can't get there from here"

I love using case for pattern matching but in this situation you're running past the edge of what bourne shell is good for.

there are two hacks to solve this problem:

at the expense of a fork, you could use egrep

pattern="this|that|those"

if
  echo "foo" | egrep "$pattern"  > /dev/null 2>&1
then
  echo "found"
else
  echo "not found"
fi

You can also do this with only built-ins using a loop. Depending on the situation, this may make your code run a billion times slower, so be sure you understand what's going on with the code.

pattern="this|that|those"

IFS="|" temp_pattern="$pattern"
echo=echo

for value in $temp_pattern
do
  case foo 
  in
    "$list") echo "matched" ; echo=: ; break ;;
  esac
done
$echo not matched

This is clearly a horror show, an example of how shell scripts can quickly spin out of control if you try to make do anything even a little bit off the map..

chris