Qual è la differenza tra $ {var}, “$ var” e “$ {var}” nella shell Bash?


134

Cosa dice il titolo: cosa significa incapsulare una variabile in {}, ""o "{}"? Non sono stato in grado di trovare spiegazioni online su questo - non sono stato in grado di fare riferimento ad esse tranne che per l'uso dei simboli, che non cede nulla.

Ecco un esempio:

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

Questo:

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

Dimostra di essere molto diverso da questo:

for group in $groups; do
    echo $group
done

e questo:

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

Solo il primo realizza ciò che voglio: iterare attraverso ogni elemento dell'array. In realtà non sono chiare sulle differenze tra $groups, "$groups", ${groups}e "${groups}". Se qualcuno potesse spiegarlo, lo apprezzerei.

Come domanda aggiuntiva: qualcuno conosce il modo accettato di fare riferimento a questi incapsulamenti?


Risposte:


228

Parentesi graffe ( $varvs. ${var})

Nella maggior parte dei casi, $vare ${var}sono gli stessi:

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

Le parentesi graffe sono necessarie solo per risolvere l'ambiguità nelle espressioni:

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

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

Quando aggiungi virgolette doppie attorno a una variabile, dici alla shell di trattarla come una singola parola, anche se contiene spazi bianchi:

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

Contrasta quel comportamento con quanto segue:

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

Come per $varvs. ${var}, le parentesi graffe sono necessarie solo per chiarimenti, ad esempio:

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

Nota che "${var}bar"nel secondo esempio sopra potrebbe anche essere scritto "${var}"bar, nel qual caso non hai più bisogno delle parentesi graffe, cioè "$var"bar. Tuttavia, se nella stringa sono presenti molte virgolette, questi moduli alternativi possono risultare difficili da leggere (e quindi difficili da mantenere). Questa pagina fornisce una buona introduzione alle citazioni in Bash.

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

Ora per il tuo array. Secondo il manuale di bash :

Fare riferimento a una variabile di matrice senza un pedice equivale a fare riferimento alla matrice con un pedice di 0.

In altre parole, se non si fornisce un indice con [], si ottiene il primo elemento dell'array:

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

Che è esattamente lo stesso di

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

Per ottenere tutti gli elementi di un array, è necessario utilizzare @come indice, ad es ${foo[@]}. Le parentesi graffe sono richieste con le matrici perché senza di esse la shell espanderebbe $fooprima la parte, dando il primo elemento dell'array seguito da un valore letterale [@]:

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

Questa pagina è una buona introduzione agli array in Bash.

Citazioni rivisitate ( ${foo[@]}vs. "${foo[@]}")

Non hai chiesto questo, ma è una sottile differenza che è bene sapere. Se gli elementi nell'array potrebbero contenere spazi bianchi, è necessario utilizzare virgolette doppie in modo che ogni elemento sia trattato come una "parola:"

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

Contrasta questo con il comportamento senza virgolette doppie:

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
C'è un altro caso: ${var:?}che fornirà un errore quando la variabile non è impostata o non è impostata. RIF: github.com/koalaman/shellcheck/wiki/SC2154
Nam Nguyen

