Was ist der Unterschied zwischen $ {var}, "$ var" und "$ {var}" in der Bash-Shell?

133

Was der Titel schon sagt: was es in eine Variable zu verkapseln bedeutet {}, ""oder "{}?“Ich habe nicht in der Lage gewesen , irgendwelche Erklärungen Online über diese zu finden - ich habe nicht in der Lage gewesen, sie zu beziehen , außer daß die Symbole verwenden, die gibt nichts nach.

Hier ist ein Beispiel:

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

Dies:

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

Erweist sich als viel anders als dies:

for group in $groups; do
    echo $group
done

und das:

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

Nur der erste erreicht das, was ich will: jedes Element im Array durchlaufen. Ich bin nicht wirklich klar auf die Unterschiede zwischen $groups, "$groups", ${groups}und "${groups}". Wenn jemand es erklären könnte, würde ich es schätzen.

Als zusätzliche Frage: Kennt jemand die akzeptierte Art, auf diese Kapselungen zu verweisen?

SheerSt
quelle

Antworten:

228

Zahnspange ( $varvs. ${var})

In den meisten Fällen $varund ${var}sind die gleichen:

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

Die geschweiften Klammern werden nur benötigt, um Mehrdeutigkeiten in Ausdrücken aufzulösen:

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

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

Wenn Sie eine Variable in doppelte Anführungszeichen setzen, weisen Sie die Shell an, sie als einzelnes Wort zu behandeln, auch wenn sie Leerzeichen enthält:

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

Vergleichen Sie dieses Verhalten mit Folgendem:

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

Wie bei $varvs. ${var}werden die Klammern nur zur Begriffsklärung benötigt, zum Beispiel:

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

Beachten Sie, dass "${var}bar"im zweiten Beispiel oben auch geschrieben werden könnte "${var}"bar, in welchem ​​Fall Sie die Klammern nicht mehr benötigen, dh "$var"bar. Wenn Ihre Zeichenfolge jedoch viele Anführungszeichen enthält, können diese alternativen Formulare schwer lesbar (und daher schwer zu pflegen) sein. Diese Seite bietet eine gute Einführung in das Zitieren in Bash.

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

Nun zu Ihrem Array. Laut Bash-Handbuch :

Das Verweisen auf eine Array-Variable ohne Index entspricht dem Verweisen auf das Array mit einem Index von 0.

Mit anderen Worten, wenn Sie keinen Index mit angeben [], erhalten Sie das erste Element des Arrays:

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

Welches ist genau das gleiche wie

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

Um alle Elemente eines Arrays abzurufen, müssen Sie @als Index verwenden, z ${foo[@]}. Die geschweiften Klammern sind für Arrays erforderlich, da die Shell ohne sie den $fooTeil zuerst erweitern würde und das erste Element des Arrays gefolgt von einem Literal ergibt [@]:

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

Diese Seite ist eine gute Einführung in Arrays in Bash.

Zitate überarbeitet ( ${foo[@]}vs. "${foo[@]}")

Sie haben nicht danach gefragt, aber es ist ein subtiler Unterschied, über den Sie gut Bescheid wissen sollten. Wenn die Elemente in Ihrem Array Leerzeichen enthalten könnten, müssen Sie doppelte Anführungszeichen verwenden, damit jedes Element als separates "Wort" behandelt wird:

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

