В чем разница между $ {var}, «$ var» и «$ {var}» в оболочке Bash?

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}". Если бы кто-нибудь мог это объяснить, я был бы признателен.

В качестве дополнительного вопроса - знает ли кто-нибудь общепринятый способ обращения к этим инкапсуляциям?

SheerSt
источник
См. Также Как перебирать аргументы в bashскрипте?
Джонатан Леффлер

Ответы:

229

Подтяжки ( $varvs. ${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

Как и в случае с $varvs. ${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[@]})

Теперь о вашем массиве. Согласно руководству по bash :

Ссылка на переменную массива без нижнего индекса эквивалентна ссылке на массив с нижним индексом 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[@]}vs. "${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
ThisSuitIsBlackNot
источник
3
Есть еще один случай:, ${var:?}который выдаст ошибку, если переменная не установлена ​​или не установлена. ССЫЛКА: github.com/koalaman/shellcheck/wiki/SC2154
Нам Нгуен,
4
@NamNguyen Если вы хотите , чтобы говорить о других формах расширения параметров , есть, по крайней мере , с десяток: ${parameter:-word}, ${parameter:=word}, ${parameter#word}, ${parameter/pattern/string}, и так далее. Я думаю, что это выходит за рамки этого ответа.
ThisSuitIsBlackNot 07
На самом деле обсуждение двойных кавычек отчасти неполное. См. Далее stackoverflow.com/questions/10067266/…
tripleee
11

TL; DR

Все приведенные вами примеры являются вариациями расширений оболочки Bash . Расширения происходят в определенном порядке, и у некоторых есть определенные варианты использования.

Фигурные скобки как разделители токенов

${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] расширяется до позиционных параметров, начиная с единицы. Когда раскрытие происходит в двойных кавычках, каждый параметр заменяется отдельным словом».
  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

Второе предложение первого абзаца в разделе « Расширение параметров в» 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
nortally
источник
2

Что ж, я знаю, что инкапсуляция переменной помогает вам работать с чем-то вроде:

${groups%example}

или подобный синтаксис, когда вы хотите что-то сделать с вашей переменной перед возвратом значения.

Теперь, если вы видите свой код, вся магия внутри

${groups[@]}

магия здесь, потому что вы не можете написать просто: $groups[@]

Вы помещаете свою переменную внутрь, {}потому что хотите использовать специальные символы []и @. Вы не можете назвать или назвать свою переменную просто: @или something[]потому что это зарезервированные символы для других операций и имен.

Sierisimo
источник
Это не указывает на очень важное значение двойных кавычек и то, как код без них в основном ломается.
Tripleee