我如何知道是否在Bash中设置了变量?

例如,如何检查用户是否向函数提供了第一个参数?

function a {
    # if $1 is set ?
}

当前回答

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中,可以在[[]]内置函数中使用-v:

#! /bin/bash -u

if [[ ! -v SOMEVAR ]]; then
    SOMEVAR='hello'
fi

echo $SOMEVAR
if [[ ${!xx[@]} ]] ; then echo xx is defined; fi

当启用Bash选项集-u时,上面的答案不起作用。此外,它们不是动态的,例如,如何测试是否定义了名为“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中,如何测试变量是否以“-u”模式定义

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

我总是发现另一个答案中的POSIX表很难找到,所以我的看法是:

parameter expansion VARIABLE set VARIABLE empty VARIABLE unset
${VARIABLE-default} $VARIABLE "" "default"
${VARIABLE=default} $VARIABLE "" $(VARIABLE="default")
${VARIABLE?default} $VARIABLE "" exit 127
${VARIABLE+default} "default" "default" ""
${VARIABLE:-default} $VARIABLE "default" "default"
${VARIABLE:=default} $VARIABLE $(VARIABLE="default") $(VARIABLE="default")
${VARIABLE:?default} $VARIABLE exit 127 exit 127
${VARIABLE:+default} "default" "" ""

请注意,每个组(前面有和没有冒号)都有相同的设置和未设置的大小写,因此唯一不同的是如何处理空大小写。

对于前面的冒号,空的和未设置的大小写是相同的,因此我将在可能的情况下使用它们(即使用:=,而不仅仅是=,因为空的大小写不一致)。

标题:

set表示VARIABLE为非空(VARIABLE=“something”)空表示VARIABLE为空/空(VARIABLE=“”)未设置表示变量不存在(未设置变量)

值:

$VARIABLE表示结果是变量的原始值。“默认”表示结果是提供的替换字符串。“”表示结果为空(空字符串)。退出127意味着脚本停止执行,退出代码127。$(VARIABLE=“默认”)表示结果为“默认”,VARIABLE(以前为空或未设置)也将设置为“默认值”。