Jaka jest różnica między $ {var}, „$ var” i „$ {var}” w powłoce Bash?

141

Co mówi tytuł: co to znaczy ująć zmienną w {}, ""lub "{}„? Nie udało mi się znaleźć żadnych wyjaśnień na ten temat w Internecie - nie mogłem się do nich odnieść poza użyciem symboli, nic nie daje.

Oto przykład:

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

To:

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

Okazuje się, że różni się znacznie od tego:

for group in $groups; do
    echo $group
done

i to:

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

Tylko pierwszy z nich spełnia to, czego chcę: iteruje przez każdy element tablicy. Nie jestem do końca jasne, na różnice między $groups, "$groups", ${groups}i "${groups}". Gdyby ktoś mógł to wyjaśnić, byłbym wdzięczny.

Jako dodatkowe pytanie - czy ktoś zna akceptowany sposób odwoływania się do tych enkapsulacji?

SheerSt
źródło
Zobacz także Jak iterować po argumentach w bashskrypcie?
Jonathan Leffler

Odpowiedzi:

243

Szelki ( $varvs. ${var})

W większości przypadków $vari ${var}są takie same:

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

Nawiasy klamrowe są potrzebne tylko do rozwiązania niejednoznaczności w wyrażeniach:

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

Cytaty ( $varporównaniu "$var"vs. "${var}")

Kiedy dodajesz podwójne cudzysłowy wokół zmiennej, mówisz powłoce, aby traktowała ją jako pojedyncze słowo, nawet jeśli zawiera spacje:

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

Porównaj to zachowanie z następującymi:

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

Podobnie jak w przypadku $varvs. ${var}, szelki są potrzebne tylko do ujednoznacznienia, na przykład:

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

Zwróć uwagę, że "${var}bar"w drugim przykładzie powyżej można również zapisać "${var}"bar, w którym to przypadku nie potrzebujesz już nawiasów klamrowych, tj "$var"bar. Jeśli jednak masz dużo cudzysłowów w swoim ciągu, te alternatywne formy mogą być trudne do odczytania (a zatem trudne w utrzymaniu). Ta strona stanowi dobre wprowadzenie do cytowania w Bash.

Tablicami ( $varporównaniu $var[@]vs. ${var[@]})

Teraz twoja tablica. Zgodnie z instrukcją bash :

Odwołanie się do zmiennej tablicowej bez indeksu jest równoważne odwołaniu się do tablicy z indeksem dolnym równym 0.

Innymi słowy, jeśli nie podasz indeksu [], otrzymasz pierwszy element tablicy:

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

Co jest dokładnie tym samym, co

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

Aby pobrać wszystkie elementy tablicy, @jako indeksu należy użyć np ${foo[@]}. Nawiasy klamrowe są wymagane przy tablicach, ponieważ bez nich powłoka najpierw rozszerzyłaby $fooczęść, podając pierwszy element tablicy, a po nim literał [@]:

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

Ta strona jest dobrym wprowadzeniem do tablic w Bash.

Cytaty ponownie odwiedzone ( ${foo[@]}vs. "${foo[@]}")

Nie pytałeś o to, ale jest to subtelna różnica, o której warto wiedzieć. Jeśli elementy Twojej tablicy mogą zawierać spacje, musisz użyć podwójnych cudzysłowów, aby każdy element był traktowany jako osobne „słowo:”

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

