ใน Bash ฉันจะตรวจสอบว่าสตริงเริ่มต้นด้วยค่าบางอย่างได้อย่างไร


745

ฉันต้องการตรวจสอบว่าสตริงเริ่มต้นด้วย "node" เช่น "node001" สิ่งที่ต้องการ

if [ $HOST == user* ]
  then
  echo yes
fi

ฉันจะทำอย่างถูกต้องได้อย่างไร


ฉันต้องรวมนิพจน์เพิ่มเติมเพื่อตรวจสอบว่า HOST เป็น "user1" หรือเริ่มต้นด้วย "node"

if [ [[ $HOST == user1 ]] -o [[ $HOST == node* ]] ];
then
echo yes
fi

> > > -bash: [: too many arguments

ฉันจะทำอย่างถูกต้องได้อย่างไร


7
อย่าล่อลวงให้รวมการแสดงออกมากเกินไป อาจดูไม่ดีนักที่มีเงื่อนไขสองแบบแยกกัน แต่คุณสามารถให้ข้อความแสดงข้อผิดพลาดได้ดีขึ้นและทำให้สคริปต์ของคุณง่ายขึ้นในการดีบัก นอกจากนี้ฉันจะหลีกเลี่ยงคุณสมบัติทุบตี สวิตช์เป็นวิธีที่จะไป
เฮ็นดรี

คำตอบ:


1073

ตัวอย่างนี้ในคู่มือการใช้สคริปต์การทุบตีขั้นสูงกล่าวว่า:

# The == comparison operator behaves differently within a double-brackets
# test than within single brackets.

[[ $a == z* ]]   # True if $a starts with a "z" (wildcard matching).
[[ $a == "z*" ]] # True if $a is equal to z* (literal matching).

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


สำหรับคำถามที่สองของคุณคุณสามารถเขียนได้ดังนี้

HOST=user1
if  [[ $HOST == user1 ]] || [[ $HOST == node* ]] ;
then
    echo yes1
fi

HOST=node001
if [[ $HOST == user1 ]] || [[ $HOST == node* ]] ;
then
    echo yes2
fi

ซึ่งจะก้อง

yes1
yes2

ifไวยากรณ์ของ Bash ยากที่จะคุ้นเคย (IMO)


12
สำหรับ regex คุณหมายถึง [[$ a = ~ ^ z. *]]?
JStrahl

3
ดังนั้นจึงมีความแตกต่างในการใช้งานระหว่าง[[ $a == z* ]]และ[[ $a == "z*" ]]? ในคำอื่น ๆ : พวกเขาทำงานแตกต่างกันอย่างไร และคุณหมายถึงอะไรโดยเฉพาะเมื่อคุณพูดว่า "$ a เท่ากับ z *"?
Niels Bom

5
คุณไม่จำเป็นต้องมีตัวคั่นคำสั่ง ";" ถ้าคุณใส่ "แล้ว" ในบรรทัดของตัวเอง
Yaza

6
เพียงเพื่อความสมบูรณ์: เพื่อตรวจสอบว่าสตริงจบด้วย ... :[[ $a == *com ]]
lepe

4
ABS เป็นทางเลือกที่โชคร้ายสำหรับการอ้างอิง - เป็นอย่างมากจาก W3Schools ของการทุบตีเต็มไปด้วยเนื้อหาที่ล้าสมัยและตัวอย่างที่ไม่ดี ช่องฟรีโนด #bash ได้รับการพยายามที่จะกีดกันการใช้งานอย่างน้อยตั้งแต่ปี 2008 มีโอกาสที่จะได้รับตำแหน่งใหม่ที่BashFAQ # 31หรือไม่? (ฉันอยากจะแนะนำวิกิของ Bash-แฮกเกอร์ด้วย แต่ตอนนี้มันใช้งานไม่ได้แล้ว)
Charles Duffy

207

หากคุณกำลังใช้ Bash รุ่นล่าสุด (v3 +) ฉันขอแนะนำตัวดำเนินการเปรียบเทียบ Bash regex =~เช่น

if [[ "$HOST" =~ ^user.* ]]; then
    echo "yes"
fi

เพื่อให้ตรงกับthis or thatใน regex ใช้|ตัวอย่างเช่น

if [[ "$HOST" =~ ^user.*|^host1 ]]; then
    echo "yes"
fi

หมายเหตุ - นี่คือไวยากรณ์การแสดงออกปกติ 'เหมาะสม'

  • user*หมายถึงuseและเหตุการณ์ที่เกิดขึ้นเป็นศูนย์หรือมากกว่าของrดังนั้นuseและuserrrrจะจับคู่
  • user.*วิธีการuserและศูนย์หรืออื่น ๆ ที่เกิดขึ้นของตัวละครใด ๆ ดังนั้นuser1, userXจะตรงกับ
  • ^user.*หมายถึงจับคู่รูปแบบuser.*ที่จุดเริ่มต้นของ $ HOST

หากคุณไม่คุ้นเคยกับไวยากรณ์นิพจน์ปกติลองอ้างอิงถึงแหล่งข้อมูลนี้


ขอบคุณ Brabster! ฉันเพิ่มลงในโพสต์ต้นฉบับคำถามใหม่เกี่ยวกับวิธีการรวมการแสดงออกในถ้า cluase
ทิม

2
มันน่าเสียดายที่คำตอบที่ยอมรับไม่ได้พูดอะไรเกี่ยวกับไวยากรณ์ของการแสดงออกปกติ
Carlos Carlos

20
FYI ตัว=~ดำเนินการBash จะทำการจับคู่นิพจน์ปกติเท่านั้นเมื่อด้านขวามือถูกตัดออก หากคุณอ้างถึงด้านขวามือ "ส่วนใดส่วนหนึ่งของรูปแบบอาจถูกยกมาเพื่อบังคับให้จับคู่เป็นสตริง" (1. ) ตรวจสอบให้แน่ใจว่าใส่นิพจน์ปกติทางด้านขวาที่ไม่ได้ยกมาและ (2. ) ถ้าคุณเก็บนิพจน์ปกติของคุณในตัวแปรตรวจสอบให้แน่ใจว่าไม่ได้อ้างอิงทางด้านขวามือเมื่อคุณขยายพารามิเตอร์
เทรเวอร์บอยด์สมิ ธ

145

ฉันมักจะพยายามติดกับ POSIX shแทนที่จะใช้ส่วนขยายของ Bash เนื่องจากหนึ่งในประเด็นสำคัญของการเขียนสคริปต์ก็คือพกพาได้ (นอกเหนือจากการเชื่อมต่อโปรแกรมไม่ใช่การแทนที่)

ในshมีวิธีง่าย ๆ ในการตรวจสอบเงื่อนไข "is-prefix"

case $HOST in node*)
    # Your code here
