如何使用 nounset shell 选项测试 4.2 版之前的 Bash 中是否定义了变量?

Tim*_*ske 22 bash test

对于“GNU bash,版本 4.2”之前的 Bash 版本,是否有任何等效-vtest命令选项替代方案?例如:

shopt -os nounset
test -v foobar && echo foo || echo bar
# Output: bar
foobar=
test -v foobar && echo foo || echo bar
# Output: foo
Run Code Online (Sandbox Code Playgroud)

Gil*_*il' 42

可移植到所有 POSIX shell:

if [ -n "${foobar+1}" ]; then
  echo "foobar is defined"
else
  echo "foobar is not defined"
fi
Run Code Online (Sandbox Code Playgroud)

作出这样的${foobar:+1},如果你想治疗foobar以同样的方式是否为空或没有定义。您还可以${foobar-}foobar未定义时获取空字符串,foobar否则获取空字符串(或在 之后放置任何其他默认值-)。

在 ksh 中,如果foobar已声明但未定义,如在 中typeset -a foobar,则${foobar+1}扩展为空字符串。

Zsh 没有声明但未设置的变量:typeset -a foobar创建一个空数组。

在 bash 中,数组的行为方式不同且令人惊讶。${a+1}仅扩展为1如果a是非空数组,例如

typeset -a a; echo ${a+1}    # prints nothing
e=(); echo ${e+1}            # prints nothing!
f=(''); echo ${f+1}          # prints 1
Run Code Online (Sandbox Code Playgroud)

相同的原则适用于关联数组:如果数组变量具有非空索引集,则将其视为已定义。

测试是否已定义任何类型的变量的另一种特定于 bash 的方法是检查它是否在. 这与定义不同,报告空数组,但报告声明但未分配的变量 ( ) 为未定义。${!PREFIX*}${foobar+1}unset foobar; typeset -a foobar

case " ${!foobar*} " in
  *" foobar "*) echo "foobar is defined";;
  *) echo "foobar is not defined";;
esac
Run Code Online (Sandbox Code Playgroud)

这等效于测试typeset -p foobaror的返回值declare -p foobar,除了typeset -p foobar在声明但未分配的变量上失败。

在 bash 中,就像在 ksh 中一样,set -o nounset; typeset -a foobar; echo $foobar在尝试扩展未定义变量时会触发错误foobar。与 ksh 不同,set -o nounset; foobar=(); echo $foobar(或echo "${foobar[@]}") 也会触发错误。

请注意,在此处描述的所有情况下,${foobar+1}扩展为空字符串当且仅当$foobar会导致set -o nounset.


Tim*_*ske 7

总结 Gilles 的回答,我制定了以下规则:

  1. 使用[[ -v foobar ]]中的Bash版本> = 4.2变量。
  2. 使用declare -p foobar &>/dev/null中的Bash版本<4.2数组变量。
  3. 分别将(( ${foo[0]+1} ))(( ${bar[foo]+1} ))用于索引 ( -a) 和键控 ( -A) 数组 ( declare) 的下标。选项 1 和 2 在这里不起作用。