用`set -u`打击空数组扩展

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

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

declare -a arr也没有帮助。)

一个常见的解决scheme是使用${arr[@]-}来代替空string,而不是(“undefined”)空数组。 然而,这不是一个好的解决scheme,因为现在你不能在一个单一的空string数组和空数组之间进行区分。 (@扩展在bash中是特殊的,它将"${arr[@]}"扩展为"${arr[0]}" "${arr[1]}" … ,这使得它成为构build命令行。)

 $ countArgs() { echo $#; } $ countArgs abc 3 $ countArgs 0 $ countArgs "" 1 $ brr=("") $ countArgs "${brr[@]}" 1 $ countArgs "${arr[@]-}" 1 $ countArgs "${arr[@]}" bash: arr[@]: unbound variable $ set +u $ countArgs "${arr[@]}" 0 

那么是否有解决这个问题的方法,除了检查if (参见下面的代码示例)中数组的长度,还是closures该短片的-u设置?

 if [ "${#arr[@]}" = 0 ]; then veryLongCommandLine else veryLongCommandLine "${arr[@]}" fi 

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

首先,这不是一个错误。

如果下标已被赋值,则认为数组variables被设置。 空string是一个有效的值。

没有下标已经被赋值,所以数组没有被设置。


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

 $ function args { perl -E'say 0+@ARGV; say "$_: $ARGV[$_]" for 0..$#ARGV' -- "$@" ; } $ set -u $ arr=() $ args "${arr[@]}" -bash: arr[@]: unbound variable $ args ${arr[@]+"${arr[@]}"} 0 $ arr=("") $ args ${arr[@]+"${arr[@]}"} 1 0: $ arr=(abc) $ args ${arr[@]+"${arr[@]}"} 3 0: a 1: b 2: c 

用bash 4.2.25和4.3.11testing

@ 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... 

对于那些不想复制arr [@]并且没有空string的人来说,这可能是另一种select

 echo "foo: '${arr[@]:-}'" 

去testing:

 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 

@ ikegami的答案是正确的,但我认为语法"${arr[@]:+${arr[@]}}"可怕。 如果你使用长数组variables名称,它开始比平时更快速地查看意大利面条。

试试这个:

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

它看起来像Bash数组切片运算符是非常宽容的。

那么Bash为什么要处理arrays的边缘情况那么困难呢? 叹。 我不能保证你的版本将允许这样的数组切片运算符的滥用,但它对我来说很花哨。

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

“有趣”的确不一致。

此外,

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

虽然我同意目前的行为可能不是@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 

所以arr[]不是那么没有约束的,我们无法得到它的元素(0)或者它的一个(空)列表的数量吗? 对我来说,这些是明智的和有用的 – 唯一的exception值似乎是${arr[@]} (和${arr[*]} )扩展。

这里有几个方法来做这样的事情,一个使用哨兵和另一个使用条件附加:

 #!/bin/bash set -o nounset -o errexit -o pipefail countArgs () { echo "$#"; } arrA=( sentinel ) arrB=( sentinel "{1..5}" "./*" "with spaces" ) arrC=( sentinel '$PWD' ) cmnd=( countArgs "${arrA[@]:1}" "${arrB[@]:1}" "${arrC[@]:1}" ) echo "${cmnd[@]}" "${cmnd[@]}" arrA=( ) arrB=( "{1..5}" "./*" "with spaces" ) arrC=( '$PWD' ) cmnd=( countArgs ) # Checks expansion of indices. [[ ! ${!arrA[@]} ]] || cmnd+=( "${arrA[@]}" ) [[ ! ${!arrB[@]} ]] || cmnd+=( "${arrB[@]}" ) [[ ! ${!arrC[@]} ]] || cmnd+=( "${arrC[@]}" ) echo "${cmnd[@]}" "${cmnd[@]}" 

事实certificate,最近发布的(2016/09/16)bash 4.4(例如在Debian中可用)已经改变了数组处理。

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

现在空arrays扩展不会发出警告

 $ set -u $ arr=() $ echo "${arr[@]}" $ # everything is fine 

而build议的解决scheme(真的很好,感谢它)与bash-4.4失败:

 $ set -u $ arr2=() $ arr2=( ${arr2[@] + "${arr2[@]}"} 'foo' ) bash: ${arr2[@] + "$arr2[@]"}: bad substitution 

有没有人得到(或多或less)独立于版本的解决scheme的build议,没有额外的数组长度或bash版本检查? 我还在自己调查最新的bash变化。

编辑

不正确的语法是我提到的问题的来源。 一个人应该确保下面的语法结构没有任何额外的空间内(特别是“+”),尽pipe它往往会增加它的可读性。 该语法(额外的空间)为bash 4.3工作,但没有自bash 4.4

 arr=( ${arr[@]+"${arr[@]}"} 'foo' ) 

有趣的不一致; 这可以让你定义一些“不考虑设置”的东西,但是在declare -p的输出中却显示出来了

 arr=() set -o nounset echo $arr[@] => -bash: arr[@]: unbound variable declare -p arr => declare -a arr='()' 

最简单和兼容的方式似乎是:

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