Dynamic case statement in bash

2019-01-27 10:23发布

问题:

I'm trying to figure out how to create a dynamic case statement in a bash script.

For example, let's say I have the output of an awk statement with the following contents

red
green
blue

In this scenario, the output can change at any time.

I'm trying to then execute different logic if a value is included in this awk output.

So if the data above is in $list, then I'd conceptually like to do something like:

case "${my_var}" in
    $list)
        .....
    something_else)
        .....
esac

I'm trying to use this to build a dynamic custom tab completion function (see http://www.debian-administration.org/article/An_introduction_to_bash_completion_part_2 for some background).

Any ideas?

Thanks.

回答1:

A case statement is probably not the right tool for the job. If you store the awk output in an array then you can loop through the array to find if a choice is in it, and as a bonus can figure out which index that is, too.

#!/bin/bash

# Store command output in an array so each word is a separate array item.    
list=($(echo $'red\ngreen\nblue'))
my_var=blue

for ((i = 0; i < ${#list}; i++)); do
    if [[ ${list[$i]} = $my_var ]]; then
        echo "found at index $i"
        break
    fi
done

if ((i == ${#list})); then
    echo "not found"
fi


回答2:

You can't do this with a case statement, but it's easy enough to set up your own helper to check for list membership.

# stub to simulate this arbitrary call
my_awk_command() { printf '%s\n' red green blue; }
# helper to check list membership
list_contains() {
  local tgt="$1"; shift
  while (( $# )); do
    if [[ $1 = "$tgt" ]] ; then
      return 0
    fi
    shift
  done
  return 1
}

# the below is Bash 4 functionality; see BashFAQ #1 on how to replace it
readarray -t awk_output < <(my_awk_command)

if list_contains "$my_var" "${my_awk_command[@]}"; then
  ...something...
elif [[ "$my_var" = something_else ]] ; then
  ...something else...
fi


回答3:

You can create a dynamic case statement in bash by doing the following:

1) ensure the list is PIPE (|) seperated. IE. red|green|blue

2) wrap your case statement in an eval

For example:

valid="red|green|blue"

eval "case \"$choice\" in
    $valid)
        echo do something good here
        ;;
    *)
        echo invalid colour
        ;;
esac"

This works for simple variable processing, I can not guarantee this will work in all cases.



回答4:

You can approach this in a couple of different hacky ways:

pattern=($(awk_command))     # red\ngreen\nblue\n
saveIFS=$IFS
IFS='|'
pattern="^(${pattern[*]})$"  # ^(red|green|blue)$  (perhaps hackish)
IFS=$saveIFS

# simple regex match if statement (not hackish)
if [[ $var =~ $pattern ]]
then
    do_something
fi

# or a backwards case statement (very hackish)
case 1 in    # this could be a variable or a command substitution
    $([[ $var =~ $pattern]] && echo 1) )  # the echo 1 could be another command or the 1 could be yet another variable
        do_something;;
    * )
        do_default;;
esac