มีตัวดำเนินการ "in" ใน bash / bourne หรือไม่?


17

ฉันกำลังมองหาโอเปอเรเตอร์“ in” ที่ใช้งานได้เช่นนี้:

if [ "$1" in ("cat","dog","mouse") ]; then
    echo "dollar 1 is either a cat or a dog or a mouse"
fi

เห็นได้ชัดว่ามันเป็นคำสั่งที่สั้นกว่าเมื่อเปรียบเทียบกับพูดโดยใช้การทดสอบหลาย ๆ แบบหรือ

คำตอบ:


31

คุณสามารถใช้case...esac

$ cat in.sh 
#!/bin/bash

case "$1" in 
  "cat"|"dog"|"mouse")
    echo "dollar 1 is either a cat or a dog or a mouse"
  ;;
  *)
    echo "none of the above"
  ;;
esac

อดีต

$ ./in.sh dog
dollar 1 is either a cat or a dog or a mouse
$ ./in.sh hamster
none of the above

ด้วยksh, bash -O extglobหรือzsh -o kshglobคุณยังสามารถใช้รูปแบบการ glob ขยาย:

if [[ "$1" = @(cat|dog|mouse) ]]; then
  echo "dollar 1 is either a cat or a dog or a mouse"
else
  echo "none of the above"
fi

ด้วยbash, ksh93หรือzshคุณยังสามารถใช้การเปรียบเทียบการแสดงออกปกติ:

if [[ "$1" =~ ^(cat|dog|mouse)$ ]]; then
  echo "dollar 1 is either a cat or a dog or a mouse"
else
  echo "none of the above"
fi

เหตุผลใด ๆ สำหรับวงเล็บคู่? ขอบคุณอีกครั้ง!
mrjayviper

1
@mrjayviper วงเล็บคู่เป็นโครงสร้างการทดสอบเพิ่มเติม - AFAIK ตัวดำเนินการ regex =~ไม่ถูกต้องในการทดสอบวงเล็บเดี่ยว POSIX
steeldriver

