7

So this is my code:

#!/bin/bash

action_list='list|add|rem'

while true; do
        echo "Actions include: list - show list
                                add - add item to list
                                rem - remove item from list"
        read -p "Input action: " action_var

        case $action_var in
                ${action_list} ) echo "Option $action_var is valid";;
                *) echo "Option $action_var is INVALID";;
        esac

        echo $action_var
done

What I want is to use the case command such that, in the future if I add more options, I don't need to hardcode them in, I can just use the "action_list" variable.

But the ${action_list} ) construct does not work. Now I tried hardcoding it like list|add|rem)... and it works.

Why wouldn't a variable work in this case?

2 Answers2

4

Checking the docs:

case

The syntax of the case command is:

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

[...] Each pattern undergoes tilde expansion, parameter expansion, command substitution, and arithmetic expansion.

What happens is that parameter expansion takes place after the patterns have been identified (using the | separator). A | from an expansion then is just the plain | character and not a pattern separator. (Similar reasoning as in another answer from me.)

muru
  • 72,889
  • So then how would I do what I want to do here? – user361323 Nov 03 '19 at 04:03
  • @user361323 you could use the [[ extended test and regular expression matching (if [[ $action_var =~ $action_list ]]; then ...). Or one of the options in How do I test if an item is in a bash array? – muru Nov 03 '19 at 04:06
  • But in this case if $action_var=do and $action_list="sum drop dont etc" it would be matched, no? So I'd have to add some magic and add $action_var="^whatever_the_var_is$" right? – user361323 Nov 03 '19 at 04:09
  • Or one of the options in How do I test if an item is in a bash array?, instead of forcing an array into a string and trying to test for inclusion in that. – muru Nov 03 '19 at 04:10
  • I also thought of using a loop, but wouldn't that create a lot of overhead? Let's say an extreme case, what if there are 2 million options in $action_list and the one that would match $action_var is at the end of the list, it'd have to iterate through all of them. The other answers (associative arrays) are a bit more complex and I need to study them a bit more. – user361323 Nov 03 '19 at 04:19
  • If you have 2 million (or even 2 hundred) actions, something somewhere has gone horribly wrong and Cthulhu has risen. – muru Nov 03 '19 at 05:03
  • Hehe, there might be a possibility because the larger scope of this project is a text based game of sorts. Of course, 2m is greatly exaggerated, even 200 is pretty far fetched. – user361323 Nov 03 '19 at 05:11
  • @user361323, if you actually had to even consider 2 million anything, you should already be as far as possible from any shell scripts... 2 thousand would already be a point where you probably should be running away. – ilkkachu Nov 03 '19 at 08:47
1

Since you have only two responses, I don’t think case is the right solution here. Many ways to go about this; I would do something like this:

action_list="list|add|remove|foo|bar"
if grep -qE "^(${action_list})$" <<< "${action_var}" ; then
  echo valid
else
  echo not valid ${action_var}
fi

If you are using bash, or a shell with similar variable expansion, this might be preferable for readability of the list variable:

action_list="list add remove foo bar"
if grep -qE "^(${action_list// /|})$" <<< "${action_var}" ; then
  echo valid
else
  echo not valid ${action_var}
fi
bxm
  • 4,855