อะไรคือความแตกต่างระหว่าง $ {var},“ $ var” และ“ $ {var}” ใน Bash shell?


135

อะไรชื่อกล่าวว่า: สิ่งที่ไม่ได้หมายถึงการแค็ปซูลตัวแปรใน{}, ""หรือ"{}?" ผมยังไม่ได้รับสามารถที่จะหาคำอธิบายใด ๆ ออนไลน์เกี่ยวกับเรื่องนี้ - ฉันไม่ได้รับสามารถที่จะหมายถึงพวกเขายกเว้นสำหรับการใช้สัญลักษณ์ซึ่ง ไม่ให้ผลอะไรเลย

นี่คือตัวอย่าง:

declare -a groups

groups+=("CN=exampleexample,OU=exampleexample,OU=exampleexample,DC=example,DC=com")
groups+=("CN=example example,OU=example example,OU=example example,DC=example,DC=com")

นี้:

for group in "${groups[@]}"; do
    echo $group
done

พิสูจน์ได้ว่าแตกต่างจากนี้มาก:

for group in $groups; do
    echo $group
done

และนี่:

for group in ${groups}; do
    echo $group
done

สิ่งแรกเท่านั้นที่บรรลุสิ่งที่ฉันต้องการ: เพื่อวนซ้ำผ่านแต่ละองค์ประกอบในอาร์เรย์ ฉันไม่ได้จริงๆที่ชัดเจนเกี่ยวกับความแตกต่างระหว่าง$groups, "$groups", และ${groups} "${groups}"ถ้าใครสามารถอธิบายได้ฉันจะขอบคุณมัน

เป็นคำถามเพิ่มเติม - มีใครทราบวิธีที่ยอมรับในการอ้างถึงสิ่งห่อหุ้มเหล่านี้หรือไม่?


คำตอบ:


229

วงเล็บปีกกา ( $varเทียบกับ${var})

ในกรณีส่วนใหญ่$varและ${var}เหมือนกัน:

var=foo
echo $var
# foo
echo ${var}
# foo

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

var=foo
echo $varbar
# Prints nothing because there is no variable 'varbar'
echo ${var}bar
# foobar

คำพูด ( $varเทียบ"$var"กับ"${var}" )

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

var="foo bar"
for i in "$var"; do # Expands to 'for i in "foo bar"; do...'
    echo $i         #   so only runs the loop once
done
# foo bar

ตรงกันข้ามกับพฤติกรรมดังต่อไปนี้:

var="foo bar"
for i in $var; do # Expands to 'for i in foo bar; do...'
    echo $i       #   so runs the loop twice, once for each argument
done
# foo
# bar

เช่นเดียวกับ$varกับ${var}วงเล็บปีกกานั้นจำเป็นสำหรับการลดความสับสนเท่านั้นเช่น:

var="foo bar"
for i in "$varbar"; do # Expands to 'for i in ""; do...' since there is no
    echo $i            #   variable named 'varbar', so loop runs once and
done                   #   prints nothing (actually "")

var="foo bar"
for i in "${var}bar"; do # Expands to 'for i in "foo barbar"; do...'
    echo $i              #   so runs the loop once
done
# foo barbar

โปรดทราบว่า"${var}bar"ในตัวอย่างที่สองข้างต้นสามารถเขียนได้"${var}"barซึ่งในกรณีนี้คุณไม่จำเป็นต้องใช้เครื่องมือจัดฟันอีกต่อไปเช่น"$var"barซึ่งในกรณีที่คุณไม่จำเป็นต้องจัดฟันอีกต่อไปคืออย่างไรก็ตามหากคุณมีเครื่องหมายคำพูดจำนวนมากในสตริงของคุณรูปแบบทางเลือกเหล่านี้อาจอ่านยาก (และยากที่จะดูแลรักษา) หน้านี้ให้ข้อมูลเบื้องต้นที่ดีเกี่ยวกับการอ้างอิงใน Bash

