Яка різниця між $ {var}, "$ var" та "$ {var}" в оболонці Bash?


133

Що говорить заголовок: що означає інкапсулювати змінну в {}, ""або "{}"? Я не зміг знайти жодних пояснень з цього приводу в Інтернеті - я не зміг посилатися на них, крім використання символів, які нічого не дає.

Ось приклад:

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}". Якщо хтось міг би це пояснити, я би вдячний.

Як додаткове запитання - чи знає хто прийнятий спосіб посилатися на ці інкапсуляції?


Відповіді:


228

Дужки ( $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[@]vs. ${var[@]})

Тепер для вашого масиву. Відповідно до посібника з bash :

Посилання на змінну масиву без індексів еквівалентно посиланню масиву на індекс.

Іншими словами, якщо ви не постачаєте індекс [], ви отримуєте перший елемент масиву:

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 . Розширення відбуваються в певному порядку, а деякі мають конкретні випадки використання.

Дужки як роздільники Token

${var}Синтаксис в основному використовуються для обмежують неоднозначні маркерів. Наприклад, врахуйте наступне:

$ 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

Токенізація

Більшість ваших запитань стосуються цитування та того, як оболонка токенізує вхід. Розглянемо різницю в тому, як оболонка виконує розбиття слів у наступних прикладах:

$ 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] x розширюється на позиційні параметри, починаючи з одиниці. Коли розширення відбувається в межах подвійних лапок, кожен параметр розширюється на окреме слово."
  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]}, нульовий елемент масиву.
  • Посилання "${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

Друге речення першого пункту під Parameter Expansion ін 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[]тому, що це зарезервовані символи для інших операцій та імен.


Це не дозволяє вказати на дуже значущий зміст подвійних лапок, і як код без них в основному порушений.
трійка
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.