Apa perbedaan antara $ {var}, "$ var", dan "$ {var}" di Bash shell?

134

Apa judulnya: apa artinya merangkum variabel dalam {},, ""atau "{}"? Saya belum bisa menemukan penjelasan daring tentang ini - saya belum bisa merujuknya kecuali menggunakan simbol, yang tidak menghasilkan apa-apa.

Ini sebuah contoh:

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")

Ini:

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

Terbukti jauh berbeda dari ini:

for group in $groups; do
    echo $group
done

dan ini:

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

Hanya yang pertama memenuhi apa yang saya inginkan: untuk mengulangi setiap elemen dalam array. Aku tidak benar-benar jelas tentang perbedaan antara $groups, "$groups", ${groups}dan "${groups}". Jika ada yang bisa menjelaskannya, saya akan sangat menghargainya.

Sebagai pertanyaan tambahan - apakah ada yang tahu cara yang diterima untuk merujuk pada enkapsulasi ini?

SheerSt
sumber

Jawaban:

228

Kawat gigi ( $varvs ${var})

Dalam kebanyakan kasus, $vardan ${var}sama:

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

Kawat gigi hanya diperlukan untuk menyelesaikan ambiguitas dalam ekspresi:

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

Kutipan ( $varvs "$var"vs. "${var}")

Saat Anda menambahkan tanda kutip ganda di sekitar variabel, Anda memberi tahu shell untuk memperlakukannya sebagai satu kata, bahkan jika itu berisi spasi putih:

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

Bandingkan perilaku itu dengan yang berikut:

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

Seperti halnya $varvs ${var}, kawat gigi hanya diperlukan untuk disambiguasi, misalnya:

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

Perhatikan bahwa "${var}bar"dalam contoh kedua di atas juga dapat ditulis "${var}"bar, dalam hal ini Anda tidak perlu kawat gigi lagi, yaitu "$var"bar. Namun, jika Anda memiliki banyak kutipan di string Anda, bentuk-bentuk alternatif ini bisa sulit dibaca (dan karenanya sulit dipertahankan). Halaman ini memberikan pengantar yang bagus untuk mengutip dalam Bash.

Array ( $varvs $var[@]vs. ${var[@]})

Sekarang untuk array Anda. Menurut manual bash :

Merujuk variabel array tanpa subskrip sama dengan mereferensikan array dengan subskrip 0.

Dengan kata lain, jika Anda tidak menyediakan indeks [], Anda mendapatkan elemen pertama dari array:

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

Yang persis sama dengan

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

Untuk mendapatkan semua elemen array, Anda perlu menggunakan @sebagai indeks, misalnya ${foo[@]}. Kawat gigi diperlukan dengan array karena tanpa mereka, shell akan memperluas $foobagian pertama, memberikan elemen pertama array diikuti oleh literal [@]:

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

Halaman ini adalah pengantar yang bagus untuk array di Bash.

Kutipan ditinjau kembali ( ${foo[@]}vs. "${foo[@]}")

Anda tidak bertanya tentang ini, tetapi itu adalah perbedaan halus yang perlu diketahui. Jika elemen dalam array Anda bisa berisi spasi putih, Anda perlu menggunakan tanda kutip ganda sehingga setiap elemen diperlakukan sebagai "kata:" yang terpisah.

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

