用`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[@]-}'"