Porównaj to z zachowaniem bez podwójnych cudzysłowów:

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
ThisSuitIsBlackNot
źródło
3
Jest inny przypadek ${var:?}:, który spowoduje błąd, gdy zmienna nie jest ustawiona lub nie jest ustawiona. REF: github.com/koalaman/shellcheck/wiki/SC2154
Nam Nguyen
5
@NamNguyen Jeśli chcesz porozmawiać o innych form interpretacji parametrów , istnieje co najmniej kilkanaście więcej: ${parameter:-word}, ${parameter:=word}, ${parameter#word}, ${parameter/pattern/string}, i tak dalej. Myślę, że to wykracza poza zakres tej odpowiedzi.
ThisSuitIsBlackNot
Właściwie dyskusja na temat podwójnych cudzysłowów jest trochę niepełna. Zobacz więcej stackoverflow.com/questions/10067266/…
tripleee
11

TL; DR

Wszystkie podane przykłady są odmianami rozszerzeń powłoki Bash . Rozszerzenia następują w określonej kolejności, a niektóre mają określone przypadki użycia.

Nawiasy klamrowe jako ograniczniki znaczników

${var}Składnia służy przede wszystkim do wyznaczania niejednoznaczne tokenów. Na przykład rozważ następujące kwestie:

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

Nawiasy klamrowe w rozszerzeniach tablic

Nawiasy są wymagane, aby uzyskać dostęp do elementów tablicy i innych specjalnych rozszerzeń . Na przykład:

$ 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

Tokenizacja

Większość pozostałych pytań dotyczy cytowania i sposobu, w jaki powłoka tokenizuje dane wejściowe. Rozważ różnicę w sposobie, w jaki powłoka dokonuje podziału na słowa w następujących przykładach:

$ 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

@Symbolu współdziała z podaniem inaczej niż *. Konkretnie:

  1. $@ „[e] rozwija się do parametrów pozycyjnych, zaczynając od jedynki. Kiedy interpretacja następuje w cudzysłowach, każdy parametr jest interpretowany jako osobne słowo”.
  2. W tablicy „[i] f słowo jest umieszczane w cudzysłowie, ${name[*]}rozwija się do pojedynczego słowa z wartością każdego elementu tablicy oddzieloną pierwszym znakiem zmiennej IFS i ${name[@]}rozwija każdy element nazwy do osobnego słowa”.

Możesz to zobaczyć w akcji w następujący sposób:

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

$ count_params "$@"
3

$ count_params "$*"
1

Użycie rozwinięcia w cudzysłowie ma ogromne znaczenie, gdy zmienne odnoszą się do wartości ze spacjami lub znakami specjalnymi, które mogą uniemożliwić powłoce podział na słowa w zamierzony sposób. Zobacz Cytowanie, aby uzyskać więcej informacji na temat cytowania w Bash.

Todd A. Jacobs
źródło
7

Musisz odróżnić tablice od prostych zmiennych - a Twój przykład używa tablicy.

Dla zwykłych zmiennych:

  • $vari ${var}są dokładnie równoważne.
  • "$var"i "${var}"są dokładnie równoważne.

Jednak te dwie pary nie są w 100% identyczne we wszystkich przypadkach. Rozważ poniższe dane wyjściowe:

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

Bez podwójnych cudzysłowów wokół zmiennej wewnętrzny odstęp jest tracony, a interpretacja jest traktowana jako dwa argumenty printfpolecenia. W przypadku podwójnych cudzysłowów wokół zmiennej zachowywany jest wewnętrzny odstęp, a interpretacja jest traktowana jako jeden argument printfpolecenia.

W przypadku tablic reguły są podobne i różne.

  • Jeśli groupsjest tablicą, odwołuje się $groupslub ${groups}jest równoznaczne z odwołaniem ${groups[0]}, zerowy element tablicy.
  • Odwołanie "${groups[@]}"jest analogiczne do odniesienia "$@"; zachowuje odstępy w poszczególnych elementach tablicy i zwraca listę wartości, po jednej wartości na element tablicy.
  • Odwołania ${groups[@]}bez podwójnych cudzysłowów nie zachowują odstępów i mogą wprowadzić więcej wartości niż elementów tablicy, jeśli niektóre elementy zawierają spacje.

Na przykład:

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

Używanie *zamiast @prowadzi do nieznacznie różnych wyników.

Zobacz także Jak iterować po argumentach w bashskrypcie .

Jonathan Leffler
źródło
3

Drugie zdanie pierwszego akapitu w sekcji Rozwijanie parametrów w man bashmówi:

Nazwa parametru lub symbol do interpretacji może być ujęta w nawiasy klamrowe, które są opcjonalne, ale służą do ochrony zmiennej, która ma być interpretowana, przed znakami występującymi bezpośrednio po niej, które mogłyby zostać zinterpretowane jako część nazwy.

Co mówi, że nazwa to po prostu nawiasy klamrowe , a głównym celem jest wyjaśnienie, gdzie zaczyna się i kończy:

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

Jeśli będziesz czytać dalej, odkryjesz,

Nawiasy klamrowe są wymagane, gdy parametr jest parametrem pozycyjnym z więcej niż jedną cyfrą…

Przetestujmy:

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

Huh. Schludny. Szczerze mówiąc, nie wiedziałem tego przed napisaniem tego (nigdy wcześniej nie miałem więcej niż 9 parametrów pozycyjnych).

Oczywiście nawiasy klamrowe są również potrzebne do wykonywania zaawansowanych funkcji rozszerzania parametrów, takich jak

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

a także rozszerzanie macierzy.

kojiro
źródło
3

Powiązany przypadek nie omówiony powyżej. Cytowanie pustej zmiennej wydaje się zmieniać rzeczy test -n. Jest to konkretnie podane jako przykład w infotekście coreutils, ale nie do końca wyjaśnione:

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.

Bardzo chciałbym usłyszeć szczegółowe wyjaśnienie. Moje testy to potwierdzają i teraz cytuję moje zmienne dla wszystkich testów łańcuchowych, aby uniknąć -zi -nzwrócić ten sam wynik.

$ 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
północnej
źródło
2

Cóż, wiem, że hermetyzacja zmiennej pomaga w pracy z czymś takim:

${groups%example}

lub taką składnię, gdzie chcesz coś zrobić ze swoją zmienną przed zwróceniem wartości.

Teraz, jeśli widzisz swój kod, cała magia jest w środku

${groups[@]}

magia jest w tym, ponieważ nie możesz po prostu napisać: $groups[@]

Umieszczasz swoją zmienną wewnątrz, {}ponieważ chcesz używać znaków specjalnych []i @. Nie możesz nazwać ani wywołać swojej zmiennej tylko: @lub something[]dlatego, że są to znaki zastrzeżone dla innych operacji i nazw.

Sierisimo
źródło
To nie pokazuje bardzo znaczącego znaczenia podwójnych cudzysłowów i tego, jak kod bez nich jest zasadniczo uszkodzony.
tripleee