Vergleichen Sie dies mit dem Verhalten ohne doppelte Anführungszeichen:

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
quelle
3
Es gibt einen anderen Fall : ${var:?}, der einen Fehler liefert, wenn die Variable nicht gesetzt oder nicht gesetzt ist. REF: github.com/koalaman/shellcheck/wiki/SC2154
Nam Nguyen
4
@NamNguyen Wenn Sie über andere Formen von sprechen Parametern Expansion gibt es mindestens ein Dutzend mehr: ${parameter:-word}, ${parameter:=word}, ${parameter#word}, ${parameter/pattern/string}, und so weiter. Ich denke, das würde den Rahmen dieser Antwort sprengen.
ThisSuitIsBlackNot
Tatsächlich ist die Diskussion über doppelte Anführungszeichen unvollständig. Siehe weitere stackoverflow.com/questions/10067266/…
Tripleee
11

TL; DR

Alle Beispiele, die Sie geben, sind Variationen von Bash Shell-Erweiterungen . Erweiterungen erfolgen in einer bestimmten Reihenfolge, und einige haben bestimmte Anwendungsfälle.

Klammern als Token-Begrenzer

Die ${var}Syntax wird hauptsächlich zum Abgrenzen mehrdeutiger Token verwendet. Betrachten Sie beispielsweise Folgendes:

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

Klammern in Array-Erweiterungen

Die geschweiften Klammern sind erforderlich, um auf die Elemente eines Arrays und für andere spezielle Erweiterungen zuzugreifen . Beispielsweise:

$ 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

Tokenisierung

Die meisten Ihrer restlichen Fragen haben mit dem Zitieren und der Tokenisierung der Eingabe durch die Shell zu tun. Berücksichtigen Sie in den folgenden Beispielen den Unterschied, wie die Shell die Wortteilung durchführt :

$ 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

Das @Symbol interagiert mit Zitaten anders als *. Speziell:

  1. $@ "[e] x erweitert sich zu den Positionsparametern, beginnend mit eins. Wenn die Erweiterung in doppelten Anführungszeichen erfolgt, wird jeder Parameter zu einem separaten Wort erweitert."
  2. In einem Array wird "[i] f das Wort in doppelte Anführungszeichen gesetzt, ${name[*]}zu einem einzelnen Wort erweitert, wobei der Wert jedes Array- ${name[@]}Elements durch das erste Zeichen der IFS-Variablen getrennt wird, und jedes Namenselement zu einem separaten Wort erweitert."

Sie können dies in Aktion wie folgt sehen:

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

$ count_params "$@"
3

$ count_params "$*"
1

Die Verwendung einer Erweiterung in Anführungszeichen ist sehr wichtig, wenn Variablen auf Werte mit Leerzeichen oder Sonderzeichen verweisen, die möglicherweise verhindern, dass die Shell die beabsichtigte Wortaufteilung durchführt. Weitere Informationen zur Funktionsweise des Zitierens in Bash finden Sie unter Zitieren.

Todd A. Jacobs
quelle
7

Sie müssen zwischen Arrays und einfachen Variablen unterscheiden - und in Ihrem Beispiel wird ein Array verwendet.

Für einfache Variablen:

  • $varund ${var}sind genau gleichwertig.
  • "$var"und "${var}"sind genau gleichwertig.

Die beiden Paare sind jedoch nicht in allen Fällen zu 100% identisch. Betrachten Sie die folgende Ausgabe:

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

Ohne die doppelten Anführungszeichen um die Variable geht der interne Abstand verloren und die Erweiterung wird als zwei Argumente für den printfBefehl behandelt. Mit den doppelten Anführungszeichen um die Variable bleibt der interne Abstand erhalten und die Erweiterung wird als ein Argument für den printfBefehl behandelt.

Bei Arrays sind die Regeln ähnlich und unterschiedlich.

  • Wenn groupses sich um ein Array handelt , das auf das nullte Element des Arrays verweist $groupsoder ${groups}gleichbedeutend mit dem Verweisen ${groups[0]}ist.
  • Das Referenzieren "${groups[@]}"ist analog zum Referenzieren "$@". Es behält den Abstand in den einzelnen Elementen des Arrays bei und gibt eine Liste von Werten zurück, einen Wert pro Element des Arrays.
  • Durch Verweisen ${groups[@]}ohne doppelte Anführungszeichen wird der Abstand nicht beibehalten und es können mehr Werte eingeführt werden, als Elemente im Array vorhanden sind, wenn einige der Elemente Leerzeichen enthalten.

Beispielsweise:

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

Die Verwendung *anstelle von @führt zu subtil unterschiedlichen Ergebnissen.

Siehe auch So durchlaufen Sie die Argumente in einem bashSkript .

Jonathan Leffler
quelle
3

Der zweite Satz des ersten Absatzes unter Parametererweiterung in man bashlautet:

Der zu erweiternde Parametername oder das zu erweiternde Symbol kann in geschweiften Klammern stehen, die optional sind, aber dazu dienen, die zu erweiternde Variable vor unmittelbar darauf folgenden Zeichen zu schützen, die als Teil des Namens interpretiert werden könnten.

Was Ihnen sagt, dass der Name einfach eine geschweifte Klammer ist und der Hauptzweck darin besteht, zu klären, wo der Name beginnt und endet:

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

Wenn Sie weiter lesen, entdecken Sie,

Die geschweiften Klammern sind erforderlich, wenn der Parameter ein Positionsparameter mit mehr als einer Ziffer ist.

Testen wir:

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

Huh. Ordentlich. Ich wusste das ehrlich gesagt nicht, bevor ich das geschrieben habe (ich hatte noch nie mehr als 9 Positionsparameter).

Natürlich benötigen Sie auch geschweifte Klammern, um die leistungsstarken Parametererweiterungsfunktionen wie z

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

sowie Array-Erweiterung.

Kojiro
quelle
3

Ein verwandter Fall, der oben nicht behandelt wurde. Das Zitieren einer leeren Variablen scheint die Dinge zu ändern test -n. Dies wird speziell als Beispiel im infoText angegeben coreutils, aber nicht wirklich erklärt:

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.

Ich würde gerne die ausführliche Erklärung hören. Mein Test bestätigt dies, und ich bin jetzt meine Variablen für alle String - Tests zu zitieren, um zu vermeiden , -zund -ndas gleiche Ergebnis zurück.

$ 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
nördlich
quelle
2

Nun, ich weiß, dass die Kapselung einer Variablen Ihnen hilft, mit etwas zu arbeiten wie:

${groups%example}

oder eine solche Syntax, bei der Sie etwas mit Ihrer Variablen tun möchten, bevor Sie den Wert zurückgeben.

Wenn Sie jetzt Ihren Code sehen, ist die ganze Magie in Ihnen

${groups[@]}

Die Magie ist da drin, weil man nicht einfach schreiben kann: $groups[@]

Sie setzen Ihre Variable in das, {}weil Sie Sonderzeichen []und verwenden möchten @. Sie können Ihre Variable nicht einfach benennen oder aufrufen: @oder something[]weil dies reservierte Zeichen für andere Operationen und Namen sind.

Sierisimo
quelle
Dies zeigt nicht die sehr wichtige Bedeutung von doppelten Anführungszeichen auf und wie Code ohne sie im Grunde genommen gebrochen wird.
Tripleee