我如何知道是否在Bash中设置了变量?
例如,如何检查用户是否向函数提供了第一个参数?
function a {
# if $1 is set ?
}
我如何知道是否在Bash中设置了变量?
例如,如何检查用户是否向函数提供了第一个参数?
function a {
# if $1 is set ?
}
当前回答
我总是使用这个,因为任何第一次看到代码的人都很容易理解:
if [ "$variable" = "" ]
then
echo "Variable X is empty"
fi
如果要检查是否为空;
if [ ! "$variable" = "" ]
then
echo "Variable X is not empty"
fi
就是这样。
其他回答
(通常)正确的方式
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脚本。
我喜欢辅助功能来隐藏Bash的粗糙细节。在这种情况下,这样做会增加更多(隐藏的)粗糙度:
# The first ! negates the result (can't use -n to achieve this)
# the second ! expands the content of varname (can't do ${$varname})
function IsDeclared_Tricky
{
local varname="$1"
! [ -z ${!varname+x} ]
}
因为我在这个实现中首先遇到了bug(灵感来自Jens和Lionel的回答),所以我想出了一个不同的解决方案:
# Ask for the properties of the variable - fails if not declared
function IsDeclared()
{
declare -p $1 &>/dev/null
}
我发现它更直接,更害羞,更容易理解/记住。测试用例表明它是等效的:
function main()
{
declare -i xyz
local foo
local bar=
local baz=''
IsDeclared_Tricky xyz; echo "IsDeclared_Tricky xyz: $?"
IsDeclared_Tricky foo; echo "IsDeclared_Tricky foo: $?"
IsDeclared_Tricky bar; echo "IsDeclared_Tricky bar: $?"
IsDeclared_Tricky baz; echo "IsDeclared_Tricky baz: $?"
IsDeclared xyz; echo "IsDeclared xyz: $?"
IsDeclared foo; echo "IsDeclared foo: $?"
IsDeclared bar; echo "IsDeclared bar: $?"
IsDeclared baz; echo "IsDeclared baz: $?"
}
main
测试用例还显示,局部var不声明var(除非后面跟着“=”)。很长一段时间以来,我以为我是这样声明变量的,只是为了发现我只是表达了我的意图。。。我想这是不可能的。
IsDeclared_Tricky xyz:1IsDeclared_Tricky foo:1IsDeclared_Tricky条:0我声明的Tricky baz:0IsDeclared xyz:1IsDeclared foo:1IsDeclared栏:0IsDeclared baz:0
奖金:用例
我主要使用此测试以某种“优雅”和安全的方式(几乎类似于接口…)向函数提供(和返回)参数:
# Auxiliary functions
function die()
{
echo "Error: $1"; exit 1
}
function assertVariableDeclared()
{
IsDeclared "$1" || die "variable not declared: $1"
}
function expectVariables()
{
while (( $# > 0 )); do
assertVariableDeclared $1; shift
done
}
# Actual example
function exampleFunction()
{
expectVariables inputStr outputStr
outputStr="$inputStr, World!"
}
function bonus()
{
local inputStr='Hello'
local outputStr= # Remove this to trigger the error
exampleFunction
echo $outputStr
}
bonus
如果调用时声明了所有必需的变量:
你好,世界!
其他:
错误:未声明变量:outputStr
为了明确回答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 |
+----------------------+-------------+---------+------------+
如果您希望测试变量是否绑定或未绑定,即使在启用了nounset选项后,这也能很好地工作:
set -o noun set
if printenv variableName >/dev/null; then
# variable is bound to a value
else
# variable is unbound
fi