¿Cuál es la diferencia entre $ {var}, "$ var" y "$ {var}" en el shell Bash?

134

Lo que dice el título: ¿qué significa encapsular una variable en {}, ""o "{}"? No he podido encontrar ninguna explicación en línea sobre esto, no he podido referirme a ellos excepto por el uso de los símbolos, que no produce nada

Aquí hay un ejemplo:

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

Esta:

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

Demuestra ser muy diferente a esto:

for group in $groups; do
    echo $group
done

y esto:

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

Solo el primero logra lo que quiero: recorrer cada elemento de la matriz. No estoy muy claro en las diferencias entre $groups, "$groups", ${groups}y "${groups}". Si alguien pudiera explicarlo, lo agradecería.

Como pregunta adicional, ¿alguien sabe la forma aceptada de referirse a estas encapsulaciones?

SheerSt
fuente
Consulte también ¿Cómo iterar sobre argumentos en un bashscript?
Jonathan Leffler

Respuestas:

228

Tirantes ( $varvs. ${var})

En la mayoría de los casos, $vary ${var}son lo mismo:

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

Las llaves solo son necesarias para resolver la ambigüedad en las expresiones:

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

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

Cuando agrega comillas dobles alrededor de una variable, le dice al shell que la trate como una sola palabra, incluso si contiene espacios en blanco:

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

Contraste ese comportamiento con lo siguiente:

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

Al igual que con $varvs. ${var}, las llaves solo son necesarias para la desambiguación, por ejemplo:

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

Tenga "${var}bar"en cuenta que en el segundo ejemplo anterior también podría escribirse "${var}"bar, en cuyo caso ya no necesitará las llaves, es decir "$var"bar. Sin embargo, si tiene muchas citas en su cadena, estas formas alternativas pueden ser difíciles de leer (y, por lo tanto, difíciles de mantener). Esta página proporciona una buena introducción a las citas en Bash.

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

Ahora para tu matriz. De acuerdo con el manual de bash :

Hacer referencia a una variable de matriz sin un subíndice es equivalente a hacer referencia a la matriz con un subíndice de 0.

En otras palabras, si no proporciona un índice [], obtiene el primer elemento de la matriz:

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

Que es exactamente lo mismo que

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

Para obtener todos los elementos de una matriz, debe usar @como índice, por ejemplo ${foo[@]}. Las llaves se requieren con las matrices porque sin ellas, el shell expandiría la $fooparte primero, dando el primer elemento de la matriz seguido de un literal [@]:

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

Esta página es una buena introducción a las matrices en Bash.

Cotizaciones revisitadas ( ${foo[@]}vs. "${foo[@]}")

