Làm thế nào tôi có thể sử dụng một biến như một điều kiện trường hợp?


17

Tôi đang cố gắng sử dụng một biến bao gồm các chuỗi khác nhau tách ra với một |như một casebài kiểm tra tuyên bố. Ví dụ:

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

Tôi muốn có thể gõ foohoặc barthực hiện phần đầu tiên của casecâu lệnh. Tuy nhiên, cả hai foobarđưa tôi đến lần thứ hai:

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

Sử dụng "$string"thay vì $stringlàm cho không có sự khác biệt. Không sử dụng string="foo|bar".

Tôi biết tôi có thể làm theo cách này:

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

Tôi có thể nghĩ ra nhiều cách giải quyết khác nhau nhưng tôi muốn biết liệu có thể sử dụng một biến làm caseđiều kiện trong bash không. Có thể và, nếu vậy, làm thế nào?


2
Tôi không thể tự đề xuất nó như một câu trả lời thực sự, nhưng vì không ai khác đề cập đến nó, bạn có thể gói lại tuyên bố trường hợp trong một eval, thoát khỏi sự lựa chọn $, parens, dấu hoa thị, dấu chấm phẩy và dòng mới. Xấu xí, nhưng nó "hoạt động".
Jeff Schaller

@JeffSchaller - đó không phải là ý kiến ​​tồi nhiều lần, và có lẽ chỉ là tấm vé trong trường hợp này. tôi cũng xem xét giới thiệu nó, nhưng readbit đã ngăn tôi lại. theo ý kiến ​​của tôi, xác thực đầu vào của người dùng, đó là những gì có vẻ như vậy, casecác mẫu không nên ở đầu danh sách đánh giá và thay vào đó nên được cắt theo *mẫu mặc định sao cho kết quả duy nhất đạt được ở đó được đảm bảo chấp nhận được. Tuy nhiên, vì vấn đề là phân tích cú pháp / lệnh mở rộng, nên đánh giá thứ hai có thể là điều được yêu cầu.
mikeerv

Câu trả lời:


13

Hướng dẫn sử dụng bash nêu:

trường hợp từ trong danh sách [[(] mẫu [| mẫu] ...) ;; ] ... esac

Mỗi mẫu được kiểm tra được mở rộng bằng cách sử dụng mở rộng dấu ngã, mở rộng tham số và biến, thay thế số học, thay thế lệnh và thay thế quy trình.

Không có «Mở rộng tên đường dẫn»

Do đó: một mẫu KHÔNG được mở rộng với «Mở rộng tên đường dẫn».

Do đó: một mẫu KHÔNG thể chứa "|" phía trong. Chỉ: hai mẫu có thể được nối với "|".

Những công việc này:

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

Sử dụng «Mở rộng toàn cầu»

Tuy nhiên, wordđược kết hợp với patternviệc sử dụng quy tắc «Mở rộng tên đường dẫn».
Và «Mở rộng toàn cầu» ở đây , ở đây và, ở đây cho phép sử dụng các mẫu ("|") xen kẽ.

Điều này cũng hoạt động:

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

Nội dung chuỗi

Kịch bản kiểm tra tiếp theo cho thấy rằng mẫu phù hợp với tất cả các dòng có chứa một foohoặc barbất kỳ nơi nào là '*$(foo|bar)*'hoặc hai biến $s1=*foo*$s2=*bar*


Kiểm tra tập lệnh:

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

Bạn có thể sử dụng extglobtùy chọn:

shopt -s extglob
string='@(foo|bar)'

Hấp dẫn; Điều gì làm cho @(foo|bar)đặc biệt so với foo|bar? Cả hai đều là các mẫu hợp lệ hoạt động như nhau khi được gõ theo nghĩa đen.
chepner

4
À, đừng bận tâm. |không phải là một phần của mẫu trong foo|bar, nó là một phần của cú pháp của casecâu lệnh để cho phép nhiều mẫu trong một mệnh đề. | một phần của mô hình mở rộng, mặc dù.
chepner

3

Bạn cần hai biến case|đường ống hoặc được phân tích cú pháp trước khi các mẫu được mở rộng.

v1=foo v2=bar

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

foo

Các mẫu shell trong các biến cũng được xử lý khác nhau khi được trích dẫn hoặc không trích dẫn:

q=?

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

not a question mark

-1

Nếu bạn muốn có một công việc tương thích với dấu gạch ngang, bạn có thể viết:

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

Giải thích: awk được sử dụng để tách "chuỗi" và kiểm tra từng phần riêng biệt. Nếu "sự lựa chọn" khớp với phần p [i] hiện đang được thử nghiệm, lệnh awk sẽ được kết thúc bằng "exit" trong dòng 11. Đối với chính bài kiểm tra, "trường hợp" của shell được sử dụng (trong 'lệnh gọi hệ thống) , như được hỏi bởi terdon. Điều này giữ khả năng sửa đổi thử nghiệm dự định- "chuỗi", ví dụ thành "foo * | bar" để khớp với "foooo" ("mẫu tìm kiếm"), vì "trường hợp" của shell cho phép. Nếu thay vào đó, bạn thích các biểu thức thông thường hơn, Bạn có thể bỏ qua "hệ thống" -call và sử dụng "~" hoặc "khớp" của awk để thay thế.


Kính gửi downvoter, tôi có thể học tốt hơn cách tránh các ứng cử viên giải pháp vô dụng nếu Bạn cho tôi biết lý do cho downvote của bạn.
Gerald Schade
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.