注意
由于bash 标签,我给出了一个以 Bash 为重点的答案。
简答
只要你在 Bash 中只处理命名变量,这个函数应该总是告诉你变量是否已经被设置,即使它是一个空数组。
variable-is-set() {
declare -p "$1" &>/dev/null
}
为什么会这样
在 Bash(至少早于 3.0)中,如果 var 是已声明/设置的变量,则 declare -p var 输出一个 declare 命令,将变量 var 设置为其当前类型和值是,并返回状态码0(成功)。如果var 未声明,则declare -p var 向stderr 输出错误消息并返回状态码1。使用&>/dev/null,将常规stdout 和stderr 输出重定向到/dev/null,永远不会被看到,并且不更改状态代码。因此该函数只返回状态码。
为什么其他方法(有时)在 Bash 中失败
-
[ -n "$var" ]: 这仅检查${var[0]} 是否为非空。 (在 Bash 中,$var 与 ${var[0]} 相同。)
-
[ -n "${var+x}" ]: 这只检查是否设置了${var[0]}。
-
[ "${#var[@]}" != 0 ]: 这只检查是否设置了至少一个$var 的索引。
当这个方法在 Bash 中失败时
这仅适用于命名变量(包括$_),不适用于某些特殊变量($!、$@、$#、$$、$*、$?、$-、@ 987654352@,$1,$2,...,以及任何我可能已经忘记的)。由于这些都不是数组,POSIX 风格的[ -n "${var+x}" ] 适用于所有这些特殊变量。但请注意不要将其包装在函数中,因为许多特殊变量在调用函数时会更改值/存在。
外壳兼容性说明
如果您的脚本包含数组,并且您正试图使其与尽可能多的 shell 兼容,那么请考虑使用 typeset -p 而不是 declare -p。我读过 ksh 只支持前者,但无法对此进行测试。我确实知道 Bash 3.0+ 和 Zsh 5.5.1 都支持 typeset -p 和 declare -p,只是其中一个可以替代另一个。但是我没有测试过这两个关键字之外的差异,也没有测试过其他的shell。
如果您需要脚本与 POSIX sh 兼容,则不能使用数组。没有数组,[ -n "{$var+x}" ] 可以工作。
Bash 中不同方法的比较代码
此函数取消设置变量var、evals 传递的代码,运行测试以确定var 是否由evald 代码设置,最后显示不同测试的结果状态代码。
我将跳过 test -v var、[ -v var ] 和 [[ -v var ]],因为它们产生的结果与 POSIX 标准 [ -n "${var+x}" ] 相同,但需要 Bash 4.2+。我也跳过了 typeset -p,因为它与我测试过的 shell(Bash 3.0 到 5.0 和 Zsh 5.5.1)中的 declare -p 相同。
is-var-set-after() {
# Set var by passed expression.
unset var
eval "$1"
# Run the tests, in increasing order of accuracy.
[ -n "$var" ] # (index 0 of) var is nonempty
nonempty=$?
[ -n "${var+x}" ] # (index 0 of) var is set, maybe empty
plus=$?
[ "${#var[@]}" != 0 ] # var has at least one index set, maybe empty
count=$?
declare -p var &>/dev/null # var has been declared (any type)
declared=$?
# Show test results.
printf '%30s: %2s %2s %2s %2s\n' "$1" $nonempty $plus $count $declared
}
测试用例代码
请注意,如果未将变量声明为关联数组,则由于 Bash 将非数字数组索引视为“0”,因此测试结果可能会出乎意料。此外,关联数组仅在 Bash 4.0+ 中有效。
# Header.
printf '%30s: %2s %2s %2s %2s\n' "test" '-n' '+x' '#@' '-p'
# First 5 tests: Equivalent to setting 'var=foo' because index 0 of an
# indexed array is also the nonindexed value, and non-numerical
# indices in an array not declared as associative are the same as
# index 0.
is-var-set-after "var=foo" # 0 0 0 0
is-var-set-after "var=(foo)" # 0 0 0 0
is-var-set-after "var=([0]=foo)" # 0 0 0 0
is-var-set-after "var=([x]=foo)" # 0 0 0 0
is-var-set-after "var=([y]=bar [x]=foo)" # 0 0 0 0
# '[ -n "$var" ]' fails when var is empty.
is-var-set-after "var=''" # 1 0 0 0
is-var-set-after "var=([0]='')" # 1 0 0 0
# Indices other than 0 are not detected by '[ -n "$var" ]' or by
# '[ -n "${var+x}" ]'.
is-var-set-after "var=([1]='')" # 1 1 0 0
is-var-set-after "var=([1]=foo)" # 1 1 0 0
is-var-set-after "declare -A var; var=([x]=foo)" # 1 1 0 0
# Empty arrays are only detected by 'declare -p'.
is-var-set-after "var=()" # 1 1 1 0
is-var-set-after "declare -a var" # 1 1 1 0
is-var-set-after "declare -A var" # 1 1 1 0
# If 'var' is unset, then it even fails the 'declare -p var' test.
is-var-set-after "unset var" # 1 1 1 1
测试输出
标题行中的测试助记符分别对应[ -n "$var" ]、[ -n "${var+x}" ]、[ "${#var[@]}" != 0 ]和declare -p var。
test: -n +x #@ -p
var=foo: 0 0 0 0
var=(foo): 0 0 0 0
var=([0]=foo): 0 0 0 0
var=([x]=foo): 0 0 0 0
var=([y]=bar [x]=foo): 0 0 0 0
var='': 1 0 0 0
var=([0]=''): 1 0 0 0
var=([1]=''): 1 1 0 0
var=([1]=foo): 1 1 0 0
declare -A var; var=([x]=foo): 1 1 0 0
var=(): 1 1 1 0
declare -a var: 1 1 1 0
declare -A var: 1 1 1 0
unset var: 1 1 1 1
总结
-
declare -p var &>/dev/null 对于在 Bash 中测试命名变量至少是 3.0 版(100%?)可靠。
-
[ -n "${var+x}" ] 在符合 POSIX 的情况下是可靠的,但不能处理数组。
- 存在用于检查变量是否为非空以及检查其他 shell 中声明的变量的其他测试。但这些测试既不适合 Bash 也不适合 POSIX 脚本。