esac

เมื่อพิจารณาถึงอายุความลับและ crufty sh (และ Bash ไม่ใช่วิธีแก้ปัญหา: มันซับซ้อนกว่ามีความสอดคล้องน้อยกว่าและพกพาได้น้อยกว่า) ฉันอยากจะชี้ให้เห็นถึงหน้าที่การใช้งานที่ดีมาก: ในขณะที่องค์ประกอบไวยากรณ์เช่นcaseในตัว การสร้างผลลัพธ์ไม่แตกต่างจากงานอื่น ๆ พวกเขาสามารถแต่งในลักษณะเดียวกัน:

if case $HOST in node*) true;; *) false;; esac; then
    # Your code here
fi

หรือแม้แต่สั้น

if case $HOST in node*) ;; *) false;; esac; then
    # Your code here
fi

หรือแม้แต่สั้นกว่า (เพียงเพื่อนำเสนอ!เป็นองค์ประกอบภาษา - แต่นี่เป็นสไตล์ที่ไม่ดีตอนนี้)

if ! case $HOST in node*) false;; esac; then
    # Your code here
fi

ถ้าคุณชอบที่ชัดเจนสร้างองค์ประกอบภาษาของคุณเอง:

beginswith() { case $2 in "$1"*) true;; *) false;; esac; }

อันนี้มันไม่ดีจริงเหรอ?

if beginswith node "$HOST"; then
    # Your code here
fi

และเนื่องจากshเป็นเพียงงานและรายการสตริง (และกระบวนการภายใน, ซึ่งงานจะถูกประกอบขึ้น), ตอนนี้เราสามารถทำการเขียนโปรแกรมที่ใช้งานได้บางอย่าง:

beginswith() { case $2 in "$1"*) true;; *) false;; esac; }
checkresult() { if [ $? = 0 ]; then echo TRUE; else echo FALSE; fi; }

all() {
    test=$1; shift
    for i in "$@"; do
        $test "$i" || return
    done
}

