Como posso usar uma variável como uma condição de caso?

14

Estou tentando usar uma variável que consiste em strings diferentes separadas por um teste de instrução | como case . Por exemplo:

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

Eu quero poder digitar foo ou bar e executar a primeira parte da instrução case . No entanto, foo e bar levam-me ao segundo:

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

Usar "$string" em vez de $string não faz diferença. Nem o uso de string="foo|bar" .

Eu sei que posso fazer assim:

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

Posso pensar em várias soluções alternativas, mas gostaria de saber se é possível usar uma variável como uma condição case no bash. É possível e, em caso afirmativo, como?

    
por terdon 06.10.2015 / 14:35

3 respostas

9

O manual do bash declara:

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.

Não «Expansão do nome do caminho»

Assim: um padrão NÃO é expandido com "Expansão do nome do caminho".

Portanto: um padrão NÃO pode conter "|" dentro. Apenas: dois padrões podem ser associados ao "|".

Isso funciona:

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

Usando o «alargamento da globalização»

No entanto, word é correspondido com pattern usando as regras de "Expansão de nome de caminho".
E "Ampliação do Globo" aqui , aqui e, aqui permite o uso de padrões alternativos ("|").

Isso também funciona:

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

Conteúdo da sequência

O próximo script de teste mostra que o padrão que corresponde a todas as linhas que contêm foo ou bar em qualquer lugar é '*$(foo|bar)*' ou as duas variáveis $s1=*foo* e $s2=*bar*

Script de teste:

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 "%s\n" "$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_
    
por 07.10.2015 / 04:43
8

Você pode usar a opção extglob :

shopt -s extglob
string='@(foo|bar)'
    
por 06.10.2015 / 14:49
3

Você precisa de duas variáveis para case porque o ou | pipe é analisado antes de os padrões serem expandidos.

v1=foo v2=bar

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

Os padrões de shell nas variáveis são tratados de maneira diferente quando citados ou sem aspas:

q=?

case a in
("$q") echo question mark;;
($q)   echo not a question mark
esac
not a question mark
    
por 06.10.2015 / 18:37