ทำไมการขยายพารามิเตอร์ด้วยช่องว่างโดยไม่ใส่เครื่องหมายอัญประกาศทำงานในวงเล็บสอง“ [[” แต่ไม่อยู่ในเครื่องหมายวงเล็บ”“”


85

ฉันสับสนกับการใช้วงเล็บเดียวหรือสองครั้ง ดูรหัสนี้:

dir="/home/mazimi/VirtualBox VMs"

if [[ -d ${dir} ]]; then
    echo "yep"
fi

มันทำงานได้อย่างสมบูรณ์แม้ว่าสตริงจะมีช่องว่าง แต่เมื่อฉันเปลี่ยนเป็นวงเล็บเดี่ยว:

dir="/home/mazimi/VirtualBox VMs"

if [ -d ${dir} ]; then
    echo "yep"
fi

มันบอกว่า:

./script.sh: line 5: [: /home/mazimi/VirtualBox: binary operator expected

เมื่อฉันเปลี่ยนเป็น:

dir="/home/mazimi/VirtualBox VMs"

if [ -d "${dir}" ]; then
    echo "yep"
fi

มันใช้งานได้ดี ใครสามารถอธิบายสิ่งที่เกิดขึ้น? เมื่อใดที่ฉันควรกำหนดเครื่องหมายคำพูดคู่ล้อมรอบตัวแปร"${var}"เพื่อป้องกันปัญหาที่เกิดจากช่องว่าง



คำถามทั่วไปเพิ่มเติมเกี่ยวกับ: unix.stackexchange.com/questions/306111/…
Ciro Santilli 改造改造中心中心法轮功六四事件

คำตอบ:


83

วงเล็บเดี่ยว[เป็นจริงนามแฝงสำหรับtestคำสั่งไม่ใช่ไวยากรณ์

หนึ่งในข้อเสีย (ของหลาย ๆ ) ของวงเล็บเดียวคือถ้าตัวถูกดำเนินการหนึ่งตัวหรือมากกว่านั้นกำลังพยายามประเมินคืนสตริงว่างเปล่ามันจะบ่นว่ามันถูกคาดหวังว่าตัวถูกดำเนินการสองตัว (ไบนารี) นี่คือเหตุผลที่คุณเห็นคนทำ[ x$foo = x$blah ]ที่xรับประกันว่าถูกดำเนินการจะไม่ประเมินเป็นสตริงว่าง

วงเล็บคู่[[ ]]บนมืออื่น ๆ ที่เป็นไวยากรณ์[ ]และเป็นมากขึ้นกว่าที่มีความสามารถ ตามที่คุณค้นพบมันไม่มีปัญหาตัวถูกดำเนินการเดี่ยวและยังอนุญาตให้ใช้ไวยากรณ์ที่คล้ายกับ C เพิ่มเติมกับ>, <, >=, <=, !=, ==, &&, ||ตัวดำเนินการ

คำแนะนำของฉันคือต่อไปนี้: หากล่ามของคุณอยู่#!/bin/bashให้ใช้เสมอ[[ ]]

เป็นสิ่งสำคัญที่จะต้องทราบว่า[[ ]]เชลล์ POSIX ทั้งหมดไม่รองรับ แต่เชลล์จำนวนมากสนับสนุนเช่นzshและkshนอกเหนือจากbash


16
ปัญหาสตริงว่าง (และอื่น ๆ อีกมากมาย) ได้รับการแก้ไขโดยใช้เครื่องหมายคำพูด "x" จะครอบคลุมชนิดของปัญหาอื่น: ที่ถูกดำเนินการอาจจะถูกนำมาเป็นผู้ประกอบการ เช่นเมื่อ$fooเป็น!หรือ(หรือ-n... ปัญหาที่ไม่ได้หมายถึงจะมีปัญหาด้วยเปลือกหอย POSIX ซึ่งมีจำนวนของการขัดแย้ง (ข้าง[และ]) ไม่เกินสี่
Stéphane Chazelas

21
ชี้แจงเป็นเพียงธรรมดาผิดเป็น[ x$foo = x$blah ] ถูกต้องในเชลล์ที่เข้ากันได้กับ POSIX ใด ๆจะทำงานในเชลล์คล้ายบอร์นใด ๆ แต่ไม่ใช่สำหรับกรณีที่คุณมีสตริงว่าง แต่ที่อาจจะหรือ...[ $foo = $bar ][ "$foo" = "$bar" ][ "x$foo" = "x$bar" ]x$foo!-n
Stéphane Chazelas

1
ความหมายที่น่าสนใจในคำตอบนี้ ผมไม่แน่ใจว่าสิ่งที่คุณหมายถึงมันไม่ได้ * * * * * * * * ไวยากรณ์ แน่นอนไม่ได้อย่างแม่นยำนามแฝงสำหรับ[ testถ้าเป็นเช่นนั้นก็testจะยอมรับวงเล็บเหลี่ยมปิด test -n foo ]. แต่testไม่ได้และ[ต้องใช้อย่างใดอย่างหนึ่ง [เหมือนกันtestทุกประการ แต่นั่นไม่ใช่วิธีการaliasทำงาน ทุบตีอธิบาย[และtestเป็นbuiltins เปลือกแต่[[เป็นคำหลักเปลือก
kojiro

1
ใน bash [เป็นเชลล์ในตัว แต่ / usr / bin / [ก็ยังสามารถเรียกใช้งานได้ มันเป็นลิงค์ไปยัง / usr / bin / test ตามธรรมเนียม แต่ใน gnu coreutils ที่ทันสมัยเป็นไบนารีแยกต่างหาก การทดสอบเวอร์ชันดั้งเดิมจะตรวจสอบ argv [0] เพื่อดูว่ามีการเรียกใช้เป็น [จากนั้นค้นหาการจับคู่]
Evan

ทุบตีของฉันไม่ทำงานกับ>=และ<=
นักเรียน

51

[คำสั่งเป็นคำสั่งธรรมดา แม้ว่าเชลล์ส่วนใหญ่จะให้มันเป็นแบบในตัวเพื่อประสิทธิภาพ แต่มันก็เป็นไปตามกฎวากยสัมพันธ์ตามปกติของเชลล์ [เทียบเท่ากับtestยกเว้นว่า[ต้องใช้]เป็นอาร์กิวเมนต์สุดท้ายและtestไม่

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

[[ $x = foo && $y = bar ]]

เนื่องจากนิพจน์เงื่อนไขทั้งหมดถูกวิเคราะห์คำโดยเชลล์ในขณะที่[ $x = foo && $y = bar ]ก่อนจะแบ่งออกเป็นสองคำสั่ง[ $x = fooและ$y = bar ]คั่นด้วยตัว&&ดำเนินการ วงเล็บคู่ที่คล้ายกันเปิดใช้งานสิ่งต่าง ๆ เช่นไวยากรณ์การจับคู่รูปแบบเช่น[[ $x == a* ]]เพื่อทดสอบว่าค่าxเริ่มต้นด้วยa; ในวงเล็บเดียวสิ่งนี้จะขยายa*ไปยังรายการไฟล์ที่ชื่อขึ้นต้นด้วยaในไดเรกทอรีปัจจุบัน วงเล็บคู่ถูกนำมาใช้ครั้งแรกใน ksh และมีให้เฉพาะใน ksh, bash และ zsh

ภายในวงเล็บเหลี่ยมคุณต้องใช้เครื่องหมายคำพูดคู่รอบ ๆ การแทนที่ตัวแปรเช่นในที่อื่น ๆ ส่วนใหญ่เพราะมันเป็นเพียงการขัดแย้งกับคำสั่ง (ซึ่งเกิดขึ้นเป็น[คำสั่ง) ภายในวงเล็บเหลี่ยมคุณไม่จำเป็นต้องมีเครื่องหมายคำพูดคู่เนื่องจากเชลล์ไม่ได้ทำการแบ่งคำหรือกลม: มันแยกวิเคราะห์การแสดงออกตามเงื่อนไขไม่ใช่คำสั่ง

มีข้อยกเว้นว่าเป็น[[ $var1 = "$var2" ]]ที่ที่คุณต้องการคำพูดหากคุณต้องการที่จะทำเปรียบเทียบสตริงไบต์เพื่อไบต์มิฉะนั้น$var2จะเป็นรูปแบบเพื่อให้ตรงกับ$var1กับ

สิ่งหนึ่งที่คุณไม่สามารถทำได้[[ … ]]คือใช้ตัวแปรเป็นตัวดำเนินการ ตัวอย่างเช่นนี่ถูกกฎหมายอย่างสมบูรณ์ (แต่ไม่ค่อยมีประโยชน์):

if [ -n "$reverse_sort" ]; then op=-gt; else op=-lt; fi

if [ "$x" "$op" "$y" ]; then 

ในตัวอย่างของคุณ

dir="/home/mazimi/VirtualBox VMs"
if [ -d ${dir} ]; then 

คำสั่งภายในifคือ[มี 4 ข้อโต้แย้ง-d, /home/mazimi/VirtualBox, และVMs ]เปลือกแยกวิเคราะห์แล้วไม่ทราบว่าจะทำอย่างไรกับ-d /home/mazimi/VirtualBox VMsคุณจะต้องป้องกันการแยกคำ${dir}เพื่อให้ได้คำสั่งที่มีรูปแบบที่ถูกต้อง

โดยทั่วไปแล้วให้ใช้เครื่องหมายอัญประกาศคู่ล้อมรอบตัวแปรและคำสั่งแทนเว้นแต่คุณจะรู้ว่าคุณต้องการแยกคำและกลมกลืนกับผลลัพธ์ สถานที่หลักที่ปลอดภัยที่จะไม่ใช้เครื่องหมายคำพูดคู่คือ:

  • ในการมอบหมาย: foo=$bar(แต่โปรดทราบว่าคุณจำเป็นต้องมีเครื่องหมายอัญประกาศคู่ในexport "foo=$bar"หรือในการกำหนดอาร์เรย์เช่นarray=("$a" "$b"))
  • ในcaseคำสั่ง: case $foo in …;
  • ในวงเล็บคู่ยกเว้นทางด้านขวามือของคน=หรือ==ผู้ประกอบการ [[ $x = "$y" ]](ยกเว้นกรณีที่คุณทำต้องการจับคู่รูปแบบ):

ในทั้งหมดนี้ถูกต้องเพื่อใช้เครื่องหมายคำพูดคู่ดังนั้นคุณอาจข้ามกฎขั้นสูงและใช้เครื่องหมายคำพูดตลอดเวลา


1
@StephaneChazelas ความผิดพลาดของฉัน ปรากฎว่า[มาจาก System III ในปี 1981ฉันคิดว่ามันเก่ากว่า
Gilles

8

เมื่อใดที่ฉันควรกำหนดเครื่องหมายคำพูดคู่ล้อมรอบตัวแปร"${var}"เพื่อป้องกันปัญหาที่เกิดจากช่องว่าง

โดยนัยในคำถามนี้คือ

ทำไมไม่ดีพอ${variable_name}

${variable_name} ไม่ได้หมายความว่าคุณคิดว่าจะทำ ...

... ถ้าคุณคิดว่ามันมีอะไรจะทำอย่างไรกับปัญหาที่เกิดจากช่องว่าง (ในค่าตัวแปร) ดีสำหรับสิ่งนี้:${variable_name}

$ bar=foo
$ bard=Shakespeare
$ echo $bard
Shakespeare
$ echo ${bar}d
food

และไม่มีอะไรอื่น! 1  ไม่ได้ทำอะไรที่ดีเว้นแต่คุณจะติดตามด้วยอักขระที่อาจเป็นส่วนหนึ่งของชื่อตัวแปรทันที: ตัวอักษร (-หรือ-), ขีดล่าง () หรือตัวเลข (-) และถึงตอนนั้นคุณสามารถหลีกเลี่ยงได้:${variable_name}AZaz_09

$ echo "$bar"d
food

ฉันไม่ได้พยายามที่จะกีดกันการใช้งาน - echo "${bar}d"น่าจะเป็นทางออกที่ดีที่สุดที่นี่ - แต่จะกีดกันคนจากการพึ่งพิงการจัดฟันแทนคำพูดหรือการใช้เครื่องหมายวงเล็บสัญชาตญาณแล้วถามว่า“ตอนนี้ฉันจะต้องคำพูดยัง? “  คุณควรใช้เครื่องหมายคำพูดเสมอเว้นเสียแต่ว่าคุณจะมีเหตุผลที่ดีไม่ควรและคุณแน่ใจว่าคุณรู้ว่าคุณกำลังทำอะไรอยู่
_________________
1    ยกเว้นแน่นอนสำหรับความจริงที่ว่ารูปแบบของการขยายพารามิเตอร์เช่น นักเล่นและ สร้างในไวยากรณ์ นอกจากนี้คุณจำเป็นต้องใช้, และอื่น ๆ เพื่ออ้างอิงที่ 10, 11 ฯลฯ พารามิเตอร์ตำแหน่ง - การคำพูดจะไม่ช่วยให้คุณกับที่${parameter:-[word]}${parameter%[word]}${parameter}${10}${11}


2

สำหรับการจัดการช่องว่างและอักขระว่าง | พิเศษในตัวแปรคุณควรล้อมรอบด้วยเครื่องหมายคำพูดคู่ การตั้งค่า IFS ที่เหมาะสมก็เป็นแนวทางปฏิบัติที่ดีเช่นกัน

แนะนำ: http://www.dwheeler.com/essays/filenames-in-shell.html

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