all "beginswith x" x xy xyz ; checkresult  # Prints TRUE
all "beginswith x" x xy abc ; checkresult  # Prints FALSE

นี่คือสง่างาม ไม่ใช่ว่าฉันจะสนับสนุนให้ใช้shสิ่งใดที่ร้ายแรง - มันทำลายทุกอย่างเร็วเกินไปตามข้อกำหนดของโลกแห่งความเป็นจริง (ไม่มี lambdas ดังนั้นเราต้องใช้สตริง แต่ฟังก์ชั่นการซ้อนกับสายไม่สามารถทำได้ท่อไม่สามารถทำได้ ฯลฯ )


12
+1 ไม่เพียงพกพาได้ แต่ยังสามารถอ่านได้สำนวนและสง่างาม (สำหรับเชลล์สคริปต์) มันยังขยายเป็นธรรมชาติไปยังหลายรูปแบบ case $HOST in user01 | node* ) ...
tripleee

มีชื่อสำหรับการจัดรูปแบบรหัสนี้หรือไม่? if case $HOST in node*) true;; *) false;; esac; then ฉันเคยเห็นมันที่นี่และที่นั่นตาของฉันมันดูเคอะเขินขึ้น
Niels Bom

@NielsBom ผมไม่ทราบว่าสิ่งที่คุณหมายถึงการจัดรูปแบบ แต่จุดของฉันคือรหัสของเชลล์ที่เป็นอย่างมากcomposable caseคำสั่งBecaues เป็นคำสั่งพวกเขาสามารถเข้าไปข้างในif ... thenได้
Jo So

ฉันไม่เห็นด้วยซ้ำว่าทำไมมันถึงคอมโพสิตฉันไม่เข้าใจเชลล์สคริปต์เพียงพอสำหรับ :-) คำถามของฉันคือว่าโค้ดนี้ใช้วงเล็บที่ไม่ตรงกันและเซมิโคลอนคู่อย่างไร มันดูไม่เหมือนเชลล์สคริปต์ที่ฉันเคยเห็นมาก่อน แต่ฉันอาจเคยเห็นสคริปต์ทุบตีมากกว่าสคริปต์ดวลจุดโทษ
Niels Bom

หมายเหตุ: ควรเป็นbeginswith() { case "$2" in "$1"*) true;; *) false;; esac; }อย่างอื่นหาก$1มีตัวอักษร*หรือ?อาจให้คำตอบที่ผิด
LLFourn

80

คุณสามารถเลือกเฉพาะส่วนของสตริงที่คุณต้องการตรวจสอบ:

if [ "${HOST:0:4}" = user ]

สำหรับคำถามการติดตามของคุณคุณสามารถใช้OR :

if [[ "$HOST" == user1 || "$HOST" == node* ]]

8
คุณควรเพิ่มเป็นสองเท่า${HOST:0:4}
Jo So

@Jo ดังนั้น: เหตุผลคืออะไร?
Peter Mortensen

@PeterMortensen ลองHOST='a b'; if [ ${HOST:0:4} = user ] ; then echo YES ; fi
Jo So

60

ฉันชอบวิธีการอื่นที่โพสต์ไปแล้ว แต่บางคนชอบที่จะใช้:

case "$HOST" in 
    user1|node*) 
            echo "yes";;
        *)
            echo "no";;
esac

แก้ไข:

ฉันได้เพิ่มทางเลือกของคุณในคำสั่งกรณีด้านบน

ในเวอร์ชั่นที่คุณแก้ไขคุณมีวงเล็บมากเกินไป ควรมีลักษณะเช่นนี้:

if [[ $HOST == user1 || $HOST == node* ]];

ขอบคุณเดนนิส! ฉันเพิ่มไปยังโพสต์ต้นฉบับคำถามใหม่เกี่ยวกับวิธีการรวมการแสดงออกในถ้า cluase
ทิม

11
"บางคนชอบ ... ": อันนี้พกพาสะดวกกว่าทั้งเวอร์ชั่นและเชลล์
carlosayam

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

และในกรณีของฉันฉันต้องทิ้งคำพูดก่อนหน้านี้): "/ *") ไม่ได้ทำงาน / *) ทำ (ฉันกำลังมองหาสตริงที่ขึ้นต้นด้วย /, คือเส้นทางที่แน่นอน)
Josiah Yoder

36