4
@NamNguyen Se si vuole parlare di altre forme di espansione dei parametri , ci sono almeno una dozzina di più: ${parameter:-word}, ${parameter:=word}, ${parameter#word}, ${parameter/pattern/string}, e così via. Penso che quelli esulino dallo scopo di questa risposta.
ThisSuitIsBlackNon

In realtà, la discussione tra virgolette doppie è in qualche modo incompleta. Vedi ancora stackoverflow.com/questions/10067266/...
tripleee

11

TL; DR

Tutti gli esempi che dai sono variazioni sulle espansioni di Bash Shell . Le espansioni avvengono in un ordine particolare e alcune hanno casi d'uso specifici.

Parentesi graffe come delimitatori di token

La ${var}sintassi viene utilizzata principalmente per delimitare token ambigui. Ad esempio, considerare quanto segue:

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

Parentesi graffe nelle espansioni di array

Le parentesi graffe sono necessarie per accedere agli elementi di un array e per altre espansioni speciali . Per esempio:

$ 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

tokenizzazione

La maggior parte delle altre domande ha a che fare con la citazione e come la shell tokenizza l'input. Considera la differenza nel modo in cui la shell esegue la suddivisione delle parole nei seguenti esempi:

$ 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

Il @simbolo interagisce citando diversamente da *. In particolare:

  1. $@ "[e] xpands ai parametri posizionali, a partire da uno. Quando l'espansione avviene tra virgolette doppie, ogni parametro si espande in una parola separata."
  2. In un array, "[i] f la parola è racchiusa tra virgolette doppie, si ${name[*]}espande in una singola parola con il valore di ciascun membro dell'array separato dal primo carattere della variabile IFS ed ${name[@]}espande ogni elemento del nome in una parola separata."

Puoi vederlo in azione come segue:

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

$ count_params "$@"
3

$ count_params "$*"
1

L'uso di un'espansione quotata è molto importante quando le variabili si riferiscono a valori con spazi o caratteri speciali che potrebbero impedire alla shell di dividere le parole nel modo in cui si intende. Vedi Citazioni per ulteriori informazioni su come funziona la quotazione in Bash.


7

Devi distinguere tra matrici e variabili semplici - e il tuo esempio sta usando una matrice.

Per variabili semplici:

  • $vare ${var}sono esattamente equivalenti.
  • "$var"e "${var}"sono esattamente equivalenti.

Tuttavia, le due coppie non sono identiche al 100% in tutti i casi. Considera l'output di seguito:

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

Senza le doppie virgolette attorno alla variabile, la spaziatura interna viene persa e l'espansione viene trattata come due argomenti per il printfcomando. Con le doppie virgolette attorno alla variabile, la spaziatura interna viene preservata e l'espansione viene trattata come un argomento per il printfcomando.

Con le matrici, le regole sono sia simili che diverse.

  • Se groupsè un array, fare riferimento $groupso ${groups}equivale a fare riferimento ${groups[0]}, l'elemento zeroth dell'array.
  • Il riferimento "${groups[@]}"è analogo al riferimento "$@"; conserva la spaziatura nei singoli elementi dell'array e restituisce un elenco di valori, un valore per elemento dell'array.
  • Il riferimento ${groups[@]}senza le doppie virgolette non conserva la spaziatura e può introdurre più valori di quanti sono gli elementi nell'array se alcuni degli elementi contengono spazi.

Per esempio:

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

L'utilizzo *invece di @porta a risultati leggermente diversi.

Vedi anche Come scorrere gli argomenti in uno bashscript .


3

La seconda frase del primo paragrafo sotto Espansione parametri in man bashdice:

Il nome del parametro o il simbolo da espandere può essere racchiuso tra parentesi graffe, che sono facoltative ma servono a proteggere la variabile da espandere dai caratteri immediatamente successivi che possono essere interpretati come parte del nome.

Il che ti dice che il nome è semplicemente parentesi graffe e lo scopo principale è chiarire dove inizia e finisce il nome:

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

Se leggi oltre scopri,

Le parentesi graffe sono necessarie quando il parametro è un parametro posizionale con più di una cifra ...

Testiamo:

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

Huh. Neat. Onestamente non lo sapevo prima di scrivere questo (non ho mai avuto più di 9 parametri posizionali prima.)

Naturalmente, hai anche bisogno di parentesi graffe per eseguire le potenti funzionalità di espansione dei parametri come

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

così come l'espansione dell'array.


3

Un caso correlato non trattato sopra. Citare una variabile vuota sembra cambiare le cose per test -n. Questo è specificamente indicato come esempio nel infotesto per coreutils, ma non è spiegato in realtà:

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.

Mi piacerebbe sentire la spiegazione dettagliata. I miei test lo confermano e ora sto citando le mie variabili per tutti i test delle stringhe, per evitare di avere -ze -nrestituire lo stesso risultato.

$ 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

Bene, so che l'incapsulamento di una variabile ti aiuta a lavorare con qualcosa del tipo:

${groups%example}

o sintassi del genere, in cui si desidera eseguire un'operazione con la variabile prima di restituire il valore.

Ora, se vedi il tuo codice, tutta la magia è dentro

${groups[@]}

la magia è lì perché non puoi scrivere solo: $groups[@]

Stai mettendo la tua variabile dentro {}perché vuoi usare caratteri speciali []e @. Non puoi nominare o chiamare la tua variabile semplicemente: @o something[]perché si tratta di caratteri riservati per altre operazioni e nomi.


Ciò non indica il significato molto significativo delle doppie virgolette e come il codice senza di esse sia sostanzialmente rotto.
Tripleee
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.