ฉันจะใช้ตัวแปรเป็นเงื่อนไขเคสได้อย่างไร?


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เงื่อนไขในการทุบตี เป็นไปได้และถ้าเป็นเช่นนั้นได้อย่างไร


2
ฉันไม่สามารถแนะนำตัวเองให้เป็นคำตอบที่แท้จริงได้ แต่เนื่องจากไม่มีใครพูดถึงคุณจึงสามารถรวมคำสั่ง case ไว้ในการevalหลีกเลี่ยง $ choice, parens, asterisk, semicolons และ newlines น่าเกลียด แต่มัน "ได้ผล"
Jeff Schaller

@JeffSchaller - มันไม่ใช่ความคิดที่เลวร้ายหลายครั้งและอาจเป็นแค่ตั๋วในกรณีนี้ ฉันคิดว่าจะแนะนำเช่นกัน แต่readบิตก็หยุดฉัน ในความเห็นของฉันการตรวจสอบการป้อนข้อมูลของผู้ใช้ซึ่งเป็นสิ่งที่ดูเหมือนว่าจะเป็นcaseรูปแบบไม่ควรอยู่ด้านบนของรายการการประเมินผลและควรถูกตัดให้เป็น*รูปแบบเริ่มต้นแทนที่จะรับประกันผลลัพธ์เท่านั้นที่ยอมรับได้ ยังเพราะปัญหาคือการแยก / คำสั่งขยายแล้วการประเมินผลที่สองอาจเป็นสิ่งที่เรียกร้องให้
mikeserv

คำตอบ:


13

คู่มือการทุบตี:

กรณีคำในรายการ [[(] รูปแบบ [| รูปแบบ] ... ) รายการ ;; ] ... 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

การใช้«ขยาย Globbing »

อย่างไรก็ตามwordถูกจับคู่กับการpatternใช้กฎ«การขยายชื่อพา ธ »
และ«เสริมการ 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)'

ที่น่าสนใจ; สิ่งที่ทำให้@(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

-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 ใช้เพื่อแยก "string" และทดสอบแต่ละส่วนแยกกัน หาก "ทางเลือก" ตรงกับส่วนที่ทดสอบในปัจจุบัน p [i] คำสั่ง awk จะถูกลงท้ายด้วย "exit" ในบรรทัดที่ 11 สำหรับการทดสอบอย่างมากจะใช้ "case" ของเชลล์ (ภายใน 'system'-call) ตามที่ถามโดย terdon สิ่งนี้ทำให้มีความเป็นไปได้ในการปรับเปลี่ยนการทดสอบ "สตริง" ตัวอย่างเช่น "foo * | bar" เพื่อให้ตรงกับ "foooo" ("รูปแบบการค้นหา") ตามที่ "กรณี" ของเชลล์อนุญาต หากคุณต้องการใช้นิพจน์ทั่วไปคุณสามารถละเว้น "system" -call และใช้ awk's "~" หรือ "match" แทน


เรียนผู้ลงคะแนนต่ำกว่าฉันเรียนรู้วิธีหลีกเลี่ยงผู้สมัครที่ไร้ประโยชน์ถ้าคุณจะบอกเหตุผลให้คุณทราบ
เจอรัลด์ Schade
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.