แม้ว่าฉันจะพบคำตอบส่วนใหญ่ที่นี่ค่อนข้างถูกต้อง แต่หลายคนก็มี Bashisms ที่ไม่จำเป็น การขยายพารามิเตอร์ POSIXให้ทุกสิ่งที่คุณต้องการ:

[ "${host#user}" != "${host}" ]

และ

[ "${host#node}" != "${host}" ]

${var#expr}แถบคำนำหน้าที่เล็กที่สุดที่ตรงกันexprจาก${var}และคืนค่านั้น ดังนั้นหาก${host}ไม่ได้ขึ้นต้นด้วยuser( node)${host#user} ( ${host#node}) ${host}เป็นเช่นเดียวกับ

exprอนุญาตให้ใช้fnmatch()สัญลักษณ์แทนดังนั้น${host#node??}เพื่อน ๆ ก็สามารถใช้งานได้


2
ผมขอยืนยันว่า bashism อาจจะจำเป็นเพราะมันไกลสามารถอ่านได้มากกว่า[[ $host == user* ]] [ "${host#user}" != "${host}" ]เมื่อให้สิทธิ์แก่คุณในการควบคุมสภาพแวดล้อมที่สคริปต์ถูกเรียกใช้งาน (กำหนดเป้าหมายเวอร์ชันล่าสุดของbash) สคริปต์รุ่นก่อนหน้าจะเหมาะสมกว่า
x-yuri

2
@ x-yuri ตรงไปตรงมาฉันแค่เอามันใส่เข้าไปในhas_prefix()ฟังก์ชั่นและอย่ามองมันอีกเลย
dhke

30

ตั้งแต่ #มีความหมายใน Bash ฉันจึงได้แนวทางต่อไปนี้

นอกจากนี้ฉันชอบดีกว่าที่จะแพ็คสตริงด้วย "" เพื่อเอาชนะช่องว่าง ฯลฯ

A="#sdfs"
if [[ "$A" == "#"* ]];then
    echo "Skip comment line"
fi

นี่คือสิ่งที่ฉันต้องการ ขอบคุณ!
IonicăBizău

ขอบคุณฉันยังสงสัยวิธีจับคู่สตริงที่เริ่มต้นด้วยblah:ดูเหมือนว่านี่คือคำตอบ!
Anentropic

12

การเพิ่มรายละเอียดไวยากรณ์เพิ่มเติมอีกเล็กน้อยในคำตอบอันดับสูงสุดของ Mark Rushakoff

การแสดงออก

$HOST == node*

สามารถเขียนเป็น

$HOST == "node"*

ผลจะเหมือนกัน เพียงตรวจสอบให้แน่ใจว่าสัญลักษณ์แทนอยู่นอกข้อความที่ยกมา หากสัญลักษณ์ตัวแทนอยู่ในเครื่องหมายคำพูดจะถูกตีความอย่างแท้จริง (เช่นไม่ใช่สัญลักษณ์แทน)


8

@OP สำหรับทั้งคำถามของคุณคุณสามารถใช้ case / esac:

string="node001"
case "$string" in
  node*) echo "found";;
  * ) echo "no node";;
esac

คำถามที่สอง

case "$HOST" in
 node*) echo "ok";;
 user) echo "ok";;
esac

case "$HOST" in
 node*|user) echo "ok";;
esac

หรือ Bash 4.0

case "$HOST" in
 user) ;&
 node*) echo "ok";;
esac


6
if [ [[ $HOST == user1 ]] -o [[ $HOST == node* ]] ];
then
echo yes
fi