อาร์เรย์ ($varเทียบ$var[@]กับ${var[@]})

ตอนนี้สำหรับอาร์เรย์ของคุณ ให้เป็นไปตามคู่มือทุบตี :

การอ้างอิงตัวแปรอาร์เรย์โดยไม่มีตัวห้อยจะเทียบเท่ากับการอ้างอิงอาร์เรย์ด้วยตัวห้อยเป็น 0

กล่าวอีกนัยหนึ่งถ้าคุณไม่ได้ใส่ดัชนี[]คุณจะได้รับองค์ประกอบแรกของอาร์เรย์:

foo=(a b c)
echo $foo
# a

ซึ่งก็เหมือนกับ

foo=(a b c)
echo ${foo}
# a

เพื่อให้ได้องค์ประกอบทั้งหมดของอาร์เรย์คุณต้องใช้เป็นดัชนีเช่น@ ${foo[@]}ต้องใช้วงเล็บปีกกากับอาร์เรย์เพราะหากไม่มีพวกเขาเชลล์จะขยาย$fooส่วนก่อนโดยให้องค์ประกอบแรกของอาร์เรย์ตามด้วยลิเทอรัล[@]:

foo=(a b c)
echo ${foo[@]}
# a b c
echo $foo[@]
# a[@]

หน้านี้เป็นข้อมูลเบื้องต้นที่ดีเกี่ยวกับอาร์เรย์ใน Bash

อ้างถึงคำพูด (${foo[@]}เทียบกับ"${foo[@]}" )

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

foo=("the first" "the second")
for i in "${foo[@]}"; do # Expands to 'for i in "the first" "the second"; do...'
    echo $i              #   so the loop runs twice
done
# the first
# the second

ตรงกันข้ามกับพฤติกรรมที่ไม่มีเครื่องหมายอัญประกาศคู่:

foo=("the first" "the second")
for i in ${foo[@]}; do # Expands to 'for i in the first the second; do...'
    echo $i            #   so the loop runs four times!
done
# the
# first
# the
# second

3
มีอีกกรณีหนึ่ง: ${var:?}ซึ่งจะแสดงข้อผิดพลาดเมื่อไม่ได้ตั้งค่าตัวแปรหรือไม่ได้ตั้งค่า REF: github.com/koalaman/shellcheck/wiki/SC2154
Nam Nguyen

