使用`set -u`猛击空数组扩展

Iva*_*sov 89 bash

我正在写一个bash脚本set -u,我有一个空数组扩展的问题:bash似乎在扩展期间将空数组视为未设置的变量:

$ set -u
$ arr=()
$ echo "foo: '${arr[@]}'"
bash: arr[@]: unbound variable
Run Code Online (Sandbox Code Playgroud)

(declare -a arr也没帮助.)

对此的常见解决方案是使用${arr[@]-}替代,从而替换空字符串而不是("未定义")空数组.然而,这不是一个好的解决方案,因为现在你无法辨别出一个带有一个空字符串的数组和一个空数组.(@ -expansion在bash中是特殊的,它扩展"${arr[@]}""${arr[0]}" "${arr[1]}" …,这使它成为构建命令行的完美工具.)

$ countArgs() { echo $#; }
$ countArgs a b c
3
$ countArgs
0
$ countArgs ""
1
$ brr=("")
$ countArgs "${brr[@]}"
1
$ countArgs "${arr[@]-}"
1
$ countArgs "${arr[@]}"
bash: arr[@]: unbound variable
$ set +u
$ countArgs "${arr[@]}"
0
Run Code Online (Sandbox Code Playgroud)

那么有没有办法解决这个问题,除了检查一个数组的长度if(参见下面的代码示例),或关闭-u该短片的设置?

if [ "${#arr[@]}" = 0 ]; then
   veryLongCommandLine
else
   veryLongCommandLine "${arr[@]}"
fi
Run Code Online (Sandbox Code Playgroud)

更新:bugs由于ikegami的解释删除了标签.

ike*_*ami 74

首先,它不是一个错误.

如果已为下标指定值,则认为数组变量已设置.空字符串是有效值.

没有为下标分配值,因此未设置数组.


有一个条件,您可以使用内联来实现您想要的:使用${arr[@]+"${arr[@]}"}而不是"${arr[@]}".

$ bash --version | head -n 1
GNU bash, version 4.4.19(1)-release (x86_64-pc-linux-gnu)

$ set -u

$ arr=()

$ echo "foo: '${arr[@]}'"
foo: ''
Run Code Online (Sandbox Code Playgroud)

用bash 4.2.25和4.3.11测试

  • @Per Cerderberg,不行.`unset arr`,`arr [1] = a`,`args $ {arr +"$ {arr [@]}"}`vs`args $ {arr [@] +"$ {arr [@]}"} ` (3认同)
  • 谁能解释它的工作原理和原因?我对`[@] +`的实际作用以及为什么第二个`$ {arr [@]}`不会引起未绑定错误感到困惑。 (2认同)
  • $ {parameter + word}`仅在未设置`parameter`时扩展`word`。 (2认同)
  • `$ {arr +"$ {arr [@]}"}`更短,似乎也能正常工作. (2认同)

dim*_*414 40

唯一的安全习惯用法${arr[@]+"${arr[@]}"}

这已经是池上回答中的建议,但在这个线程中有很多错误信息和猜测。其他模式(例如${arr[@]-}或 )在 Bash 的所有主要版本${arr[@]:0}都不是安全的。

如下表所示,在所有现代 Bash 版本中唯一可靠的扩展是${arr[@]+"${arr[@]}"}(column +")。值得注意的是,Bash 4.2 中的其他几个扩展失败了,包括(不幸的是)较短的${arr[@]:0}习惯用法,它不仅会产生错误的结果,而且实际上失败了。如果您需要支持 4.4 之前的版本,尤其是 4.2,这是唯一可行的习惯用法。

跨版本不同习语的屏幕截图

不幸的+是,乍一看看起来相同的其他扩展确实会发出不同的行为。例如,使用:+代替+(:+"在表中) 不起作用,因为:-expansion将具有单个空元素 ( (''))的数组视为“null”,因此不会(始终)扩展为相同的结果。

引述全膨胀,而不是嵌套阵列("${arr[@]+${arr[@]}}""+在表中),我会预期是大致相当于,在4.2类似地不安全的。

您可以在此要点中查看生成此数据的代码以及 bash 的其他几个版本的结果。

  • 我没有看到您测试“${arr[@]}”。我错过了什么吗?据我所知,它至少在“5.x”中有效。 (2认同)
  • @x-yuri 是的,Bash 4.4 解决了这个问题;如果您知道您的脚本只能在 4.4+ 上运行,但许多系统仍在早期版本上,则不需要使用此模式。 (2认同)

小智 23

@ ikegami接受的答案是巧妙的错误!正确的咒语是${arr[@]+"${arr[@]}"}:

$ countArgs () { echo "$#"; }
$ arr=('')
$ countArgs "${arr[@]:+${arr[@]}}"
0   # WRONG
$ countArgs ${arr[@]+"${arr[@]}"}
1   # RIGHT
$ arr=()
$ countArgs ${arr[@]+"${arr[@]}"}
0   # Let's make sure it still works for the other case...
Run Code Online (Sandbox Code Playgroud)

  • 这在我的回答中已经得到了解决。(事实上​​,我确信我之前已经对此答案发表过评论?!) (2认同)

Jay*_*yen 14

对于那些不想复制arr [@]并且可以使用空字符串的人来说,这可能是另一种选择

echo "foo: '${arr[@]:-}'"
Run Code Online (Sandbox Code Playgroud)

去测试:

set -u
arr=()
echo a "${arr[@]:-}" b # note two spaces between a and b
for f in a "${arr[@]:-}" b; do echo $f; done # note blank line between a and b
arr=(1 2)
echo a "${arr[@]:-}" b
for f in a "${arr[@]:-}" b; do echo $f; done
Run Code Online (Sandbox Code Playgroud)

  • 如果你只是插入变量,这将有效,但是如果你想在`for`中使用数组,当数组未定义/定义为空时,最终会有一个空字符串,你可能想要的地方如果未定义数组,则循环体不会运行. (9认同)

agg*_*g3l 13

事实证明,在最近发布的(2016/09/16)bash 4.4中已经改变了阵列处理(例如在Debian stretch中可用).

$ bash --version | head -n1
bash --version | head -n1
GNU bash, version 4.4.0(1)-release (x86_64-pc-linux-gnu)
Run Code Online (Sandbox Code Playgroud)

现在空数组扩展不会发出警告

$ set -u
$ arr=()
$ echo "${arr[@]}"

$ # everything is fine
Run Code Online (Sandbox Code Playgroud)


kev*_*rpe 6

@ ikegami的答案是正确的,但我认为语法"${arr[@]:+${arr[@]}}"可怕.如果你使用长数组变量名,它开始看起来比平时更快.

试试这个:

$ set -u

$ count() { echo $# ; } ; count x y z
3

$ count() { echo $# ; } ; arr=() ; count "${arr[@]}"
-bash: abc[@]: unbound variable

$ count() { echo $# ; } ; arr=() ; count "${arr[@]:0}"
0

$ count() { echo $# ; } ; arr=(x y z) ; count "${arr[@]:0}"
3
Run Code Online (Sandbox Code Playgroud)

看起来Bash数组切片运算符非常宽容.

那么为什么Bash如此难以处理数组的边缘情况呢? 叹. 我不能保证你的版本会允许滥用数组切片操作符,但它对我来说很有用.

警告:我使用GNU bash, version 3.2.25(1)-release (x86_64-redhat-linux-gnu) 你的里程可能会有所不同.

  • 池上最初有这个,但删除它,因为它是不可靠的,在理论上(没有理由这应该工作)和实践(OP的版本的bash不接受它). (7认同)

小智 6

确实"有趣"的不一致.

此外,

$ set -u
$ echo $#
0
$ echo "$1"
bash: $1: unbound variable   # makes sense (I didn't set any)
$ echo "$@" | cat -e
$                            # blank line, no error
Run Code Online (Sandbox Code Playgroud)

虽然我同意当前的行为可能不是@ikegami解释的错误,IMO我们可以说错误在定义("set")本身,和/或它不一致地应用的事实.手册页中的前一段说

... ${name[@]}将名称的每个元素扩展为单独的单词.如果没有数组成员,则${name[@]}展开为空.

这完全符合它所说的关于位置参数扩展的说法"$@".并不是说数组和位置参数的行为没有其他的不一致......但对我来说,并没有暗示这两个细节之间应该是不一致的.

继续,

$ arr=()
$ echo "${arr[@]}"
bash: arr[@]: unbound variable   # as we've observed.  BUT...
$ echo "${#arr[@]}"
0                                # no error
$ echo "${!arr[@]}" | cat -e
$                                # no error
Run Code Online (Sandbox Code Playgroud)

所以arr[]不是那么没有约束我们不能得到它的元素(0)或其键的(空)列表的计数?对我来说,这些是明智的,也是有用的 - 唯一的异常似乎是${arr[@]}(和${arr[*]})扩张.