为什么在bash中将空数组视为未设置?

gre*_*Dot 8 arrays bash shell windows-subsystem-for-linux

最近,我在计算机上设置了Microsoft的Windows Linux子系统。它只是模仿Linux环境和东西。基本上是Cygwin,但与底层Windows系统的连接要好一些。从Cygwin切换到WSL之后,我遇到了一个问题。我不知道它是否特定于Windows的实现,但这在Cygwin中不会发生。

为了更快地捕获代码中的错误,我开始使用bash的set -u选项,该选项使外壳“在替换时将未设置的变量视为错误”。否则,bash在扩展它们时会将未设置的变量视为设置为空字符串的变量。

但是,这对数组有一个奇怪的意外结果(至少在WSL上):

Me@Computer:~$ set -u
==>
Me@Computer:~$ declare -p array
==> bash: declare: array: not found
Me@Computer:~$ array=( )
==>
Me@Computer:~$ declare -p array
==> declare -a array='()'
Me@Computer:~$ echo "${array[@]}"       # Expands to "echo" (with 0 args), right?
==> bash: array[@]: unbound variable    # Wrong! wtf, bash??
Run Code Online (Sandbox Code Playgroud)

从的输出中可以看到declare -p array,bash 确实识别出数组为空和数组未设置之间的区别-直到需要真正对其进行扩展时,bash才适合。我知道bash 特别对待@and *变量,甚至在引用时也是如此,因此我尝试了很多东西。无效:

Me@Computer:~$ echo "${array[@]}"
==> bash: array[@]: unbound variable
Me@Computer:~$ echo "${array[*]}"
==> bash: array[*]: unbound variable
Me@Computer:~$ echo ${array[@]}
==> bash: array[@]: unbound variable
Me@Computer:~$ echo ${array[*]}
==> bash: array[*]: unbound variable
Run Code Online (Sandbox Code Playgroud)

奇怪的是,我可以访问数组的索引数组。但是,bash存在相反的问题,因为当要求输入未设置的数组的索引时,它也会成功执行:

Me@Computer:~$ echo "${!array[@]}"
==>
Me@Computer:~$ echo "${!unset_array[@]}"
==>
Run Code Online (Sandbox Code Playgroud)

(以上适用于数组扩展格式的所有变体。)

最令人沮丧的是,我什至无法访问空数组的长度:

Me@Computer:~$ echo "${#array[@]}"
==> bash: array[@]: unbound variable
Run Code Online (Sandbox Code Playgroud)

格式的所有变体也都失败了。

有人知道为什么会这样吗?这是一个错误,还是这种预期的行为?如果是后者,动机是什么?有什么方法可以禁用此行为,让我保持set -u


解决方法:

我利用位置参数不受此现象影响这一事实,找到了一个非常糟糕的解决方法。如果有人找到更好的,请告诉我!

Me@Computer:~$ tmp=( "$@" )                    # Stash the real positional params; we need that array
Me@Computer:~$ set --                          # "$@" is now empty.
Me@Computer:~$ example_cmd "${array[@]-$@}"    # Now expands w/out error *and* w/ the right number of args
Me@Computer:~$ set -- "${tmp-$@}"              # Put the positional params back where we found them
Me@Computer:~$ unset tmp                       # Cleaning up after ourselves
Run Code Online (Sandbox Code Playgroud)

(请注意,重置位置参数时,仍然需要使用欺骗手段,以防万一它们原来是空的。)每次使用可能为空的数组时,都需要执行这些扭曲操作。


其他说明:

  • test -v还认为空数组未设置,这与不同declare -p
  • 关联数组也会出现相同的问题。
  • 我尝试使用declare(即declare -a array=( ))初始化数组,但没有任何改变。
  • 幸运的是,位置参数数组似乎不受此现象的影响。
  • 我想到了只"${array[@]-}"在需要访问数组时使用它,但这在所有情况下都行不通。"${array[@]}",双引号时,应该扩展为每个数组元素的单独单词;空数组的话,应该被扩展为0的话(比较set -- "$@";echo $#set -- "$*";echo $#)。"${array[@]-}",但是扩展为一个单词,即空字符串。

版本和环境信息:

就像我在顶部说的那样,我在Windows 10上使用Linux的Windows子系统。其他信息:

Me@Computer:~$ bash --version
==> GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)
    ...
Me@Computer:~$ echo "$-"
==> himuBCH
Run Code Online (Sandbox Code Playgroud)

Ben*_* W. 9

这并不特定于是否在 WSL 下运行 Bash,而是取决于 Bash 版本。

该行为已被报告为 Bash 4.1 的错误,但被认为是预期行为。Chet 还指出 和 的不同行为$@$*因为 POSIX 强制要求它。当时推荐的解决方法与安迪的评论类似,是:

echo ${argv[0]+"${argv[@]}"}
Run Code Online (Sandbox Code Playgroud)

它扩展为"${argv[@]}"ifargv已设置,否则没有其他内容(请注意外部扩展未被引用)。

在 Bash 4.4 中,行为发生了变化,如CHANGES中所述,从 bash-4.4-beta2 更改为 bash-4.4-rc2,作为“新功能”:

当启用该选项时,使用${a[@]}${a[*]}使用没有任何分配元素的数组nounset不会再引发未绑定变量错误。