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

标题是什么意思: 在 {}"""{}中封装一个变量意味着什么?我还没能在网上找到任何关于这个问题的解释——除了使用符号之外,我还不能参考它们,因为它们没有产生任何结果。

这里有一个例子:

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}"。如果有人能解释一下,我会很感激的。

作为一个额外的问题——是否有人知道引用这些封装的公认方法?

113197 次浏览

我知道封装一个变量可以帮助你处理这样的事情:

${groups%example}

或者类似的语法,在返回值之前要对变量执行一些操作。

现在,如果你看到你的代码,所有的魔法都在里面

${groups[@]}

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

您将变量放在 {}中,因为您希望使用特殊字符 []@。不能仅仅命名或调用变量: @something[],因为它们是用于其他操作和名称的保留字符。

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]

以及数组扩展。

您需要区分数组和简单变量,您的示例使用的是数组。

对于普通变量:

  • $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脚本中迭代参数

DR

您给出的所有示例都是 Bash外壳扩张的变体。扩展以特定的顺序发生,有些还有特定的用例。

大括号作为标记分隔符

${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 如何标记输入有关。在下面的示例中,考虑 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 ]扩展到位置参数,从1开始。当扩展发生在双引号中时,每个参数都扩展为一个单独的单词。”
  2. 在数组中,“[ i ]如果单词是双引号,则 ${name[*]}展开为单个单词,每个数组成员的值由 IFS 变量的第一个字符分隔,而 ${name[@]}将名称的每个元素展开为单独的单词。”

你可以看到如下的行动:

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


$ count_params "$@"
3


$ count_params "$*"
1

当变量引用带空格或特殊字符的值时,引号扩展的使用非常重要,这可能会防止 shell 按照您希望的方式拆分单词。有关引号在 Bash 中如何工作的更多信息,请参见 引用

大括号($var${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

引文($var"$var""${var}")

当你在变量周围添加双引号时,你告诉 shell 把它当作一个单词,即使它包含空格:

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

$var${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 中的引用提供了很好的介绍。

数组($var$var[@]${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[@]}"${foo[@]}")

你没有问这个问题,但这是一个微妙的差异,这是很好的了解。如果数组中的元素可以包含空格,则需要使用双引号,以便将每个元素作为单独的“ word:”

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

上述未涉及的相关案件。引用一个空变量似乎会改变 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