Reputation:
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
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
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
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
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
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
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
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
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