No preguntaste sobre esto, pero es una sutil diferencia que es bueno saber. Si los elementos en su matriz pueden contener espacios en blanco, debe usar comillas dobles para que cada elemento sea tratado como una "palabra" 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 esto con el comportamiento sin comillas dobles:

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
fuente
3
Hay otro caso: ${var:?}que proporcionará un error cuando la variable se desarma o no se configura. REF: github.com/koalaman/shellcheck/wiki/SC2154
Nam Nguyen
44
@NamNguyen Si quieres hablar de otras formas de expansión de parámetros , hay al menos una docena más: ${parameter:-word}, ${parameter:=word}, ${parameter#word}, ${parameter/pattern/string}, y así sucesivamente. Creo que están más allá del alcance de esta respuesta.
ThisSuitIsBlackNot
En realidad, la discusión de las comillas dobles es algo incompleta. Ver más stackoverflow.com/questions/10067266/…
tripleee
11

TL; DR

Todos los ejemplos que da son variaciones de las expansiones de Bash Shell . Las expansiones ocurren en un orden particular, y algunas tienen casos de uso específicos.

Aparatos ortopédicos como delimitadores de tokens

La ${var}sintaxis se usa principalmente para delimitar tokens ambiguos. Por ejemplo, considere lo siguiente:

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

Tirantes en expansiones de matriz

Las llaves se requieren para acceder a los elementos de una matriz y para otras expansiones especiales . Por ejemplo:

$ 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

Tokenización

La mayoría del resto de sus preguntas tienen que ver con las citas y cómo el shell tokeniza la entrada. Considere la diferencia en cómo el shell realiza la división de palabras en los siguientes ejemplos:

$ 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

El @símbolo interactúa con las citas de manera diferente a *. Específicamente:

  1. $@ "[e] xpands a los parámetros posicionales, comenzando desde uno. Cuando la expansión se produce entre comillas dobles, cada parámetro se expande a una palabra separada".
  2. En una matriz, "[i] f la palabra está entre comillas dobles, se ${name[*]}expande a una sola palabra con el valor de cada miembro de la matriz separado por el primer carácter de la variable IFS, y ${name[@]}expande cada elemento de nombre a una palabra separada".

Puede ver esto en acción de la siguiente manera:

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

$ count_params "$@"
3

$ count_params "$*"
1

El uso de una expansión entre comillas es muy importante cuando las variables se refieren a valores con espacios o caracteres especiales que podrían evitar que el shell divida las palabras de la manera que desea. Consulte Citas para obtener más información sobre cómo funcionan las citas en Bash.

Todd A. Jacobs
fuente
7

Debe distinguir entre matrices y variables simples, y su ejemplo es utilizar una matriz.

Para variables simples:

  • $vary ${var}son exactamente equivalentes
  • "$var"y "${var}"son exactamente equivalentes

Sin embargo, los dos pares no son 100% idénticos en todos los casos. Considere la salida a continuación:

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

Sin las comillas dobles alrededor de la variable, el espacio interno se pierde y la expansión se trata como dos argumentos para el printfcomando. Con las comillas dobles alrededor de la variable, se conserva el espacio interno y la expansión se trata como un argumento para el printfcomando.

Con las matrices, las reglas son similares y diferentes.

  • Si groupses una matriz, que hace referencia $groupso ${groups}es equivalente a una referencia ${groups[0]}, el elemento cero de la matriz.
  • La referencia "${groups[@]}"es análoga a la referencia "$@"; conserva el espacio en los elementos individuales de la matriz y devuelve una lista de valores, un valor por elemento de la matriz.
  • Hacer referencia ${groups[@]}sin las comillas dobles no conserva el espacio y puede introducir más valores que elementos en la matriz si algunos de los elementos contienen espacios.

Por ejemplo:

$ 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 en *lugar de @conduce a resultados sutilmente diferentes.

Consulte también Cómo iterar sobre los argumentos en un bashscript .

Jonathan Leffler
fuente
3

La segunda oración del primer párrafo bajo Expansión de parámetros en man bashdice:

El nombre o símbolo del parámetro a expandir puede estar encerrado entre llaves, que son opcionales pero sirven para proteger la variable a expandir de los caracteres inmediatamente posteriores que podrían interpretarse como parte del nombre.

Lo que le dice que el nombre es simplemente llaves , y el propósito principal es aclarar dónde comienza y dónde termina el nombre:

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

Si lees más, descubres,

Las llaves se requieren cuando el parámetro es un parámetro posicional con más de un dígito ...

Probemos:

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

Huh Ordenado. Honestamente, no sabía eso antes de escribir esto (nunca antes había tenido más de 9 parámetros posicionales).

Por supuesto, también necesita llaves para realizar las potentes funciones de expansión de parámetros como

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

así como la expansión de la matriz.

kojiro
fuente
3

Un caso relacionado no cubierto anteriormente. Citar una variable vacía parece cambiar las cosas test -n. Esto se da específicamente como un ejemplo en el infotexto para coreutils, pero no se explica realmente:

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.

Me encantaría escuchar la explicación detallada. Mi prueba confirma esto, y ahora estoy citando mis variables para todas las pruebas de cadena, para evitar tener -zy -ndevolver el mismo 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
norte
fuente
2

Bueno, sé que la encapsulación de una variable te ayuda a trabajar con algo como:

${groups%example}

o una sintaxis como esa, donde desea hacer algo con su variable antes de devolver el valor.

Ahora, si ves tu código, toda la magia está dentro

${groups[@]}

la magia está ahí porque no puedes escribir solo: $groups[@]

Estás poniendo tu variable dentro de {}porque quieres usar caracteres especiales []y @. No puede nombrar o llamar a su variable solo: @o something[]porque estos son caracteres reservados para otras operaciones y nombres.

Sierisimo
fuente
Esto no señala el significado muy significativo de las comillas dobles, y cómo el código sin ellas está básicamente roto.
tripleee