How can I use a variable as a case condition?

I am trying to use a variable consisting of different strings separated with a | as a case statement test. For example:

string=""foo"|"bar""
read choice
case $choice in
    $string)
        echo "You chose $choice";;
    *)
        echo "Bad choice!";;
esac

I want to be able to type foo or bar and execute the first part of the case statement. However, both foo and bar take me to the second:

$ foo.sh
foo
Bad choice!
$ foo.sh
bar
Bad choice!

Using "$string" instead of $string makes no difference. Neither does using string="foo|bar".

I know I can do it this way:

case $choice in
    "foo"|"bar")
        echo "You chose $choice";;
    *)
        echo "Bad choice!";;
esac

I can think of various workarounds but I would like to know if it’s possible to use a variable as a case condition in bash. Is it possible and, if so, how?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

The bash manual states:

case word in [ [(] pattern [ | pattern ] … ) list ;; ] … esac

Each pattern examined is expanded using tilde expansion, parameter and variable expansion, arithmetic substitution, command substitution, and process substitution.

No «Pathname expansion»

Thus: a pattern is NOT expanded with «Pathname expansion».

Therefore: a pattern could NOT contain “|” inside. Only: two patterns could be joined with the “|”.

This works:

s1="foo"; s2="bar"    # or even s1="*foo*"; s2="*bar*"

read choice
case $choice in
    $s1|$s2 )     echo "Two val choice $choice"; ;;  # not "$s1"|"$s2"
    * )           echo "A Bad  choice! $choice"; ;;
esac

Using « Extended Globbing »

However, word is matched with pattern using « Pathname Expansion » rules.
And « Extended Globbing » here, here and, here allows the use of alternating (“|”) patterns.

This also work:

shopt -s extglob

string='@(foo|bar)'

read choice
    case $choice in
        $string )      printf 'String  choice %-20s' "$choice"; ;;&
        $s1|$s2 )      printf 'Two val choice %-20s' "$choice"; ;;
        *)             printf 'A Bad  choice! %-20s' "$choice"; ;;
    esac
echo

String content

The next test script shows that the pattern that match all lines that contain either foo or bar anywhere is '*$(foo|bar)*' or the two variables $s1=*foo* and $s2=*bar*


Testing script:

shopt -s extglob    # comment out this line to test unset extglob.
shopt -p extglob

s1="*foo*"; s2="*bar*"

string="*foo*"
string="*foo*|*bar*"
string='@(*foo*|*bar)'
string='*@(foo|bar)*'
printf "%sn" "$string"

while IFS= read -r choice; do
    case $choice in
        "$s1"|"$s2" )   printf 'A first choice %-20s' "$choice"; ;;&
        $string )   printf 'String  choice %-20s' "$choice"; ;;&
        $s1|$s2 )   printf 'Two val choice %-20s' "$choice"; ;;
        *)      printf 'A Bad  choice! %-20s' "$choice"; ;;
    esac
    echo
done <<-_several_strings_
f
b
foo
bar
*foo*
*foo*|*bar*
"foo"
"foo"
afooline
onebarvalue
now foo with spaces
_several_strings_

Method 2

You can use the extglob option:

shopt -s extglob
string='@(foo|bar)'

Method 3

You need two variables for case because the or | pipe is parsed before the patterns are expanded.

v1=foo v2=bar

case foo in ("$v1"|"$v2") echo foo; esac

foo

Shell patterns in variables are handled differently when quoted or unquoted as well:

q=?

case a in
("$q") echo question mark;;
($q)   echo not a question mark
esac

not a question mark

Method 4

Here is a POSIX solution using eval :

#!/bin/sh
string="foo|bar"
read choice
eval "
case $choice in
    $string)
        echo "You chose $choice";;
    *)
        echo "Bad choice!";;
esac
"

Explanation : the eval command makes the shell first expand the arguments so we are left with

case $choice in
    foo|bar)
        echo "You chose $choice";;
    *)
        echo "Bad choice!";;
esac

and then it interprets again the command and executes it.

Method 5

One can use AWK’s syntax

if($field ~ /regex/)

as well as

if($i ~ var)

to compare variable to input (var and star-argument list $*)

parse_arg_exists() {
  [ $# -eq 1 ] && return
  [ $# -lt 2 ] && printf "%sn" "Usage: ${FUNCNAME[*]} <match_case> list-or-$*" 
  "Prints the argument index that's matched in the regex-case (~ patn|patn2)" && exit 1
  export arg_case=$1
  shift
  echo "[email protected]" | awk 'BEGIN{FS=" "; ORS=" "; split(ENVIRON["arg_case"], a, "|")} {
    n=-1
    for(i in a) {
     for(f=1; f<=NF; f++) {
      if($f ~ a[i]) n=f
     }
    }
  }
  END {
    if(n >= 0) print "arg index " n "n"
    }'
 unset arg_case
}
string="--dot|-d"
printf "testing %sn" "$string"
args="--dot -b -c"; printf "%sn" "$args"
parse_arg_exists "$string" "$args"
args="-b -o"; printf "%sn" "$args"
parse_arg_exists "$string" "$args"
args="-b -d -a"; printf "%sn" "$args"
parse_arg_exists "$string" "$args"

Prints out:

testing --dot|-d
--dot -b -c
arg index 1
 -b -o
-b -d -a
arg index 2

Method 6

If You want a dash-compatible work-around, You could write:

  string="foo|bar"
  read choice
  awk 'BEGIN{
   n=split("'$string'",p,"|");
   for(i=1;i<=n;i++)
    if(system("
      case "'$choice'" in "p[i]")
       echo "You chose '$choice'";
       exit 1;;
      esac"))
     exit;
   print "Bad choice"
  }'

Explanation:

  • awk is used to split string and test each part separately. If choice matches the currently tested part p[i], the awk-command will be ended with exit in line 11.
  • For the very test, the shell’s case is used (within a system-call), as asked by @terdon. This keeps the possibility to modify the intended test-string for example to foo*|bar in order to match also for foooo (“search pattern”), as the shell’s case allows.
  • If instead you would prefer regular expressions, you could omit the system-call and use awk‘s ~or match instead.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x