在Bash中,如何在“-u”模式下测试变量是否已定义

71

我刚刚发现了bash中的set -u,它帮助我发现了一些以前未被发现的错误。但我也有一个场景,在这个场景中,在计算一些默认值之前,我需要测试变量是否已定义。我想到的最好方法是:

if [ "${variable-undefined}" == undefined ]; then
    variable="$(...)"
fi

这种方法可行(只要变量不具有字符串值undefined)。我想知道是否有更好的方法?


7个回答

72

这是我发现最适合我的方法,受到其他答案的启发:

if [ -z "${varname-}" ]; then
  ...
  varname=$(...)
fi

5
有人能解释一下为什么 "${varname-}" 和 "${varname:-}" 都可以使用吗?我很惊讶第一个也可以使用。 - kevinarpe
5
${varname-} 是 ${varname-default} 的一种形式。如果 varname 没有被设置,那么就会返回默认值,在这个例子中是一个空字符串。 - Aaron J Lang
12
在各种运算符被描述之前的 man 页面中,以简短的语句描述了去除冒号的版本。很容易被忽略。${varname:-foo}varname 未设置或设置为空字符串时扩展为 foo${varname-foo} 只有在 varname 未设置时才扩展为 foo - chepner
1
这仍然无法区分被设置为空值的变量和未定义的变量。 - haridsv

36

以下方式无效:测试零长度的字符串

你可以用几种方法测试未定义的字符串。使用标准的测试条件,看起来像这样:

# Test for zero-length string.
[ -z "$variable" ] || variable='foo'

然而,这将无法与 set -u 一起使用。

可行的方法:条件赋值

另外,您可以使用条件赋值,这是一种更类似于Bash的方法。例如:

# Assign value if variable is unset or null.
: "${variable:=foo}"

由于Bash处理表达式的方式,您可以在使用set -u而不出现"bash: variable: unbound variable"错误的情况下安全地使用此表达式。


3
这并没有完全回答问题,因为它没有提供一种测试未定义变量的方法,只是在变量未定义时设置它。如果需要其他逻辑,这并不能帮助解决问题。 - Andrew Ferrier
这个答案似乎更适合简单地进行测试:http://unix.stackexchange.com/a/56846/18985 - Andrew Ferrier
3
这个问题特指在变量未设定时分配默认值的情况;虽然测试变量是否设置而不随后设置它也有其有效的用例,但这个问题并不需要。 - chepner
4
那个前导的 : 是做什么用的? - PypeBros
@PypeBros 显然 : 被别名为 true:https://dev59.com/questions/unA75IYBdhLWcg3wkZ6A - Bryce Guinta
显示剩余2条评论

11
在bash 4.2及更新版本中,有一种明确的方法来检查变量是否设置,即使用-v。那么问题中的示例可以像这样实现:
if [[ ! -v variable ]]; then
   variable="$(...)"
fi

请查看http://www.gnu.org/software/bash/manual/bashref.html#Bash-Conditional-Expressions

如果您只想在变量未定义时设置变量,可能更好的方法是执行以下操作:

variable="${variable-$(...)}"

请注意,此方法不能处理已定义但为空的变量。


请注意,直到4.2版本之前,-v选项并未添加到bash中。 - chepner

5
上面的答案不是动态的,例如如何测试变量名为“dummy”的变量是否定义?请尝试以下方法:
is_var_defined()
{
    if [ $# -ne 1 ]
    then
        echo "Expected exactly one argument: variable name as string, e.g., 'my_var'"
        exit 1
    fi
    # Tricky.  Since Bash option 'set -u' may be enabled, we cannot directly test if a variable
    # is defined with this construct: [ ! -z "$var" ].  Instead, we must use default value
    # substitution with this construct: [ ! -z "${var:-}" ].  Normally, a default value follows the
    # operator ':-', but here we leave it blank for empty (null) string.  Finally, we need to
    # substitute the text from $1 as 'var'.  This is not allowed directly in Bash with this
    # construct: [ ! -z "${$1:-}" ].  We need to use indirection with eval operator.
    # Example: $1="var"
    # Expansion for eval operator: "[ ! -z \${$1:-} ]" -> "[ ! -z \${var:-} ]"
    # Code  execute: [ ! -z ${var:-} ]
    eval "[ ! -z \${$1:-} ]"
    return $?  # Pedantic.
}

相关: 如何检查Bash中的变量是否已设置?


1
使用 [[ -n $var ]][[ $var ]] 而不是 [ ! -z "$var" ] 会更好,你觉得呢?另外,Bash 从 v2 开始就支持变量间接引用,所以这里不需要使用 eval。例如:is_var_defined(){ if (( $# != 1 )); then echo "Expected exactly one argument: variable name as string, e.g., 'my_var'" >&2; return 1; fi; [[ -n ${!1:-} ]]; } - Six

1

不幸的是,在较旧版本的bash中不支持[[ -v variable ]](至少在我使用的Debian Squeeze版本4.1.5中不支持)。

您可以改用子shell,如下所示:

if (true $variable)&>/dev/null; then
    variable="$(...)"
fi

0
if [ "${var+SET}" = "SET" ] ; then
    echo "\$var = ${var}"
fi

我不知道${var+value}支持多久了,但至少在4.1.2版本之前是可以使用的。旧版本没有${var+value},只有${var:+value}。区别在于,如果$var设置为非空字符串,则${var:+value}仅计算为"value",而${var+value}也会在$var设置为空字符串时计算为"value"。

如果没有[[ -v var ]]或${var+value},我认为您需要使用另一种方法。可能是一个子shell测试,就像在先前的答案中所描述的那样:

if ( set -u; echo "$var" ) &> /dev/null; then
    echo "\$var = ${var}
fi

如果您的shell进程已经激活了"set -u",那么在子shell中也将激活它,而不需要再次使用"set -u",但是在子shell命令中包含它可以使解决方案在父进程没有启用"set -u"的情况下也能工作。
(您还可以使用另一个进程,如"printenv"或"env"来测试变量的存在性,但这只适用于变量被导出的情况。)

0
在你的脚本开头,你可以使用空值来定义变量。
variable_undefined=""

那么

if [ "${variable_undefined}" == "" ]; then
    variable="$(...)"
fi

破折号不是有效的变量名称字符。这是OP正在使用的参数扩展语法的一部分。 - Dennis Williamson
2
最好使用“-z”进行测试,以检查字符串的长度是否为零。 - coelhudo
@coelhuedo...但是在set -u模式下,您无法扩展未定义的变量以通过test -z运行它,因此出现了这个问题。 - Charles Duffy
我喜欢这种解决问题的方式,因为它强制你在脚本顶部“声明”变量。 - Ramon
当您需要使用其他进程设置的环境变量时,这种方法无法正常工作。 - al.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接