我如何知道是否在Bash中设置了变量?
例如,如何检查用户是否向函数提供了第一个参数?
function a {
# if $1 is set ?
}
我如何知道是否在Bash中设置了变量?
例如,如何检查用户是否向函数提供了第一个参数?
function a {
# if $1 is set ?
}
当前回答
(通常)正确的方式
if [ -z ${var+x} ]; then echo "var is unset"; else echo "var is set to '$var'"; fi
其中${var+x}是一个参数扩展,如果var未设置,则其求值结果为空,否则替换字符串x。
报价摘要
可以省略引号(所以我们可以说${var+x}而不是“${var+x}”),因为这种语法和用法保证它只扩展到不需要引号的对象(因为它要么扩展到x(不包含换行符,因此不需要引号),要么不扩展到任何对象(这导致[-z],它方便地计算出与[-z“”]相同的值(true))。
然而,虽然可以安全地省略引号,而且这并不是所有人都能立即看到的(这对第一位也是主要Bash编码者的引号解释作者来说都不明显),但有时最好使用引号[-z“${var+x}”]来编写解决方案,代价很小,可能会导致O(1)速度损失。第一作者还将此添加为使用此解决方案的代码旁边的注释,给出了此答案的URL,现在还包括了为什么可以安全地省略引号的解释。
(经常)错误的方式
if [ -z "$var" ]; then echo "var is blank"; else echo "var is set to '$var'"; fi
这通常是错误的,因为它无法区分未设置的变量和设置为空字符串的变量。也就是说,如果var='',则上述解决方案将输出“var为空”。
在用户必须指定扩展名或其他财产列表的情况下,必须区分unset和“设置为空字符串”,如果不指定,则默认为非空值,而指定空字符串应使脚本使用空扩展名或附加财产列表。
然而,这种区别在每种情况下可能都不是必不可少的。在这种情况下,[-z“$var”]就可以了。
其他回答
Note
由于Bash标签,我给出了一个非常注重Bash的答案。
简短的回答
只要您只处理Bash中的命名变量,这个函数应该始终告诉您变量是否已设置,即使它是一个空数组。
variable-is-set() {
declare -p "$1" &>/dev/null
}
为什么这样做
在Bash(至少可以追溯到3.0)中,如果var是一个声明的/set变量,那么declare-p var输出一个声明命令,将变量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中失败时
这只适用于命名变量(包括$_),而不适用于某些特殊变量($!,$@,$#,$$,$*,$?,$-,$0,$1,$2,…,以及我可能忘记的任何变量)。由于这些都不是数组,POSIX样式[-n“${var+x}”]适用于所有这些特殊变量。但请注意不要将其包装在函数中,因为在调用函数时,许多特殊变量会更改值/存在性。
外壳兼容性说明
如果您的脚本有数组,并且试图使其与尽可能多的shell兼容,那么考虑使用typeset-p而不是declare-p。我读到ksh只支持前者,但还没有能够测试这一点。我知道Bash 3.0+和Zsh 5.5.1都支持typeset-p和declare-p,不同之处仅在于其中一个是另一个的替代。但除了这两个关键字之外,我还没有测试过它们的差异,也没有测试过其他外壳。
如果您需要脚本与POSIX sh兼容,那么就不能使用数组。如果没有数组,[-n“{$var+x}”]可以工作。
Bash中不同方法的比较代码
此函数取消设置变量var,对传递的代码求值,运行测试以确定var是否由evald代码设置,最后显示不同测试的结果状态代码。
我跳过了测试-vvar、[-vvar]和[[-vvar]],因为它们产生了与POSIX标准[-n“${var+x}”]相同的结果,同时需要Bash 4.2+。我也跳过了typeset-p,因为它与我测试过的shell(Bash 3.0到5.0和Zsh 5.5.1)中的声明-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],并分别声明-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
总结
声明-p var&>/dev/null对于测试Bash中的命名变量是(100%?)可靠的,至少从3.0开始。[-n“${var+x}”]在符合POSIX的情况下是可靠的,但不能处理数组。其他测试用于检查变量是否为非空,以及检查其他shell中声明的变量。但这些测试既不适合Bash也不适合POSIX脚本。
if [ "$1" != "" ]; then
echo \$1 is set
else
echo \$1 is not set
fi
尽管对于参数,通常最好测试$#,我认为这是参数的数量。
if [ $# -gt 0 ]; then
echo \$1 is set
else
echo \$1 is not set
fi
要检查变量是否设置为非空值,请使用[-n“$x”],正如其他人已经指出的那样。
大多数情况下,最好将具有空值的变量与未设置的变量以相同的方式处理。但如果需要,您可以区分这两个:[-n“${x+set}”](如果设置了x,则“${x+set}”扩展为set,如果未设置x,则扩展为空字符串)。
要检查是否传递了参数,请测试$#,这是传递给函数(或不在函数中时传递给脚本)的参数数(请参见Paul的答案)。
在现代版本的Bash(我认为是4.2或更高版本;我不确定)上,我会这样做:
if [ ! -v SOMEVARIABLE ] #note the lack of a $ sigil
then
echo "Variable is unset"
elif [ -z "$SOMEVARIABLE" ]
then
echo "Variable is set to an empty string"
else
echo "Variable is set to some string"
fi
为了明确回答OP关于如何确定变量是否已设置的问题,Lionel的回答是正确的:
if test "${name+x}"; then
echo 'name is set'
else
echo 'name is not set'
fi
这个问题已经有很多答案,但没有一个提供真正的布尔表达式来明确区分变量值。
以下是我得出的一些明确表达:
+-----------------------+-------------+---------+------------+
| Expression in script | name='fish' | name='' | unset name |
+-----------------------+-------------+---------+------------+
| test "$name" | TRUE | f | f |
| test -n "$name" | TRUE | f | f |
| test ! -z "$name" | TRUE | f | f |
| test ! "${name-x}" | f | TRUE | f |
| test ! "${name+x}" | f | f | TRUE |
+-----------------------+-------------+---------+------------+
顺便说一下,这些表达式是等价的:测试<表达式><=>〔<表达式>〕
其他需谨慎使用的歧义表达:
+----------------------+-------------+---------+------------+
| Expression in script | name='fish' | name='' | unset name |
+----------------------+-------------+---------+------------+
| test "${name+x}" | TRUE | TRUE | f |
| test "${name-x}" | TRUE | f | TRUE |
| test -z "$name" | f | TRUE | TRUE |
| test ! "$name" | f | TRUE | TRUE |
| test ! -n "$name" | f | TRUE | TRUE |
| test "$name" = '' | f | TRUE | TRUE |
+----------------------+-------------+---------+------------+