1
@steeldriver [เป็นเพียงPOSIX แต่[[เป็นคุณสมบัติเพิ่มเติมของ bash, ksh (เห็นได้ชัดว่ามันมีต้นกำเนิดมาจากที่นั่นและ zsh. caseตัวอย่างคือ POSIX ส่วนใหญ่ของทั้งหมดแม้ว่า
Sergiy Kolodyazhnyy

2
@ StéphaneChazelasตั้งแต่ bash 4.1-alpha เป็นอย่างน้อยจึงไม่จำเป็นต้องตั้งค่า extglog อย่างชัดเจน จากการเปลี่ยนแปลงของ bash : s บังคับให้ extglob เปิดชั่วคราวเมื่อแยกอาร์กิวเมนต์รูปแบบเป็นตัวดำเนินการ == และ! = ไปที่ [[คำสั่งเพื่อความเข้ากันได้
Isaac

@steeldriver case "$1" inไม่จำเป็นต้องอ้างสิทธิ์ $ 1 ในไม่มีการแยกคำหรือการขยายชื่อพา ธ ในโทเค็นนั้น
Isaac

9

ไม่มีการทดสอบแบบ "ใน" ในการทุบตี แต่มีการทดสอบ regex (ไม่ใช่ในแบบ Bourne):

if [[ $1 =~ ^(cat|dog|mouse)$ ]]; then
    echo "dollar 1 is either a cat or a dog or a mouse"
fi

และมักเขียนโดยใช้ตัวแปร (มีปัญหาน้อยลงกับการอ้างอิง):

regex='^(cat|dog|mouse)$'

if [[ $1 =~ $regex ]]; then
    echo "dollar 1 is either a cat or a dog or a mouse"
fi

สำหรับเชลล์เป้าหมายที่เก่ากว่าคุณต้องใช้การจับคู่เคส:

case $1 in
    cat|dog|mouse)   echo "dollar 1 is either a cat or a dog or a mouse";;
esac

นี่คือคำตอบที่ได้รับการยอมรับสำหรับฉัน
Nam G VU

6

การใช้ a caseนั้นทำได้ดีและดีถ้าคุณมีสัตว์เลี้ยงที่คุณต้องการจับคู่ แต่จะไม่ทำงานหากคุณต้องการสร้างรูปแบบในรันไทม์เนื่องจากcaseไม่ตีความการสลับจากภายในพารามิเตอร์ที่ขยาย

สิ่งนี้จะจับคู่เฉพาะสตริงตัวอักษรcat|dog|mouse:

patt='cat|dog|mouse'
case $1 in 
        $patt) echo "$1 matches the case" ;; 
esac

อย่างไรก็ตามคุณสามารถใช้ตัวแปรกับนิพจน์ทั่วไปที่ตรงกันได้ ตราบใดที่ตัวแปรไม่ได้ถูกยกมาตัวดำเนินการ regex ใด ๆ ที่อยู่ภายในจะมีความหมายพิเศษ

patt='cat|dog|mouse'
if [[ "$1" =~ ^($patt)$ ]]; then
        echo "$1 matches the pattern"
fi

คุณสามารถใช้อาเรย์แบบเชื่อมโยงได้ การตรวจสอบว่ามีคีย์อยู่ในคีย์นั้นเป็นสิ่งที่ใกล้เคียงที่สุดกับinโอเปอเรเตอร์ที่ Bash ระบุหรือไม่ แม้ว่าไวยากรณ์จะค่อนข้างน่าเกลียด:

declare -A arr
arr[cat]=1
arr[dog]=1
arr[mouse]=1

if [ "${arr[$1]+x}" ]; then
        echo "$1 is in the array"
fi

( ${arr[$1]+x}ขยายเป็นxถ้าarr[$1]ตั้งค่าว่างเปล่า )


5

คุณสามารถใช้caseคำสั่งในการifทดสอบ แต่รหัสจะมีลักษณะขนเล็กน้อย:

if case "$1" in (cat|dog|mouse) true ;; (*) false; esac; then
    printf '"%s" is one of cat, dog or mouse\n' "$1"
else
    printf '"%s" is unknown\n' "$1"
fi

หรือสั้นกว่าเล็กน้อย

if ! case "$1" in (cat|dog|mouse) false; esac; then
    printf '"%s" is one of cat, dog or mouse\n' "$1"
else
    printf '"%s" is unknown\n' "$1"
fi

นี่เป็นการใช้ส่วนcaseคำสั่งเพียงเพื่อทำการจับคู่รูปแบบสำหรับส่วนifคำสั่ง แนะนำการทดสอบจริง / เท็จที่ไม่จำเป็น

มันจะดีกว่าที่จะใช้เพียงcase:

case "$1" in
    cat|dog|mouse)
        printf '"%s" is one of cat, dog or mouse\n' "$1"
        ;;
    *)
        printf '"%s" is unknown\n' "$1"
esac

อย่าทำสิ่งนี้:

is_one_of () {
    eval "case $1 in ($2) return 0; esac"
    return 1
}

if is_one_of "$1" 'cat|dog|mouse'; then
    printf '"%s" is one of cat, dog or mouse\n' "$1"
else
    printf '"%s" is unknown\n' "$1"
fi

หรือสิ่งนี้:

is_one_of () (
    word=$1
    shift
    IFS='|'
    eval "case $word in ($*) return 0; esac"
    return 1
)

if is_one_of "$1" cat dog mouse; then
    printf '"%s" is one of cat, dog or mouse\n' "$1"
else
    printf '"%s" is unknown\n' "$1"
fi

... เนื่องจากคุณกำลังเพิ่ม cruft ที่อันตรายมากขึ้นเพื่อให้สามารถใช้ifคำสั่งในรหัสของคุณแทนcaseคำสั่งที่สมเหตุสมผลอย่างสมบูรณ์


จะไม่เป็นการดีกว่าถ้าแบ่งกรณีและการเรียกฟังก์ชั่นและประเมินสถานะทางออกภายในคำสั่ง if?
Sergiy Kolodyazhnyy

@SergiyKolodyazhnyy และปล่อยให้รูปแบบเป็นอาร์กิวเมนต์ของฟังก์ชั่น? มันจะต้องมีการทำevalของcaseคำสั่งในกรณีนี้และมันจะยิ่งมีแนวโน้มที่จะเกิดข้อผิดพลาด
Kusalananda

"$1"|"$2"|"$3"ในกรณีนี้มีรูปแบบที่เรียบง่ายแต่ละอาจจะผ่านเป็นพารามิเตอร์ตำแหน่งที่จะทำงานและทำ ยังunix.stackexchange.com/a/234415/85039 แต่ใช่มันมีขนเล็กน้อย
Sergiy Kolodyazhnyy

4

grep เข้าใกล้

if echo $1 | grep -qE "^(cat|dog|mouse)$"; then 
    echo "dollar 1 is either a cat or a dog or a mouse"
fi
  • -qเพื่อหลีกเลี่ยงผลลัพธ์ไปยังหน้าจอใด ๆ (เร็วกว่าการพิมพ์>/dev/null)
  • -Eสำหรับ(cat|dog|mouse)ด้านการแสดงออกปกติขยายความต้องการนี้
  • ^(cat|dog|mouse)$ตรงกับบรรทัดใด ๆ ที่เริ่มต้น ( ^) กับแมวสุนัขหรือเมาส์ ( (cat|dog|mouse)) ตามด้วยท้ายบรรทัด ( $)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.