user189312
user189312

Reputation:

shell matching a pattern using a case-statement which is stored inside a variable

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.

Upvotes: 11

Views: 11606

Answers (7)

bufh
bufh

Reputation: 3410

It is possible to match a sub-string in a string without spawning a sub-process (such as grep) using the only POSIX compliant methods of sh(1p), as defined in Section 2.6.2, Parameter Expansion.

Here is a convenience function:

# Note:
# Unlike a regular expression, the separator *must* enclose the pattern;
# and it could be a multi chars.

isin() {
    PATTERN=${2:?a pattern is required}
    SEP=${3:-|}
    [ -z "${PATTERN##*${SEP}${1}${SEP}*}" ]
}

Examples:

for needle in foo bar; do
    isin "$needle" "|hello|world|foo|" && echo "found: $needle"
done

# using ";" as separator
for needle in foo bar; do
    isin "$needle" ";hello;world;foo;" \; && echo "found: $needle"
done

# using the string "RS" as separator
for needle in foo bar; do
    isin "$needle" "RShelloRSworldRSfooRS" RS && echo "found: $needle"
done

You can mix this solution with the case statement if you want both of the worlds:

PATTERN="|foo bar|baz|bla|"

case "$needle" in
    xyz) echo "matched in a static part" ;;
    *)
        if [ -z "${PATTERN##*|${needle}|*}" ]; then
            echo "$needle matched $PATTERN"
        else
            echo "not found"
        fi
esac

Note

Sometimes it is good to remember you could do your whole script in awk(1p), which is also POSIX, but I believe this is for another answer.

Upvotes: 7

ata
ata

Reputation: 2065

This obviates the need for escaping or anything else:

PATTERN="foo bar baz bla"

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

Upvotes: -1

chris
chris

Reputation: 141

"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..

Upvotes: 1

Dennis Williamson
Dennis Williamson

Reputation: 360105

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

Upvotes: 0

dimba
dimba

Reputation: 27581

This should work:

PATTERN="foo|bar|baz|bla"

shopt -s extglob

case "foo" in
    @($(echo $PATTERN)))
        printf "matched\n"
        ;;
    *)
        printf "no match\n"
        ;;
esac

Upvotes: 3

glenn jackman
glenn jackman

Reputation: 246807

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

Upvotes: 0

Philipp
Philipp

Reputation: 49812

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.

Upvotes: 1

Related Questions