Bagaimana saya bisa menggunakan variabel sebagai kondisi kasus?

17

Saya mencoba menggunakan variabel yang terdiri dari string berbeda yang dipisahkan dengan |sebagai casetes pernyataan. Sebagai contoh:

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

Saya ingin dapat mengetik fooatau bardan menjalankan bagian pertama dari casepernyataan itu. Namun, keduanya foodan barbawa saya ke yang kedua:

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

Menggunakan "$string"bukannya $stringtidak ada bedanya. Tidak menggunakan string="foo|bar".

Saya tahu saya bisa melakukannya dengan cara ini:

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

Saya dapat memikirkan berbagai solusi tetapi saya ingin tahu apakah mungkin menggunakan variabel sebagai casekondisi dalam bash. Apakah mungkin dan, jika ya, bagaimana?

terdon
sumber
2
Saya tidak bisa memaksakan diri untuk menyarankannya sebagai jawaban yang nyata, tetapi karena tidak ada orang lain yang menyebutkannya, Anda dapat membungkus pernyataan kasus dengan eval, melepaskan $ pilihan, parens, asterisk, titik koma, dan baris baru. Jelek, tapi "berhasil".
Jeff Schaller
@ JeffSchaller - itu bukan ide yang buruk banyak kali, dan mungkin hanya tiket dalam hal ini. Saya dianggap merekomendasikannya juga, tetapi readsedikit menghentikan saya. menurut pendapat saya, validasi input pengguna, yang memang seperti ini, casepola seharusnya tidak berada di bagian atas daftar evaluasi, dan lebih baik dipangkas ke *pola default sehingga satu-satunya hasil yang mencapai di sana dijamin dapat diterima. tetap saja, karena masalahnya adalah urutan parse / ekspansi, maka evaluasi kedua bisa menjadi apa yang diperlukan.
mikeserv

Jawaban:

13

Manual bash menyatakan:

kata dalam daftar [[(] pola [| pola] ...) ;; ] ... esac

Setiap pola diperiksa diperluas menggunakan ekspansi tilde, parameter dan ekspansi variabel, substitusi aritmatika, substitusi perintah, dan substitusi proses.

Tidak ada «Perluasan jalur»

Jadi: suatu pola TIDAK diperluas dengan «ekspansi Pathname».

Karenanya: suatu pola TIDAK dapat mengandung "|" dalam. Hanya: dua pola dapat digabungkan dengan "|".

Ini bekerja:

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

Menggunakan «Extended Globbing»

Namun, worddicocokkan dengan patternmenggunakan aturan «Pathname Expansion».
Dan «Extended Globbing» di sini , di sini dan, di sini memungkinkan penggunaan pola bolak-balik ("|").

Ini juga berfungsi:

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

Konten string

Skrip pengujian berikutnya menunjukkan bahwa pola yang cocok dengan semua baris yang berisi salah satu fooatau di barmana saja adalah '*$(foo|bar)*'atau dua variabel $s1=*foo*dan$s2=*bar*


Skrip pengujian:

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_
Komunitas
sumber
9

Anda dapat menggunakan extglobopsi:

shopt -s extglob
string='@(foo|bar)'
choroba
sumber
Menarik; apa yang membuat @(foo|bar)istimewa dibandingkan dengan foo|bar? Keduanya adalah pola yang valid yang berfungsi sama saat diketik secara harfiah.
chepner
4
Ah, sudahlah. |bukan bagian dari pola dalam foo|bar, itu bagian dari sintaks casepernyataan untuk memungkinkan beberapa pola dalam satu klausa. | adalah bagian dari pola yang diperluas.
chepner
3

Anda memerlukan dua variabel untuk casekarena atau |pipa diurai sebelum pola diperluas.

v1=foo v2=bar

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

foo

Pola Shell dalam variabel ditangani secara berbeda ketika dikutip atau tidak dikutip juga:

q=?

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

not a question mark
mikeserv
sumber
-1

Jika Anda ingin bekerja di dash-kompatibel, Anda bisa menulis:

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

Penjelasan: awk digunakan untuk membagi "string" dan menguji setiap bagian secara terpisah. Jika "pilihan" cocok dengan bagian yang sedang diuji p [i], perintah-awk akan diakhiri dengan "keluar" di baris 11. Untuk uji coba sendiri, "kasing" shell digunakan (dalam panggilan 'sistem') , seperti yang ditanyakan oleh terdon. Hal ini membuat kemungkinan untuk memodifikasi "string" pengujian yang dimaksud misalnya menjadi "foo * | bar" agar sesuai dengan "foooo" ("pola pencarian"), sesuai dengan "case" shell yang memungkinkan. Jika sebaliknya Anda lebih suka ekspresi reguler, Anda bisa menghilangkan "sistem" -call dan menggunakan awk "~" atau "cocok" sebagai gantinya.

Gerald Schade
sumber
Dear downvoter, saya bisa belajar bagaimana menghindari kandidat solusi yang tidak berguna jika Anda memberi tahu saya alasan downvote Anda.
Gerald Schade