Bash shell中的$ {var},“ $ var”和“ $ {var}”之间有什么区别?


133

标题说的是:将变量封装在{}""或中是什么意思"{}?我无法在网上找到有关此的任何解释-除了使用符号之外,我无法引用它们。什么都不产生

这是一个例子:

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

这个:

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

事实证明与此不同:

for group in $groups; do
    echo $group
done

还有这个:

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

只有第一个可以完成我想要的工作:遍历数组中的每个元素。对于,和之间的区别$groups,我不清楚。如果有人可以解释,我将不胜感激。"$groups"${groups}"${groups}"

另外一个问题-有人知道引用这些封装的公认方法吗?


Answers:


228

大括号($varvs. ${var}

在大多数情况下,$var并且${var}是相同的:

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

大括号仅用于解决表达式中的歧义:

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

引号($varvs. "$var"vs."${var}"

在变量周围加上双引号时,即使外壳包含空格,也要告诉外壳将其视为一个单词:

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

将该行为与以下内容进行对比:

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

$varvs.一样${var},大括号仅用于消除歧义,例如:

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

请注意,"${var}bar"在上面的第二个示例中也可以编写代码"${var}"bar,在这种情况下,您不再需要括号"$var"bar。但是,如果字符串中的引号很多,那么这些替代形式可能很难阅读(因此很难维护)。这一页很好地介绍了Bash中的报价。

阵列($varvs. $var[@]vs.${var[@]}

现在为您的阵列。根据bash手册

引用不带下标的数组变量等效于引用带下标0的数组。

换句话说,如果不使用提供索引,则将[]获得数组的第一个元素:

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

完全一样

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

要获取数组的所有元素,您需要将其@用作索引,例如${foo[@]}。花括号是数组所必需的,因为没有它们,shell会$foo首先扩展该部分,给出数组的第一个元素,然后是文字[@]

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

该页面很好地介绍了Bash中的数组。

重新引用行情(${foo[@]}vs. "${foo[@]}"

您没有问这个问题,但是知道这是一个微妙的区别。如果数组中的元素可以包含空格,则需要使用双引号将每个元素视为一个单独的“单词:”

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

将此与没有双引号的行为进行对比:

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
还有另一种情况:${var:?},当未设置或未设置变量时,它将提供错误。参考:github.com/koalaman/shellcheck/wiki/SC2154
Nam Nguyen

4
@NamNguyen如果你想谈论其他形式的参数扩展,至少有十几:${parameter:-word}${parameter:=word}${parameter#word}${parameter/pattern/string},等。我认为这些超出了此答案的范围。
ThisSuitIsBlackNotNot

实际上,双引号的讨论是不完整的。另见stackoverflow.com/questions/10067266/...
tripleee

11

TL; DR

您提供的所有示例都是Bash Shell扩展的变体。扩展按特定顺序进行,其中一些具有特定的用例。

大括号作为令牌定界符

${var}语法主要用于界定歧义标记。例如,考虑以下内容:

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

数组扩展中的花括号

需要大括号才能访问数组的元素以及进行其他特殊扩展。例如:

$ 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

代币化

您其余的大多数问题都与引号以及shell如何标记输入有关。在以下示例中,考虑外壳程序如何执行词拆分的区别:

$ 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

@符号与引号的交互作用不同于*。特别:

  1. $@ “ [e]扩展到位置参数,从一个开始。当扩展出现在双引号内时,每个参数扩展为一个单独的单词。”
  2. 在数组中,“单词被双引号括起来,${name[*]}扩展为单个单词,每个数组成员的值均被IFS变量的第一个字符分隔,并将${name[@]}name的每个元素扩展为单独的单词。”

您可以看到以下操作:

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

$ count_params "$@"
3

$ count_params "$*"
1

当变量引用带有空格或特殊字符的值时,使用带引号的扩展名非常重要,因为这些值可能会阻止外壳程序按预期方式对单词进行拆分。有关在Bash中如何进行报价的更多信息,请参见报价


7

您需要区分数组和简单变量-您的示例正在使用数组。

对于普通变量:

  • $var并且${var}是完全等价的。
  • "$var"并且"${var}"是完全等价的。

但是,这两对并非在所有情况下都100%相同。考虑以下输出:

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

如果在变量周围没有双引号,则会丢失内部间距,并且扩展将被视为printf命令的两个参数。用双引号括住变量,保留内部间距,并将扩展视为printf命令的一个参数。

对于数组,规则既相似又不同。

  • 如果groups是数组,则引用$groups${groups}等于引用${groups[0]},即数组的第零个元素。
  • 引用"${groups[@]}"类似于引用"$@" ; 它保留了数组中各个元素的间距,并返回值列表,数组中每个元素一个值。
  • 引用${groups[@]}没有双引号不保留间距和比有数组中的元素,如果某些元素包含空格可以引入更多的价值。

例如:

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

使用*而不是@会导致微妙的不同结果。

另请参见如何遍历bash脚本中的参数


3

根据第一段第二句参数扩展man bash说,

可以将要扩展的参数名称或符号括在括号中,该括号是可选的,但用于保护要扩展的变量不受紧随其后的字符的影响,这些字符可以解释为名称的一部分。

它告诉您名称只是大括号,主要目的是弄清楚名称的开始和结束位置:

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

如果您进一步阅读,会发现,

当参数是一个位数以上的位置参数时,必须使用大括号…

让我们测试一下:

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

嗯 整齐。老实说,我在写这篇文章之前不知道这一点(以前我从来没有超过9个位置参数。)

当然,您还需要花括号来执行强大的参数扩展功能,例如

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

以及数组扩展。


3

上面未涉及的相关案例。引用一个空变量似乎改变了事情test -n。在的info文本中专门给出了一个示例coreutils,但并未真正说明:

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.

我希望听到详细的解释。我的测试证实了这一点,现在为所有字符串测试引用我的变量,以避免产生-z-n返回相同的结果。

$ 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

好吧,我知道变量的封装可以帮助您处理以下内容:

${groups%example}

或类似的语法,您想在返回值之前先对变量进行操作。

现在,如果您看到自己的代码,所有的魔术都在里面

${groups[@]}

魔术在那里,因为你不能只写: $groups[@]

您要将变量放在内,{}因为您想使用特殊字符[]@。您不能仅仅命名或调用变量:@或者something[]因为这些是其他操作和名称的保留字符。


这未能指出双引号的非常重要的意义,以及没有双引号的代码基本上是如何被破坏的。
Tripleee
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.