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

时间:2018-01-23 04:27:29

标签: arrays bash shell windows-subsystem-for-linux

最近,我在我的计算机上设置了Microsoft的Windows Subsystem for Linux。它只是模拟Linux环境和东西;基本上,它是Cygwin,但更好地连接到底层的Windows系统。然而,在从Cygwin切换到WSL之后,我遇到了一个问题。我不知道它是否特别适用于Windows'是否实现,但这并不是在Cygwin中发生的。

为了更快地捕获代码中的错误,我已经开始使用bash的set -u选项,这会导致shell在替换时将未设置的变量视为错误"如果没有这个,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??

正如您从declare -p array的输出中看到的那样,bash 确实识别数组为空且数组未设置之间的区别 - 直到实际扩展它为止bash引发了一场健康。我知道bash特别对待@*变量,当引用它时更是如此,所以我尝试了很多东西。没有任何作用:

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

奇怪的是,我可以访问数组的索引数组;然而,bash然后有一个相反的问题,当它被要求输入未设置数组的索引时成功:

Me@Computer:~$ echo "${!array[@]}"
==>
Me@Computer:~$ echo "${!unset_array[@]}"
==>

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

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

Me@Computer:~$ echo "${#array[@]}"
==> bash: array[@]: unbound variable

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

有谁知道为什么会这样?这是一个错误,还是这种预期的行为?如果是后者,那么动机是什么?是否有任何方法可以禁用此行为,以便我保留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

(请注意,在重置位置参数时仍需要使用技巧,以防它们本身最初为空。)每次使用可能为空的数组时都需要执行这些扭曲。

其他说明:

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

版本&环境信息:

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

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

1 个答案:

答案 0 :(得分:2)

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

对于Bash 4.1,该行为为reported as a bug,但为considered intended behaviour。切特还指出$@$*的不同行为是因为POSIX要求它。与Andy的评论类似,当时建议的解决方法是:

echo ${argv[0]+"${argv[@]}"}

如果设置了"${argv[@]}",则扩展为argv,否则扩展为其他值(请注意外部扩展名未引用)。

CHANGES中记录的行为在Bash 4.4中从bash-4.4-beta2变为bash-4.4-rc2,作为“新功能”:

  

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