Как я могу использовать переменную как условие случая?

17

Я пытаюсь использовать переменную, состоящую из разных строк, разделенных с помощью |в качестве caseпроверки операторов. Например:

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

Я хочу иметь возможность набрать fooили barвыполнить первую часть caseзаявления. Впрочем, и то, fooи другое barменя возьмут

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

Использование "$string"вместо $stringне имеет значения. Ни один не использует string="foo|bar".

Я знаю, что могу сделать это так:

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

Я могу думать о различных обходных путях, но я хотел бы знать, возможно ли использовать переменную в качестве caseусловия в bash. Возможно ли это, и если да, то как?

Тердон
источник
2
Я не могу заставить себя предложить это как реальный ответ, но, поскольку никто другой не упомянул об этом, вы можете заключить в дело case eval, исключая $ choice, parens, звездочку, точку с запятой и символы новой строки. Ужасно, но это "работает".
Джефф Шаллер
@JeffSchaller - это не плохая идея много раз, и, возможно, просто билет в этом случае. Я тоже посоветовал порекомендовать это, но readбит остановил меня. по моему мнению, проверка правильности ввода данных пользователем, что и выглядит, что caseшаблоны не должны быть в верхней части списка оценки, а должны быть сокращены до *шаблона по умолчанию, так что единственные результаты, которые там достигаются, гарантированно приемлемы. Тем не менее, поскольку проблема заключается в порядке разбора / расширения, тогда может потребоваться вторая оценка.
mikeserv

Ответы:

13

Руководство по bash гласит:

регистр слов в списке [[(] pattern [| pattern] ...) ;; ] ... esac

Каждый исследуемый шаблон расширяется с использованием расширения тильды, расширения параметров и переменных, арифметической замены, подстановки команд и подстановки процесса.

Нет «Расширение пути»

Таким образом: шаблон НЕ раскрывается с «расширением пути».

Следовательно: шаблон НЕ может содержать «|» внутри. Только: два шаблона могут быть объединены с "|".

Это работает:

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

Использование «Extended Globbing»

Однако wordэто соответствует patternправилам «Расширение пути».
А «Extended Globbing» здесь , здесь и здесь позволяет использовать чередующиеся («|») шаблоны.

Это также работает:

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

Строковый контент

Следующий тестовый скрипт показывает, что шаблон, который соответствует всем строкам, которые содержат fooили barгде- '*$(foo|bar)*'либо, или две переменные $s1=*foo*и$s2=*bar*


Скрипт тестирования:

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_
Сообщество
источник
9

Вы можете использовать extglobопцию:

shopt -s extglob
string='@(foo|bar)'
choroba
источник
Интересный; что делает @(foo|bar)особенным по сравнению с foo|bar? Оба являются допустимыми шаблонами, которые работают одинаково при буквальном наборе.
Chepner
4
Ах, неважно. |это не часть шаблона foo|bar, это часть синтаксиса caseоператора, позволяющая использовать несколько шаблонов в одном предложении. | является частью расширенного шаблона, хотя.
Chepner
3

Вам нужны две переменные, caseпотому что |труба или анализируется перед расширением шаблонов.

v1=foo v2=bar

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

foo

Шаблоны оболочки в переменных обрабатываются по-разному, когда они заключаются в кавычки или не заключаются в кавычки:

q=?

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

not a question mark
mikeserv
источник
-1

Если вы хотите обходной путь, совместимый с тире, вы можете написать:

  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"
  }'

Объяснение: awk используется для разбиения «строки» и проверки каждой части отдельно. Если «choice» соответствует проверенной в данный момент части p [i], команда awk завершается строкой «exit» в строке 11. Для самого теста используется «case» оболочки (в вызове 'system') как спросил тердон. Это сохраняет возможность изменить предполагаемую тестовую «строку», например, на «foo * | bar», чтобы соответствовать также для «foooo» («шаблон поиска»), как позволяет «case» оболочки. Если вместо этого вы бы предпочли регулярные выражения, вы могли бы опустить системный вызов и вместо этого использовать awk "~" или "match".

Джеральд Шейд
источник
Уважаемый downvoter, я мог бы лучше узнать, как избежать бесполезных кандидатов на решение, если бы Вы сказали мне причину своего downvote.
Джеральд Шаде