Bandingkan ini dengan perilaku tanpa tanda kutip ganda:

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
IniSuitIsBlackNot
sumber
3
Ada kasus lain:, ${var:?}yang akan memberikan kesalahan ketika variabel tidak disetel atau tidak disetel. REF: github.com/koalaman/shellcheck/wiki/SC2154
Nam Nguyen
4
@NamNguyen Jika Anda ingin berbicara tentang bentuk-bentuk lain dari ekspansi parameter , setidaknya ada selusin lebih: ${parameter:-word}, ${parameter:=word}, ${parameter#word}, ${parameter/pattern/string}, dan sebagainya. Saya pikir itu di luar cakupan jawaban ini.
ThisSuitIsBlackNot
Sebenarnya, diskusi tentang tanda kutip ganda agak tidak lengkap. Lihat lebih lanjut stackoverflow.com/questions/10067266/…
tripleee
11

TL; DR

Semua contoh yang Anda berikan adalah variasi pada Bash Shell Expansions . Ekspansi terjadi dalam urutan tertentu, dan beberapa memiliki kasus penggunaan khusus.

Kawat gigi sebagai Pembatas Token

The ${var}sintaks terutama digunakan untuk pembatasan token ambigu. Sebagai contoh, pertimbangkan hal berikut:

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

Kawat Gigi dalam Ekspansi Array

Kawat gigi diperlukan untuk mengakses elemen-elemen dari array dan untuk ekspansi khusus lainnya . Sebagai contoh:

$ 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

Tokenisasi

Sebagian besar sisa pertanyaan Anda ada hubungannya dengan mengutip, dan bagaimana shell tokenizes input. Pertimbangkan perbedaan dalam bagaimana shell melakukan pemisahan kata dalam contoh-contoh berikut:

$ 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

The @simbol berinteraksi dengan mengutip berbeda dari *. Secara khusus:

  1. $@ "[e] xpands ke parameter posisi, mulai dari satu. Ketika ekspansi terjadi dalam tanda kutip ganda, setiap parameter diperluas ke kata yang terpisah."
  2. Dalam sebuah array, "[i] f kata tersebut dikutip ganda, ${name[*]}berkembang menjadi satu kata dengan nilai setiap anggota array dipisahkan oleh karakter pertama dari variabel IFS, dan ${name[@]}memperluas setiap elemen nama ke kata yang terpisah."

Anda dapat melihat ini dalam tindakan sebagai berikut:

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

$ count_params "$@"
3

$ count_params "$*"
1

Penggunaan ekspansi yang dikutip sangat penting ketika variabel merujuk pada nilai dengan spasi atau karakter khusus yang dapat mencegah shell dari pemisahan kata dengan cara yang Anda inginkan. Lihat Mengutip untuk lebih lanjut tentang cara mengutip bekerja di Bash.

Todd A. Jacobs
sumber
7

Anda perlu membedakan antara array dan variabel sederhana - dan contoh Anda menggunakan array.

Untuk variabel polos:

  • $vardan ${var}persis sama.
  • "$var"dan "${var}"persis sama.

Namun, kedua pasangan tidak 100% identik dalam semua kasus. Pertimbangkan output di bawah ini:

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

Tanpa tanda kutip ganda di sekitar variabel, spasi internal hilang dan ekspansi diperlakukan sebagai dua argumen untuk printfperintah. Dengan tanda kutip ganda di sekitar variabel, spasi internal dipertahankan dan ekspansi diperlakukan sebagai satu argumen untuk printfperintah.

Dengan array, aturannya sama dan berbeda.

  • Jika groupsarray, referensi $groupsatau ${groups}sama dengan referensi ${groups[0]}, elemen nol array.
  • Referensi "${groups[@]}"adalah analog dengan referensi "$@"; itu menjaga jarak dalam elemen individual array, dan mengembalikan daftar nilai, satu nilai per elemen array.
  • Referensi ${groups[@]}tanpa tanda kutip ganda tidak mempertahankan spasi dan dapat memperkenalkan lebih banyak nilai daripada elemen dalam array jika beberapa elemen mengandung spasi.

Sebagai contoh:

$ 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
$

Menggunakan *alih-alih @mengarah ke hasil yang sedikit berbeda.

Lihat juga Cara mengulang argumen di bashskrip .

Jonathan Leffler
sumber
3

Kalimat kedua dari paragraf pertama di bawah Parameter Expansion di man bashmengatakan,

Nama parameter atau simbol yang akan diperluas dapat dilampirkan dalam kurung kurawal, yang merupakan opsional tetapi berfungsi untuk melindungi variabel agar diperluas dari karakter segera setelah itu yang dapat diartikan sebagai bagian dari nama.

Yang memberi tahu Anda bahwa nama itu hanya kurung kurawal , dan tujuan utamanya adalah untuk menjelaskan di mana nama itu dimulai dan berakhir:

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

Jika Anda membaca lebih lanjut, Anda menemukan,

Kawat gigi diperlukan ketika parameter adalah parameter posisi dengan lebih dari satu digit ...

Mari kita uji:

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

Hah. Rapi. Jujur saya tidak tahu itu sebelum menulis ini (saya tidak pernah memiliki lebih dari 9 parameter posisi sebelumnya.)

Tentu saja, Anda juga perlu kawat gigi untuk melakukan fitur ekspansi parameter yang kuat seperti

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

serta ekspansi array.

kojiro
sumber
3

Kasus terkait yang tidak dibahas di atas. Mengutip variabel kosong tampaknya mengubah segalanya test -n. Ini secara khusus diberikan sebagai contoh dalam infoteks untuk coreutils, tetapi tidak benar-benar dijelaskan:

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.

Saya ingin mendengar penjelasan terperinci. Pengujian saya mengonfirmasi hal ini, dan saya sekarang mengutip variabel saya untuk semua tes string, untuk menghindari -zdan -nmengembalikan hasil yang sama.

$ 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
secara nortal
sumber
2

Yah, saya tahu bahwa enkapsulasi variabel membantu Anda untuk bekerja dengan sesuatu seperti:

${groups%example}

atau sintaksis seperti itu, di mana Anda ingin melakukan sesuatu dengan variabel Anda sebelum mengembalikan nilainya.

Sekarang, jika Anda melihat kode Anda, semua keajaiban ada di dalam

${groups[@]}

keajaiban ada di sana karena Anda tidak dapat menulis hanya: $groups[@]

Anda meletakkan variabel Anda di dalam {}karena Anda ingin menggunakan karakter khusus []dan @. Anda tidak dapat memberi nama atau memanggil variabel Anda hanya: @atau something[]karena ini adalah karakter khusus untuk operasi dan nama lain.

Sierisimo
sumber
Ini gagal menunjukkan arti yang sangat signifikan dari kutipan ganda, dan bagaimana kode tanpa mereka pada dasarnya rusak.
tripleee