ไม่ทำงานเพราะทุก[,[[และtestรับรู้ไวยากรณ์ nonrecursive เดียวกัน ดูการแสดงออกตามเงื่อนไขในหน้า Bash man ของคุณ

นอกเหนือจากนี้แล้ว SUSv3 ยังบอกอีกว่า

คำสั่งเงื่อนไขที่ได้รับจาก KornShell (วงเล็บเหลี่ยมคู่[[]] ) ถูกลบออกจากคำอธิบายภาษาคำสั่งเชลล์ในข้อเสนอเริ่มต้น คัดค้านว่าปัญหาที่แท้จริงนั้นเกิดจากการใช้คำสั่งทดสอบในทางที่ผิด( [ ) ในทางที่ผิดและการวางลงในเชลล์นั้นเป็นวิธีที่ผิดในการแก้ไขปัญหา ควรใช้เอกสารที่เหมาะสมและคำสงวนเปลือกใหม่แทน! ) นั้นเพียงพอแล้ว

การทดสอบที่ต้องการการดำเนินการทดสอบหลายรายการสามารถทำได้ที่ระดับเชลล์โดยใช้การเรียกใช้คำสั่งการทดสอบและโลจิคัลเชลล์แต่ละรายการแทนที่จะใช้แฟล็ก -o-ข้อผิดพลาดในการทดสอบข้อผิดพลาดการทดสอบ

คุณต้องเขียนด้วยวิธีนี้ แต่การทดสอบไม่รองรับ:

if [ $HOST == user1 -o $HOST == node* ];
then
echo yes
fi

ทดสอบใช้=เพื่อความเท่าเทียมกันของสตริงและที่สำคัญกว่านั้นไม่รองรับการจับคู่รูปแบบ

case/ esacมีการสนับสนุนที่ดีสำหรับการจับคู่รูปแบบ:

case $HOST in
user1|node*) echo yes ;;
esac

มันมีประโยชน์เพิ่มเติมที่ไม่ได้ขึ้นอยู่กับ Bash และไวยากรณ์เป็นแบบพกพา จากสเปก Unix เดี่ยว , เชลล์คำสั่งภาษา :

case word in
    [(]pattern1) compound-list;;
    [[(]pattern[ | pattern] ... ) compound-list;;] ...
    [[(]pattern[ | pattern] ... ) compound-list]
esac

1
[และtestเป็น Bash builtins รวมถึงโปรแกรมภายนอก ลองtype -a [ดู
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

ขอบคุณมากสำหรับการอธิบายปัญหาเกี่ยวกับ "สารประกอบหรือ" @ ใครบางคน - กำลังมองหาบางอย่างเช่นนั้นอย่างแม่นยำ! ไชโย! PS บันทึก (ไม่เกี่ยวข้องกับ OP) if [ -z $aa -or -z $bb ]; ... ให้ " bash: [: - หรือ: คาดว่าผู้ประกอบการแบบไบนารี "; แต่if [ -z "$aa" -o -z "$bb" ] ; ...ผ่านไป
sdaau

2

grep

ลืมประสิทธิภาพการทำงานนี่คือ POSIX และดูดีกว่าcaseโซลูชัน:

mystr="abcd"
if printf '%s' "$mystr" | grep -Eq '^ab'; then
  echo matches
fi

คำอธิบาย:


2

ฉัน tweaked @ markrushakoff คำตอบเพื่อให้มันเป็นฟังก์ชั่น callable:

function yesNo {
  # Prompts user with $1, returns true if response starts with y or Y or is empty string
  read -e -p "
$1 [Y/n] " YN

  [[ "$YN" == y* || "$YN" == Y* || "$YN" == "" ]]
}

ใช้แบบนี้:

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] y
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] Y
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] yes
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n]
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] n
false

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] ddddd
false

นี่คือเวอร์ชันที่ซับซ้อนมากขึ้นที่ให้ค่าเริ่มต้นที่ระบุ:

function toLowerCase {
  echo "$1" | tr '[:upper:]' '[:lower:]'
}

function yesNo {
  # $1: user prompt
  # $2: default value (assumed to be Y if not specified)
  # Prompts user with $1, using default value of $2, returns true if response starts with y or Y or is empty string

  local DEFAULT=yes
  if [ "$2" ]; then local DEFAULT="$( toLowerCase "$2" )"; fi
  if [[ "$DEFAULT" == y* ]]; then
    local PROMPT="[Y/n]"
  else
    local PROMPT="[y/N]"
  fi
  read -e -p "
$1 $PROMPT " YN

  YN="$( toLowerCase "$YN" )"
  { [ "$YN" == "" ] && [[ "$PROMPT" = *Y* ]]; } || [[ "$YN" = y* ]]
}

ใช้แบบนี้:

$ if yesNo "asfd" n; then echo "true"; else echo "false"; fi

asfd [y/N]
false

$ if yesNo "asfd" n; then echo "true"; else echo "false"; fi

asfd [y/N] y
true

$ if yesNo "asfd" y; then echo "true"; else echo "false"; fi

asfd [Y/n] n
false

-5

อีกสิ่งที่คุณสามารถทำได้คือcatสิ่งที่คุณก้องและท่อด้วยinline cut -c 1-1

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