ケース条件として変数を使用するにはどうすればよいですか?


17

ステートメントテスト|としてaで区切られた異なる文字列で構成される変数を使用しようとしていますcase。例えば:

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

ステートメントの最初の部分を入力fooまたはbar実行できるようにしcaseます。しかし、両方foobar第二に私を取ります:

$ 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ステートメントを$ choice、括弧、アスタリスク、セミコロン、および改行でエスケープできますeval。glyいですが、それは「機能します」。
ジェフシャラー

@JeffSchaller-多くの場合、それは悪い考えではなく、この場合の単なるチケットです。私もそれを推薦することを検討しましたが、readビットは私を止めました。これはと思われるものである私の意見のユーザー入力の検証、で、caseパターンはすべきではない、評価リストの最上部に表示され、むしろまで剪定する必要があり*、デフォルトのパターンがあるに達するだけの結果が許容保証されるように。それでも、問題は解析/展開の順序であるため、2番目の評価が要求される可能性があります。
mikeserv

回答:


13

bashマニュアルの状態:

[[(] pattern [| pattern] ...)リストのケースワード;; ] ... esac

検査される各パターンは、チルダ展開、パラメーターおよび変数展開、算術置換、コマンド置換、プロセス置換を使用して拡張されます。

«パス名の展開»なし

したがって、パターンは«パス名展開»で展開されません。

したがって、パターンに「|」を含めることはできません 内部。のみ:2つのパターンを「|」で結合できます。

これは動作します:

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

«拡張グローブ機能»の使用

ただし、「パス名拡張」ルールの使用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)*'2つの変数で$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)'

面白い; 何が@(foo|bar)特別なのfoo|barですか?両方とも、文字どおりに入力しても同じように機能する有効なパターンです。
-chepner

4
ああ、気にしないで。|はのパターンの一部ではなく、1つの句で複数のパターンを許可foo|barするcaseステートメントの構文の一部です。| あるものの、延長パターンの一部。
chepner

3

パターンが展開される前にcase or |パイプが解析されるため、2つの変数が必要です。

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

-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コマンドは11行目の「exit」で終了します。まさにテストでは、シェルの「case」が使用されます(「system」コール内) 、terdonの質問どおり。これにより、シェルの「ケース」が許す限り、「foooo」(「検索パターン」)にも一致するように、意図したtest-「string」を「foo * | bar」などに変更する可能性が保持されます。代わりに正規表現を好む場合は、「システム」呼び出しを省略し、代わりにawkの「〜」または「一致」を使用できます。


親愛なるdownvoter、あなたがあなたのdownvoteの理由を教えてくれるなら、無駄な解決策候補を避ける方法をよりよく学ぶことができます。
ジェラルドシェード
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.