如何在Bash中检查变量是否已设置

2359

我如何在Bash中知道一个变量是否已经设置?

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

function a {
    # if $1 is set ?
}

17
如果测试 $# 大于 0,则打印“arg <参数>”。 - Jens
284
注意寻求解决方案的人:对于这个问题,有许多评价很高的答案回答了变量是否为空的问题。更正式的解决方案(即“变量是否设置”)在Jens和Lionel回答中提到。 - Nathan Kidd
12
Russell Harmon和Seamus的"-v"测试是正确的,尽管这似乎只在新版本的“bash”中可用,而且不能在不同的shell中移植。 - Graeme
6
正如@NathanKidd所指出的,Lionel和Jens提供了正确的解决方案。prosseek,您应该将您接受的答案切换为其中之一。请参考此处 - Garrett
4
...否则,对于我们中更有眼光的人来说,错误的答案可能会被踩,因为@prosseek没有解决问题。 - dan3
显示剩余8条评论
38个回答

31

概述

  • 使用test -n "${var-}"检查变量是否不为空(因此必须已定义/设置)。用法:

if test -n "${var-}"; then
  echo "var is set to <$var>"
else
  echo "var is not set or empty"
fi
  • 使用test -n "${var+x}"来检查变量是否已定义/设置(即使为空)。用法:

  • if test -n "${var+x}"; then
      echo "var is set to <$var>"
    else
      echo "var is not set"
    fi
    

    请注意,第一个用例在shell脚本中更为常见,这也是您通常要使用的。

    注释

    • 此解决方案应适用于所有POSIX shell(sh、bash、zsh、ksh、dash)
    • 针对此问题的其他答案可能是正确的,但可能会让没有经验的shell脚本编写者感到困惑,因此我想提供一个TLDR的答案,以便这些人能够轻松理解。

    解释

    为了理解此解决方案的工作原理,您需要了解POSIX test命令和POSIX shell参数扩展(规范)的基本知识,因此让我们介绍一下需要了解答案所需的绝对基础知识。

    test 命令评估表达式并返回 true 或 false (通过其退出状态)。运算符-n如果操作数是非空字符串,则返回true。例如,test -n "a"返回true,而test -n ""返回false。现在,要检查变量是否为空(这意味着它必须已定义),您可以使用test -n "$var"。但是,一些shell脚本设置了一个选项(set -u),该选项会导致对未定义变量的任何引用都会发出错误,因此如果变量var未定义,则表达式$var将导致错误。为了正确处理此情况,您必须使用变量扩展,告诉shell如果未定义变量,则将其替换为替代字符串,避免上述错误。

    变量扩展${var-}的含义是:如果变量var未定义(也称为“unset”),则将其替换为空字符串。因此,test -n "${var-}"将返回true,如果$var不为空的话,这几乎总是您想要在shell脚本中检查的内容。反过来检查,即$var未定义或非空的情况,是test -z "${var-}"

    现在到第二个用例:检查变量var是否被定义,无论是否为空。这是一个不那么常见但稍微复杂一些的用例,我建议您阅读Lionel非常好的答案以更好地理解它。


    看起来你在 test -z 的解释中不小心加入了一个 "not"。从 test 的 man 页面可以看到:-z STRING 如果 STRING 的长度为零,则返回 true。因此,如果 var 未定义或为空,则 test -z "${var-}" 将返回 true。 - user3750888

    26

    如果未设置则退出

    这个方法适用于我。我希望我的脚本能够在参数未设置时退出并输出错误信息。

    #!/usr/bin/env bash
    
    set -o errexit
    
    # Get the value and empty validation check all in one
    VER="${1:?You must pass a version of the format 0.0.0 as the only argument}"
    

    运行时会返回错误

    peek@peek:~$ ./setver.sh
    ./setver.sh: line 13: 1: You must pass a version of the format 0.0.0 as the only argument
    

    仅进行检查,无退出 - 空值和未设置的值为INVALID

    如果您只想检查值是否设置为VALID或未设置/为空为INVALID,请尝试此选项。

    TSET="good val"
    TEMPTY=""
    unset TUNSET
    
    if [ "${TSET:-}" ]; then echo "VALID"; else echo "INVALID";fi
    # VALID
    if [ "${TEMPTY:-}" ]; then echo "VALID"; else echo "INVALID";fi
    # INVALID
    if [ "${TUNSET:-}" ]; then echo "VALID"; else echo "INVALID";fi
    # INVALID
    

    或者,甚至是短测试 ;-)

    [ "${TSET:-}"   ] && echo "VALID" || echo "INVALID"
    [ "${TEMPTY:-}" ] && echo "VALID" || echo "INVALID"
    [ "${TUNSET:-}" ] && echo "VALID" || echo "INVALID"
    

    仅用于检查,不可退出 - 仅空值为无效

    这是问题的答案。如果你只想检查设置/空值=有效或未设置=无效,请使用此选项。

    注意,“..-1}”中的“1”是无关紧要的,可以是任何内容(如x)

    TSET="good val"
    TEMPTY=""
    unset TUNSET
    
    if [ "${TSET+1}" ]; then echo "VALID"; else echo "INVALID";fi
    # VALID
    if [ "${TEMPTY+1}" ]; then echo "VALID"; else echo "INVALID";fi
    # VALID
    if [ "${TUNSET+1}" ]; then echo "VALID"; else echo "INVALID";fi
    # INVALID
    

    短测验

    [ "${TSET+1}"   ] && echo "VALID" || echo "INVALID"
    [ "${TEMPTY+1}" ] && echo "VALID" || echo "INVALID"
    [ "${TUNSET+1}" ] && echo "VALID" || echo "INVALID"
    

    我把这个答案献给@mklement0(评论),他挑战我准确回答了这个问题。

    参考资料:2.6.2 参数扩展


    26

    对于那些在使用 set -u 的脚本中希望检查未设置或为空的内容的人:

    if [ -z "${var-}" ]; then
       echo "Must provide var environment variable. Exiting...."
       exit 1
    fi
    

    如果设置了set -u,那么普通的[ -z "$var" ]检查会因为var; unbound variable而失败,但是[ -z "${var-}" ]会被展开为空字符串而不会失败。


    你缺少了一个冒号。应该是${var:-},而不是${var-}。但在Bash中,我可以确认即使没有冒号也可以工作。 - Palec
    7
    ${var:-}${var-}有不同的含义。${var:-}在变量var未定义或为空时扩展为空字符串,而${var-}仅在未定义时扩展为空字符串。 - RubenLaguna
    刚学到了新东西,谢谢!省略冒号会导致仅测试未设置的参数。换句话说,如果包括冒号,操作符将测试参数的存在性和其值不为空;如果省略冒号,则操作符仅测试存在性。 在这里没有区别,但知道这点很好。 - Palec

    19

    请阅读 bash 手册中的“参数扩展”部分。参数扩展并不提供一般性的检测变量是否已设置,但如果未设置参数,有几件事情可以做。

    例如:

    function a {
        first_arg=${1-foo}
        # rest of the function
    }
    

    如果已经分配了,将把first_arg设置为$1,否则它使用值"foo"。如果必须保证a仅接受一个参数,并且不存在好的默认值,则可以在未提供参数时退出并显示错误消息:

    function a {
        : ${1?a must take a single argument}
        # rest of the function
    }
    

    (请注意在这个例子中使用:作为一个空命令,它只是展开其参数的值。我们不想在这个例子中对$1做任何操作,如果它没有设置就退出。)


    1
    我很惊讶其他答案中没有提到简单而优雅的 : ${var?message} - tripleee
    你从哪里得到这个的?太棒了!它简单而优雅,而且完美运行!谢谢你! - Roger

    19

    为了清晰回答楼主的问题,即如何确定变量是否设置,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       |
    +-----------------------+-------------+---------+------------+
    

    顺便提一下,这些表达式是等效的:test <expression> <=> [ <expression> ]

    其他含糊不清的表达式需要谨慎使用:

    +----------------------+-------------+---------+------------+
    | 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       |
    +----------------------+-------------+---------+------------+
    

    18

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

    大多数情况下,将具有空值的变量与未设置的变量视为相同的变量是一个好主意。但是如果需要区分这两种情况,则可以这样做:[ -n "${x+set}" ]"${x+set}"会扩展为set,如果x已设置,则为空字符串)。

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


    16

    我很惊讶没有人尝试编写一个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          |            |
    +------------+------------+------------+------------+
    

    这个脚本缺少一些功能,比如无法显示副作用赋值的时间(是否发生),但也许有更有雄心壮志的人想要以此为起点并加以改进。


    2
    干得好,Mike。看起来很棒。就像你说的那样,它并不是完全全面的,但很容易找到和比较。 - Mike

    15
    在 Bash 中,您可以在[[ ]]内置命令中使用-v
    #! /bin/bash -u
    
    if [[ ! -v SOMEVAR ]]; then
        SOMEVAR='hello'
    fi
    
    echo $SOMEVAR
    

    它应该做什么? - Peter Mortensen

    14
    如果你像我一样,你实际上在搜索的是:"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
    

    12

    测试变量var是否已设置:[ ${var+x} ]

    按名称测试变量是否设置:[ ${!name+x} ]

    测试位置参数是否已设置:[ ${N+x} ],其中N实际上是一个整数。

    这个答案与Lionel的答案几乎相似,但通过省略-z来探索更简约的方法。

    测试命名变量是否已设置:

    function is_set {
        local v=$1
        echo -n "${v}"
        if [ ${!v+x} ]; then
            echo " = '${!v}'"
        else
            echo " is unset"
        fi
    }
    

    测试定位参数是否已设置:

    function a {
        if [ ${1+x} ]; then
            local arg=$1
            echo "a '${arg}'"
        else
            echo "a: arg is unset"
        fi
    }
    

    测试表明,在空格和有效的测试表达式方面不需要额外的注意。

    set -eu
    
    V1=a
    V2=
    V4=-gt
    V5="1 -gt 2"
    V6="! -z 1"
    V7='$(exit 1)'
    
    is_set V1
    is_set V2
    is_set V3
    is_set V4
    is_set V5
    is_set V6
    is_set V7
    
    a 1
    a
    a "1 -gt 2"
    a 1 -gt 2
    
    $ ./test.sh
    
    V1 = 'a'
    V2 = ''
    V3 is unset
    V4 = '-gt'
    V5 = '1 -gt 2'
    V6 = '! -z 1'
    V7 = '$(exit 1)'
    a '1'
    a: arg is unset
    a '1 -gt 2'
    a '1'
    

    最后,请注意set -eu,它可以保护我们免受常见错误的影响,例如变量名称中的错别字。我建议使用它,但这意味着必须正确处理未设置变量和设置为空字符串的变量之间的区别。


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