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

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

function a {
    # if $1 is set ?
}

当前回答

case "$1" in
 "") echo "blank";;
 *) echo "set"
esac

其他回答

要检查非空/非零字符串变量,即如果已设置,请使用

if [ -n "$1" ]

它与-z相反。我发现自己使用-n多于-z。

您可以像这样使用它:

if [ -n "$1" ]; then
  echo "You supplied the first parameter!"
else
  echo "First parameter not supplied."
fi

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仅运行命令”

你希望这是一行,所以下面这行是你想要的

仅适用于Bash 4.2或更高版本

仅在设置时运行

if [[ -v mytest ]]; then echo "this runs only if variable is set"; fi

仅在未设置时运行

if [[ ! -v mytest2 ]]; then echo "this runs only if variable is not set"; fi

我很惊讶没有人尝试编写一个shell脚本来以编程方式生成这个臭名昭著的难以摸索的表。既然我们在这里试图学习编码技术,为什么不用代码表达答案?:)这是我的看法(应该在任何POSIX shell中都适用):

H="+-%s-+-%s----+-%s----+-%s--+\n"       # table divider printf format
R="| %-10s | %-10s | %-10s | %-10s |\n"  # table row printf format

S='V'     # S is a variable that is set-and-not-null
N=''      # N is a variable that is set-but-null (empty "")
unset U   # U is a variable that is unset

printf "$H" "----------" "-------" "-------" "---------";
printf "$R" "expression" "FOO='V'" "FOO='' " "unset FOO";
printf "$H" "----------" "-------" "-------" "---------";
printf "$R" "\${FOO:-x}" "${S:-x}" "${N:-x}" "${U:-x}  "; S='V';N='';unset U
printf "$R" "\${FOO-x} " "${S-x} " "${N-x} " "${U-x}   "; S='V';N='';unset U
printf "$R" "\${FOO:=x}" "${S:=x}" "${N:=x}" "${U:=x}  "; S='V';N='';unset U
printf "$R" "\${FOO=x} " "${S=x} " "${N=x} " "${U=x}   "; S='V';N='';unset U
#                                  "${N:?x}" "${U:?x}  "
printf "$R" "\${FOO:?x}" "${S:?x}" "<error>" "<error>  "; S='V';N='';unset U
#                                            "${U?x}   "
printf "$R" "\${FOO?x} " "${S?x} " "${N?x} " "<error>  "; S='V';N='';unset U
printf "$R" "\${FOO:+x}" "${S:+x}" "${N:+x}" "${U:+x}  "; S='V';N='';unset U
printf "$R" "\${FOO+x} " "${S+x} " "${N+x} " "${U+x}   "; S='V';N='';unset U
printf "$H" "----------" "-------" "-------" "---------";

以及运行脚本的输出:

+------------+------------+------------+------------+
| expression | FOO='V'    | FOO=''     | unset FOO  |
+------------+------------+------------+------------+
| ${FOO:-x}  | V          | x          | x          |
| ${FOO-x}   | V          |            | x          |
| ${FOO:=x}  | V          | x          | x          |
| ${FOO=x}   | V          |            | x          |
| ${FOO:?x}  | V          | <error>    | <error>    |
| ${FOO?x}   | V          |            | <error>    |
| ${FOO:+x}  | x          |            |            |
| ${FOO+x}   | x          | x          |            |
+------------+------------+------------+------------+

该脚本缺少了一些功能,比如在发生(或不发生)副作用分配时显示,但也许其他更有野心的人希望以此为出发点,并以此为出发。

要检查变量是否设置为非空值,请使用[-n“$x”],正如其他人已经指出的那样。

大多数情况下,最好将具有空值的变量与未设置的变量以相同的方式处理。但如果需要,您可以区分这两个:[-n“${x+set}”](如果设置了x,则“${x+set}”扩展为set,如果未设置x,则扩展为空字符串)。

要检查是否传递了参数,请测试$#,这是传递给函数(或不在函数中时传递给脚本)的参数数(请参见Paul的答案)。