Qual é a diferença entre $ {var}, "$ var" e "$ {var}" no shell Bash?

133

O que o título diz: o que significa encapsular uma variável em {}, ""ou "{}"? Não consegui encontrar explicações on-line sobre isso - não consegui me referir a elas, exceto pelo uso dos símbolos, que não produz nada.

Aqui está um exemplo:

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

Este:

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

Prova ser muito diferente disso:

for group in $groups; do
    echo $group
done

e isto:

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

Somente o primeiro realiza o que eu quero: iterar através de cada elemento da matriz. Eu não sou muito claro sobre as diferenças entre $groups, "$groups", ${groups}e "${groups}". Se alguém pudesse explicar, eu agradeceria.

Como uma pergunta extra - alguém sabe a maneira aceita de se referir a esses encapsulamentos?

SheerSt
fonte
Consulte também Como iterar sobre argumentos em um bashscript?
Jonathan Leffler

Respostas:

228

Suspensórios ( $varvs. ${var})

Na maioria dos casos, $vare ${var}são os mesmos:

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

Os colchetes são necessários apenas para resolver a ambiguidade nas expressões:

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

Citações ( $varvs. "$var"vs. "${var}")

Ao adicionar aspas duplas em torno de uma variável, você diz ao shell para tratá-lo como uma única palavra, mesmo que contenha espaços em branco:

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

Compare esse comportamento com o seguinte:

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

Assim como no $varvs. ${var}, as chaves são necessárias apenas para desambiguação, por exemplo:

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

Observe que "${var}bar"no segundo exemplo acima também pode ser escrito "${var}"bar; nesse caso, você não precisa mais dos aparelhos, ou seja "$var"bar. No entanto, se você tiver muitas aspas em sua sequência, esses formulários alternativos podem ser difíceis de ler (e, portanto, difíceis de manter). Esta página fornece uma boa introdução à citação no Bash.

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

Agora para a sua matriz. De acordo com o manual do bash :

Referenciar uma variável de matriz sem um subscrito é equivalente a referenciar a matriz com um subscrito de 0.

Em outras palavras, se você não fornecer um índice [], obterá o primeiro elemento da matriz:

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

Qual é exatamente o mesmo que

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

Para obter todos os elementos de uma matriz, você precisa usar @como índice, por exemplo ${foo[@]}. As chaves são necessárias com matrizes porque, sem elas, o shell expandiria a $foopeça primeiro, fornecendo o primeiro elemento da matriz seguido de um literal [@]:

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

Esta página é uma boa introdução às matrizes no Bash.

Citações revisitadas ( ${foo[@]}vs. "${foo[@]}")

Você não perguntou sobre isso, mas é uma diferença sutil que é bom saber. Se os elementos em sua matriz puderem conter espaços em branco, você precisará usar aspas duplas para que cada elemento seja tratado como uma "palavra:" separada.

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