4
@NamNguyen หากคุณต้องการที่จะพูดคุยเกี่ยวกับรูปแบบอื่น ๆ ของการขยายตัวพารามิเตอร์มีอย่างน้อยโหลเพิ่มเติมได้ที่: ${parameter:-word}, ${parameter:=word}, ${parameter#word}, ${parameter/pattern/string}และอื่น ๆ ฉันคิดว่าสิ่งเหล่านี้อยู่นอกเหนือขอบเขตของคำตอบนี้
ThisSuitIsBlackNot

อันที่จริงการสนทนาของเครื่องหมายคำพูดคู่เป็นเรื่องที่ไม่สมบูรณ์ ดูเพิ่มเติมstackoverflow.com/questions/10067266/…
tripleee

11

TL; DR

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

วงเล็บเป็นตัวคั่นโทเค็น

${var}ไวยากรณ์ถูกนำมาใช้เป็นหลักสำหรับ delimiting ราชสกุลคลุมเครือ ตัวอย่างเช่นพิจารณาสิ่งต่อไปนี้:

$ var1=foo; var2=bar; var12=12
$ echo $var12
12
$ echo ${var1}2
foo2

วงเล็บปีกกาในการขยายอาร์เรย์

วงเล็บจำเป็นในการเข้าถึงองค์ประกอบของอาร์เรย์และอื่น ๆขยายพิเศษ ตัวอย่างเช่น:

$ foo=(1 2 3)

# Returns first element only.
$ echo $foo
1

# Returns all array elements.
$ echo ${foo[*]}
1 2 3

# Returns number of elements in array.
$ echo ${#foo[*]}
3

tokenization

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

$ var1=foo; var2=bar; count_params () { echo $#; }

# Variables are interpolated into a single string.
$ count_params "$var1 $var2"
1

# Each variable is quoted separately, created two arguments.
$ count_params "$var1" "$var2"
2

ปฏิสัมพันธ์สัญลักษณ์มีข้อความที่แตกต่างกว่า@ *โดยเฉพาะ:

  1. $@ "[e] xpands ไปยังพารามิเตอร์ตำแหน่งโดยเริ่มจากหนึ่งเมื่อการขยายเกิดขึ้นภายในเครื่องหมายคำพูดคู่พารามิเตอร์แต่ละตัวจะขยายเป็นคำแยกกัน"
  2. ในอาร์เรย์ "[i] f คำนั้นยกมาสองคำ${name[*]}ขยายเป็นคำเดียวโดยให้ค่าของสมาชิกอาร์เรย์แต่ละตัวคั่นด้วยอักขระตัวแรกของตัวแปร IFS และ${name[@]}ขยายแต่ละองค์ประกอบของชื่อเป็นคำแยกกัน"

คุณสามารถเห็นสิ่งนี้ในการดำเนินการดังต่อไปนี้:

$ count_params () { echo $#; }
$ set -- foo bar baz 

$ count_params "$@"
3

$ count_params "$*"
1

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


7

คุณต้องแยกความแตกต่างระหว่างอาร์เรย์และตัวแปรอย่างง่าย - และตัวอย่างของคุณใช้อาร์เรย์

สำหรับตัวแปรธรรมดา:

  • $varและ${var}เทียบเท่ากันทุกประการ
  • "$var"และ"${var}"เทียบเท่ากันทุกประการ

อย่างไรก็ตามทั้งสองคู่ไม่เหมือนกัน 100% ในทุกกรณี พิจารณาผลลัพธ์ด้านล่าง:

$ var="  abc  def  "
$ printf "X%sX\n" $var
XabcX
XdefX
$ printf "X%sX\n" "${var}"
X  abc  def  X
$

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

ด้วยอาร์เรย์กฎมีทั้งที่เหมือนกันและแตกต่างกัน

  • ถ้าgroupsเป็นอาร์เรย์การอ้างอิง$groupsหรือ${groups}เทียบเท่ากับการอ้างอิง${groups[0]}องค์ประกอบ zeroth ของอาร์เรย์
  • อ้างอิง"${groups[@]}"จะคล้ายคลึงกับการอ้างอิง"$@"; จะรักษาระยะห่างในแต่ละองค์ประกอบของอาร์เรย์และส่งคืนรายการค่าหนึ่งค่าต่อองค์ประกอบของอาร์เรย์
  • การอ้างอิง${groups[@]}โดยไม่มีเครื่องหมายคำพูดคู่จะไม่รักษาระยะห่างและสามารถเพิ่มค่าได้มากกว่าที่มีองค์ประกอบในอาร์เรย์หากองค์ประกอบบางส่วนมีช่องว่าง

ตัวอย่างเช่น:

$ groups=("abc def" "  pqr  xyz  ")
$ printf "X%sX\n" ${groups[@]}
XabcX
XdefX
XpqrX
XxyzX
$ printf "X%sX\n" "${groups[@]}"
Xabc defX
X  pqr  xyz  X
$ printf "X%sX\n" $groups
XabcX
XdefX
$ printf "X%sX\n" "$groups"
Xabc defX
$

ใช้*แทนลูกค้าเป้าหมาย@เพื่อให้ได้ผลลัพธ์ที่แตกต่างกันอย่างละเอียด

ดูเพิ่มเติมวิธีการย้ำกว่าการขัดแย้งในbashสคริปต์


3

ประโยคที่สองของย่อหน้าแรกภายใต้การขยายพารามิเตอร์ในman bashพูดว่า

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

ซึ่งจะบอกคุณว่าชื่อนี้เป็นเพียงวงเล็บปีกกาและจุดประสงค์หลักคือเพื่อชี้แจงว่าชื่อเริ่มต้นและสิ้นสุดที่ใด:

foo='bar'
echo "$foobar"
# nothing
echo "${foo}bar"
barbar

หากคุณอ่านเพิ่มเติมคุณจะค้นพบ

ต้องใช้เครื่องหมายวงเล็บเมื่อพารามิเตอร์เป็นพารามิเตอร์ตำแหน่งที่มีตัวเลขมากกว่าหนึ่งหลัก ...

มาทดสอบกัน:

$ set -- {0..100}
$ echo $22
12
$ echo ${22}
20

ฮะ. เรียบร้อย ฉันไม่รู้จริงๆว่าก่อนที่จะเขียนสิ่งนี้ (ฉันไม่เคยมีพารามิเตอร์ตำแหน่งมากกว่า 9 ตำแหน่งมาก่อน)

แน่นอนคุณต้องมีวงเล็บปีกกาเพื่อใช้คุณสมบัติการขยายพารามิเตอร์ที่มีประสิทธิภาพเช่น

${parameter:-word}
${parameter:=word}
${parameter:?word}
 [read the section for more]

เช่นเดียวกับการขยายอาร์เรย์


3

กรณีที่เกี่ยวข้องไม่ได้กล่าวถึงข้างต้น test -nเธซเธฑตัวแปรที่ว่างเปล่าดูเหมือนว่าจะเปลี่ยนแปลงสิ่งที่ สิ่งนี้ได้รับโดยเฉพาะเป็นตัวอย่างในinfoข้อความสำหรับcoreutilsแต่ไม่ได้อธิบายอย่างแท้จริง:

16.3.4 String tests
-------------------

These options test string characteristics.  You may need to quote
STRING arguments for the shell.  For example:

     test -n "$V"

  The quotes here prevent the wrong arguments from being passed to
`test' if `$V' is empty or contains special characters.

ฉันชอบที่จะได้ยินคำอธิบายโดยละเอียด การทดสอบของฉันยืนยันสิ่งนี้และตอนนี้ฉันกำลังอ้างถึงตัวแปรของฉันสำหรับการทดสอบสตริงทั้งหมดเพื่อหลีกเลี่ยงการมี-zและ-nส่งคืนผลลัพธ์เดียวกัน

$ unset a
$ if [ -z $a ]; then echo unset; else echo set; fi
unset
$ if [ -n $a ]; then echo set; else echo unset; fi    
set                                                   # highly unexpected!

$ unset a
$ if [ -z "$a" ]; then echo unset; else echo set; fi
unset
$ if [ -n "$a" ]; then echo set; else echo unset; fi
unset                                                 # much better

2

ฉันรู้ว่าการห่อหุ้มตัวแปรช่วยให้คุณทำงานกับสิ่งต่างๆเช่น:

${groups%example}

หรือไวยากรณ์แบบนั้นที่คุณต้องการทำอะไรกับตัวแปรของคุณก่อนที่จะส่งคืนค่า

ตอนนี้ถ้าคุณเห็นรหัสของคุณเวทมนตร์ทั้งหมดอยู่ข้างใน

${groups[@]}

ความมหัศจรรย์อยู่ในนั้นเพราะคุณไม่สามารถเขียนได้: $groups[@]

คุณกำลังใส่ตัวแปรของคุณไว้ใน{}เพราะคุณต้องการใช้อักขระพิเศษ[]และ@. คุณไม่สามารถตั้งชื่อหรือเรียกตัวแปรของคุณได้: @หรือsomething[]เนื่องจากเป็นอักขระสงวนไว้สำหรับการดำเนินการและชื่ออื่น ๆ


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