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

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 da ningún resultado.

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 

Prueba 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: iterar a través de cada elemento en la matriz. No estoy muy claro sobre 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?

Refuerzos ( $var frente a ${var} )

En la mayoría de los casos, $var y ${var} son los mismos:

 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 ( $var vs. "$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 $var frente a ${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 en cuenta que "${var}bar" en el segundo ejemplo anterior también se puede escribir en la "${var}bar" "${var}"bar , en cuyo caso ya no necesita las llaves, es decir, la "$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.

Arrays ( $var frente a $var[@] frente a ${var[@]} )

Ahora para su matriz. De acuerdo con el manual bash :

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

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

 foo=(abc) echo $foo # a 

Que es exactamente lo mismo que

 foo=(abc) echo ${foo} # a 

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

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

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

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

No preguntaste sobre esto, pero es una sutil diferencia que es bueno conocer. Si los elementos en su conjunto pueden contener espacios en blanco, debe usar comillas dobles para que cada elemento se trate 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 

Contraste 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 

Necesita distinguir entre matrices y variables simples, y su ejemplo es usar una matriz.

Para variables simples:

  • $var y ${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 el resultado 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 espaciado interno se pierde y la expansión se trata como dos argumentos para el comando printf . Con las comillas dobles alrededor de la variable, el espaciado interno se conserva y la expansión se trata como un argumento para el comando printf .

Con las matrices, las reglas son similares y diferentes.

  • Si groups es una matriz, hacer referencia a $groups o ${groups} equivale a hacer referencia a ${groups[0]} , el elemento zeroth de la matriz.
  • Hacer referencia a "${groups[@]}" es análogo a hacer referencia a "$@" ; conserva el espaciado en los elementos individuales de la matriz y devuelve una lista de valores, un valor por elemento de la matriz.
  • Hacer referencia a ${groups[@]} sin las comillas dobles no conserva el espaciado y puede introducir más valores que los elementos que hay 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 * vez de @ conduce a resultados sutilmente diferentes.

Vea también Cómo iterar sobre los argumentos en un script bash .

TL; DR

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

Tirantes como delimitadores de tokens

La syntax ${var} 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 

Refuerzos en expansiones de matriz

Los refuerzos son necesarios 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 rest de sus preguntas tiene que ver con las citas, y cómo el shell tokeniza la entrada. Tenga en cuenta la diferencia en la forma en que el intérprete de comandos 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 comillas de forma diferente a * . Específicamente:

  1. $@ “[e] xpands a los parámetros posicionales, comenzando desde uno. Cuando la expansión ocurre entre comillas dobles, cada parámetro se expande a una palabra separada”.
  2. En una matriz, “[i] f la palabra tiene comillas dobles, ${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 del nombre a una palabra separada “.

Puedes 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 citada importa mucho cuando las variables se refieren a valores con espacios o caracteres especiales que pueden evitar que el shell se divida en palabras de la manera que usted desea. Consulte Citar para obtener más información sobre cómo funciona la cita en Bash.

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

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

Lo que indica 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 …

Vamos a probar:

 $ 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 hacer 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 matriz.

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

 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 que -z y -n devuelvan 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 

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

 ${groups%example} 

o una syntax como esa, donde quieres hacer algo con tu variable antes de devolver el valor.

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

 ${groups[@]} 

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

Está colocando su variable dentro de {} porque quiere usar caracteres especiales [] y @ . No puede nombrar ni llamar a su variable simplemente: @ o something[] porque estos son caracteres reservados para otras operaciones y nombres.