Compare isso com o comportamento sem aspas duplas:

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
fonte
3
Há outro caso:, ${var:?}que fornecerá erro quando a variável estiver desabilitada ou não definida. REF: github.com/koalaman/shellcheck/wiki/SC2154
Nam Nguyen
4
@NamNguyen Se você quiser falar sobre outras formas de expansão de parâmetros , há pelo menos mais uma dúzia: ${parameter:-word}, ${parameter:=word}, ${parameter#word}, ${parameter/pattern/string}, e assim por diante. Eu acho que esses estão além do escopo desta resposta.
precisa saber é o seguinte
Na verdade, a discussão de aspas duplas é meio incompleta. Veja mais stackoverflow.com/questions/10067266/…
tripleee
11

TL; DR

Todos os exemplos que você fornece são variações das expansões do Bash Shell . As expansões acontecem em uma ordem específica e algumas têm casos de uso específicos.

Chaves como delimitadores de token

A ${var}sintaxe é usada principalmente para delimitar tokens ambíguos. Por exemplo, considere o seguinte:

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

Chaves em expansões de matriz

As chaves são necessárias para acessar os elementos de uma matriz e para outras expansões especiais . Por exemplo:

$ 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

Tokenização

A maioria das outras perguntas tem a ver com citações e como o shell simboliza a entrada. Considere a diferença de como o shell executa a divisão de palavras nos seguintes exemplos:

$ 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

O @símbolo interage com a citação de maneira diferente de *. Especificamente:

  1. $@ "[e] xpands para os parâmetros posicionais, começando por um. Quando a expansão ocorre entre aspas duplas, cada parâmetro se expande para uma palavra separada."
  2. Em uma matriz, "[i] f, a palavra é citada duas vezes, se ${name[*]}expande para uma única palavra com o valor de cada membro da matriz separado pelo primeiro caractere da variável IFS e ${name[@]}expande cada elemento do nome para uma palavra separada".

Você pode ver isso em ação da seguinte maneira:

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

$ count_params "$@"
3

$ count_params "$*"
1

O uso de uma expansão citada é muito importante quando variáveis ​​se referem a valores com espaços ou caracteres especiais que podem impedir que o shell seja dividido da maneira que você deseja. Consulte Citação para obter mais informações sobre como funciona a citação no Bash.

Todd A. Jacobs
fonte
7

Você precisa distinguir entre matrizes e variáveis ​​simples - e seu exemplo está usando uma matriz.

Para variáveis ​​simples:

  • $vare ${var}são exatamente equivalentes.
  • "$var"e "${var}"são exatamente equivalentes.

No entanto, os dois pares não são 100% idênticos em todos os casos. Considere a saída abaixo:

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

Sem as aspas duplas em torno da variável, o espaçamento interno é perdido e a expansão é tratada como dois argumentos para o printfcomando. Com aspas duplas em torno da variável, o espaçamento interno é preservado e a expansão é tratada como um argumento para o printfcomando.

Com matrizes, as regras são semelhantes e diferentes.

  • Se groupsé uma matriz, referenciando $groupsou ${groups}é equivalente a referência ${groups[0]}, o elemento zero da matriz.
  • Referenciar "${groups[@]}"é análogo a referenciar "$@"; ele preserva o espaçamento nos elementos individuais da matriz e retorna uma lista de valores, um valor por elemento da matriz.
  • A referência ${groups[@]}sem as aspas duplas não preserva o espaçamento e pode introduzir mais valores do que os elementos da matriz, se alguns deles contiverem espaços.

Por exemplo:

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

Usar em *vez de @leva a resultados sutilmente diferentes.

Consulte também Como iterar sobre os argumentos em um bashscript .

Jonathan Leffler
fonte
3

A segunda frase do primeiro parágrafo sob expansão de parâmetros no man bashdiz,

O nome ou símbolo do parâmetro a ser expandido pode ser colocado entre chaves, que são opcionais, mas servem para proteger a variável a ser expandida dos caracteres imediatamente a seguir, que podem ser interpretados como parte do nome.

O que informa que o nome é simplesmente chavetas e o principal objetivo é esclarecer onde o nome começa e termina:

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

Se você ler mais, descobrirá:

As chaves são necessárias quando parâmetro é um parâmetro posicional com mais de um dígito…

Vamos testar:

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

Hã. Arrumado. Sinceramente, eu não sabia disso antes de escrever isso (nunca tive mais de 9 parâmetros posicionais antes).

Obviamente, você também precisa de chaves para fazer os poderosos recursos de expansão de parâmetros, como

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

bem como expansão de matriz.

kojiro
fonte
3

Um caso relacionado não coberto acima. Citar uma variável vazia parece mudar as coisas test -n. Isso é dado especificamente como um exemplo no infotexto para coreutils, mas não é realmente explicado:

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.

Eu adoraria ouvir a explicação detalhada. Meus testes confirmam isso, e agora estou citando minhas variáveis ​​para todos os testes de cadeia de caracteres, para evitar ter -ze -nretornar o mesmo resultado.

$ 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
nortalmente
fonte
2

Bem, eu sei que o encapsulamento de uma variável ajuda você a trabalhar com algo como:

${groups%example}

ou sintaxe como essa, na qual você deseja fazer algo com sua variável antes de retornar o valor.

Agora, se você vir seu código, toda a magia está dentro

${groups[@]}

a mágica está aí porque você não pode escrever apenas: $groups[@]

Você está colocando sua variável dentro do {}porque deseja usar caracteres especiais []e @. Você não pode nomear ou chamar sua variável apenas: @ou something[]porque esses são caracteres reservados para outras operações e nomes.

Sierisimo
fonte
Isso não indica o significado muito significativo das aspas duplas e como o código sem elas é basicamente quebrado.
Tripleee