arrays - 为什么空数组在 bash 中被视为未设置?

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

最近,我设置了微软的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 上使用适用于 Linux 的 Windows 子系统。其他信息:

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

最佳答案

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

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

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

如果设置了 argv 则扩展为 "${argv[@]}",否则没有任何内容(注意外部扩展未被引用)。

在 Bash 4.4 中,行为发生了变化,如 CHANGES 中所述,从 bash-4.4-beta2 到 bash-4.4-rc2,作为一个“新特性”:

Using ${a[@]} or ${a[*]} with an array without any assigned elements when the nounset option is enabled no longer throws an unbound variable error.

关于arrays - 为什么空数组在 bash 中被视为未设置?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48394251/

相关文章:

c++ - 从其行和列的给定总和生成一个二维整数数组

bash - 导出 `/bin/bash -c` 内的变量

bash - 从 grep 输出创建文件时,为什么我会得到一些额外的、奇怪的字符?

linux - 在现有子目录中递归创建目录树

c++ - 程序在图形 shell 中的运行速度比在命令 shell 中快

linux - Grep 一件事然后从下面的行中删除

C++ Array of Arrays,分别定义部分

c - 二维数组中穿过障碍物(地雷)的最短路径(C 编程)

c# - 如何返回包含枚举值的字符串 []

linux - 使用 Sed/Awk 仅删除 Linux 文件中的字母字符行