【问题标题】:How to check if a variable is set in Bash如何检查是否在 Bash 中设置了变量?
【发布时间】:2011-04-05 19:35:34
【问题描述】:

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

例如,如何检查用户是否将第一个参数传递给函数?

function a {
    # if $1 is set ?
}

【问题讨论】:

  • if test $# -gt 0; then printf 'arg <%s>\n' "$@"; fi.
  • 解决方案寻求者注意:这个问题有很多评价很高的答案,它们回答了“可变非空”这个问题。 Jens 和 Lionel 在下面的回答中提到了更多的校正解决方案(“是变量集”)。
  • 此外,Russell Harmon 和 Seamus 的 -v 测试也是正确的,尽管这似乎仅适用于 bash 的新版本,并且不能跨 shell 移植。
  • 正如@NathanKidd 所指出的,Lionel 和 Jens 给出了正确的解决方案。 proseek,你应该 switch your accepted answer 到其中之一。
  • ... 或者不正确的答案可能会被我们当中更有眼光的人否决,因为@prosseek 没有解决问题。

标签: bash shell variables


【解决方案1】:

(通常)正确的方式

if [ -z ${var+x} ]; then echo "var is unset"; else echo "var is set to '$var'"; fi

其中${var+x}parameter expansion,如果未设置var,则计算结果为空,否则替换字符串x

引述题外话

引号可以省略(所以我们可以说${var+x}而不是"${var+x}"),因为这种语法和用法保证它只会扩展为不需要引号的东西(因为它要么扩展为x(其中包含没有分词,所以它不需要引号),或者什么都没有(这导致[ -z ],它方便地计算为与[ -z "" ] 相同的值(true))。

然而,虽然引号可以安全地省略,而且对所有人来说都不是很明显(对于同时也是主要 Bash 编码器的the first author of this quotes explanation 来说甚至都不明显),但有时最好使用以下方法编写解决方案引用为[ -z "${var+x}" ],以极小的可能成本为 O(1) 速度损失。第一作者还在使用此解决方案的代码旁边添加了此注释,提供了此答案的 URL,现在还包括解释为什么可以安全地省略引号。

(经常)错误的方式

if [ -z "$var" ]; then echo "var is blank"; else echo "var is set to '$var'"; fi

这通常是错误的,因为它不区分未设置的变量和设置为空字符串的变量。也就是说,如果var='',那么上面的解决方案会输出“var is blank”。

在用户必须指定扩展名或附加属性列表并且不指定它们默认为非空值的情况下,未设置和“设置为空字符串”之间的区别是必不可少的,而指定空字符串应该使脚本使用空扩展名或附加属性列表。

不过,这种区别可能并非在每种情况下都必不可少。在这种情况下,[ -z "$var" ] 就可以了。

【讨论】:

  • @Garrett,您的编辑使这个答案不正确,${var+x} 是正确的替代品。使用[ -z ${var:+x} ] 产生的结果与[ -z "$var" ] 没有不同。
  • 这不起作用。无论 var 是否设置为一个值,我都会得到“未设置”(用“unset var”清除,“echo $var”不产生输出)。
  • 对于解决方案的语法${parameter+word},官方手册部分为gnu.org/software/bash/manual/…;但是,其中的一个错误是,它没有很清楚地提及此语法,而只是说引号(省略冒号[“:”]会导致仅对未设置的参数进行测试。.. ${parameter:+word} [意思]如果参数为null或未设置,则不替换任何内容,否则替换单词的扩展。);引用的参考文献pubs.opengroup.org/onlinepubs/9699919799/utilities/…(很高兴知道)这里有更清晰的文档。
  • 使用多个表达式时似乎需要引号,例如[ -z "" -a -z ${var+x} ] 在 bash 4.3-ubuntu 中获得 bash: [: argument expected(但不是例如 zsh)。我真的不喜欢使用恰好在某些情况下有效的语法的答案。
  • 使用简单的[ -z $var ] 并不比省略引号更“错误”。无论哪种方式,您都在对您的输入做出假设。如果您可以将空字符串视为未设置,则只需 [ -z $var ]
【解决方案2】:

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

if [ -n "$1" ]

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

你会像这样使用它:

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

【讨论】:

  • 我通常更喜欢[[ ]] 而不是[ ],因为[[ ]] 更强大并且在某些情况下引起的问题更少(请参阅this question 了解两者之间的差异)。该问题专门要求 bash 解决方案,但未提及任何可移植性要求。
  • 问题是如何查看变量是否设置。那么你不能假设变量 is 已设置。
  • 我同意,[] 特别适用于 shell(非 bash)脚本。
  • 请注意 -n 是默认测试,因此更简单的 [ "$1" ][[ $1 ]] 也可以工作。还要注意[[ ]]quoting is unnecessary
  • 好像不是真的相反...真正相反的是if [ ! -z "$1" ]
【解决方案3】:

以下是测试参数是否未设置空(“Null”)设置了值的方法:

+--------------------+----------------------+-----------------+-----------------+
|   Expression       |       parameter      |     parameter   |    parameter    |
|   in script:       |   Set and Not Null   |   Set But Null  |      Unset      |
+--------------------+----------------------+-----------------+-----------------+
| ${parameter:-word} | substitute parameter | substitute word | substitute word |
| ${parameter-word}  | substitute parameter | substitute null | substitute word |
| ${parameter:=word} | substitute parameter | assign word     | assign word     |
| ${parameter=word}  | substitute parameter | substitute null | assign word     |
| ${parameter:?word} | substitute parameter | error, exit     | error, exit     |
| ${parameter?word}  | substitute parameter | substitute null | error, exit     |
| ${parameter:+word} | substitute word      | substitute null | substitute null |
| ${parameter+word}  | substitute word      | substitute word | substitute null |
+--------------------+----------------------+-----------------+-----------------+

来源:POSIX: Parameter Expansion

在所有用“替代”显示的情况下,表达式都被替换为显示的值。在所有用“assign”显示的情况下,参数都被分配了那个值,它也替换了表达式。

要实际展示这一点:

+--------------------+----------------------+-----------------+-----------------+
|   Expression       |  FOO="world"         |     FOO=""      |    unset FOO    |
|   in script:       |  (Set and Not Null)  |  (Set But Null) |     (Unset)     |
+--------------------+----------------------+-----------------+-----------------+
| ${FOO:-hello}      | world                | hello           | hello           |
| ${FOO-hello}       | world                | ""              | hello           |
| ${FOO:=hello}      | world                | FOO=hello       | FOO=hello       |
| ${FOO=hello}       | world                | ""              | FOO=hello       |
| ${FOO:?hello}      | world                | error, exit     | error, exit     |
| ${FOO?hello}       | world                | ""              | error, exit     |
| ${FOO:+hello}      | hello                | ""              | ""              |
| ${FOO+hello}       | hello                | hello           | ""              |
+--------------------+----------------------+-----------------+-----------------+

【讨论】:

  • @HelloGoodbye 是的,它确实有效:set foo; echo ${1:-set but null or unset} echos "foo"; set --; echo ${1:-set but null or unset} 回显设置但为空 ...
  • @HelloGoodbye 位置参数可以设置,呃,set :-)
  • 这个答案非常令人困惑。有关如何使用此表的任何实际示例?
  • @BenDavis 详细信息在指向 POSIX 标准的链接中进行了解释,该标准引用了该表的来源章节。我在编写的几乎所有脚本中使用的一种构造是: ${FOOBAR:=/etc/foobar.conf},用于为未设置或为空的变量设置默认值。
  • parameter 是任何变量名。 word 是一些要替换的字符串,具体取决于您使用的表中的语法,以及变量 $parameter 是设置、设置但为空还是未设置。不要让事情变得更复杂,但word 也可以包含变量!这对于传递由字符分隔的可选参数非常有用。例如:${parameter:+,${parameter}} 输出逗号分隔的,$parameter,如果它已设置但不为空。在这种情况下,word,${parameter}
【解决方案4】:

虽然此处所述的大多数技术都是正确的,但 bash 4.2 支持对变量是否存在的实际测试 (man bash),而不是测试变量的值。

[[ -v foo ]]; echo $?
# 1

foo=bar
[[ -v foo ]]; echo $?
# 0

foo=""
[[ -v foo ]]; echo $?
# 0

值得注意的是,这种方法在set -u / set -o nounset 模式下用于检查未设置的变量时不会导致错误,这与许多其他方法不同,例如使用[ -z

【讨论】:

  • 在 bash 4.1.2 中,无论是否设置变量,[[ -v aaa ]]; echo $? ==> -bash: conditional binary operator expected -bash: syntax error near 'aaa'
  • 在 bash 4.2 中添加了 'test' 内置函数的 '-v' 参数。
  • 添加了 -v 参数作为对 -u shell 选项(名词集)的补充。打开'nounset' (set -u),如果不是交互式的,shell 将返回一个错误并终止。 $# 非常适合检查位置参数。但是,命名变量需要其他方式,而不是破坏,将它们标识为未设置。不幸的是,这个功能来得太晚了,因为许多人注定要与早期版本兼容,在这种情况下他们不能使用它。唯一的其他选择是使用技巧或“黑客”来绕过它,如上所示,但这是最不雅的。
  • 请注意,这不适用于已声明但未设置的变量。例如declare -a foo
  • 这就是为什么我在接受/最高投票的答案之后继续阅读。
【解决方案5】:

有很多方法可以做到这一点,以下是其中之一:

if [ -z "$1" ]

如果 $1 为 null 或未设置则成功

【讨论】:

  • 未设置的参数和带空值的参数是有区别的。
  • 我只是想学究一下,并指出这是对问题提出的相反的答案。问题询问是否设置了变量,而不是是否设置了变量。
  • [[ ]] 只不过是一个可移植性陷阱。此处应使用if [ -n "$1" ]
  • 这个答案不正确。该问题要求一种方法来判断变量是否已设置,而不是是否设置为非空值。给定foo=""$foo 有一个值,但-z 只会报告它是空的。如果你有set -o nounset-z $foo 就会爆炸。
  • 这取决于“变量被设置”的定义。如果要检查变量是否设置为非零字符串,那么mbranning answer 是正确的。但是如果要检查变量是否已声明但未初始化(即foo=),那么Russel's answer是正确的。
【解决方案6】:

我总是在other answer 中发现 POSIX 表很难理解,所以这是我的看法:

   +----------------------+------------+-----------------------+-----------------------+
   |   if VARIABLE is:    |    set     |         empty         |        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"  |          ""           |          ""           |
   +----------------------+------------+-----------------------+-----------------------+

请注意,每个组(带和不带前冒号)具有相同的 setunset 情况,因此唯一不同的是 empty 案件已处理。

使用前面的冒号,emptyunset 情况是相同的,所以我会尽可能使用它们(即使用:=,而不仅仅是=,因为空壳不一致)。

标题:

  • set 表示VARIABLE 非空(VARIABLE="something"
  • empty 表示VARIABLE 为空/null (VARIABLE="")
  • 未设置表示VARIABLE不存在(unset VARIABLE

价值观:

  • $VARIABLE 表示结果是变量的原始值。
  • "default" 表示结果是提供的替换字符串。
  • "" 表示结果为 null(空字符串)。
  • exit 127 表示脚本停止执行,退出代码为 127。
  • $(VARIABLE="default") 表示结果为"default" 并且VARIABLE(之前为空或未设置)也将设置为等于"default"

【讨论】:

  • 您只修复了实际编码错误(使用变量时不需要的间接实例)。我进行了编辑以修复更多美元在解释描述方面产生影响的情况。顺便说一句,所有 shell 变量(数组除外)都是字符串变量;它们可能恰好包含一个可以解释为数字的字符串。谈论字符串只会模糊信息。引号与字符串无关,它们只是转义的替代方法。 VARIABLE="" 可以写成VARIABLE=。不过,前者更具可读性。
  • 感谢表格非常有用,但是如果未设置或为空,我会尝试让脚本退出。但我得到的退出代码是 1 而不是 127。
  • VARIABLE 未设置时,我看到A=${VARIABLE=default}A 等于"default",而不是VARIABLE 的原始值(未设置)。您对$(VARIABLE="default") 的描述是否正确?也许我误读了您所说的原始价值。
  • 根据documentation,它说“在所有情况下,应替换参数的最终值。”因此,也许应该将描述更改为:$(VARIABLE="default") 表示结果为"default",并且VARIABLE 也将设置为等于"default"
  • @Tom 是的,这是正确的 - 我已经按照你的建议编辑了我的答案,希望能让事情更清楚。
【解决方案7】:

要查看变量是否为非空,我使用

if [[ $var ]]; then ...       # `$var' expands to a nonempty string

相反的测试变量是否未设置或为空:

if [[ ! $var ]]; then ...     # `$var' expands to the empty string (set or not)

要查看是否设置了变量(空或非空),我使用

if [[ ${var+x} ]]; then ...   # `var' exists (empty or nonempty)
if [[ ${1+x} ]]; then ...     # Parameter 1 exists (empty or nonempty)

如果未设置变量,则相反的测试:

if [[ ! ${var+x} ]]; then ... # `var' is not set at all
if [[ ! ${1+x} ]]; then ...   # We were called with no arguments

【讨论】:

  • 我也用了一段时间了;只是以简化的方式:[ $isMac ] && param="--enable-shared"
  • @Bhaskar 我认为这是因为this answer 已经提供了基本相同的答案(使用${variable+x} 得到x iff $variable 已设置)几乎半年前。它还有更多详细信息解释了为什么它是正确的。
【解决方案8】:

在现代版本的 Bash(我认为是 4.2 或更高版本;我不确定)上,我会试试这个:

if [ ! -v SOMEVARIABLE ] #note the lack of a $ sigil
then
    echo "Variable is unset"
elif [ -z "$SOMEVARIABLE" ]
then
    echo "Variable is set to an empty string"
else
    echo "Variable is set to some string"
fi

【讨论】:

  • 还要注意[ -v "$VARNAME" ] 不是错误的,只是执​​行不同的测试。假设VARNAME=foo;然后它检查是否设置了一个名为foo 的变量。
  • 注意那些想要便携性的人,-v 不符合 POSIX 标准
  • 如果得到[: -v: unexpected operator,则必须确保使用bash 而不是sh
【解决方案9】:

注意

由于bash 标签,我给出了一个以 Bash 为重点的答案。

简答

只要你在 Bash 中只处理命名变量,这个函数应该总是告诉你变量是否已经被设置,即使它是一个空数组。

variable-is-set() {
    declare -p "$1" &>/dev/null
}

为什么会这样

在 Bash(至少早于 3.0)中,如果 var 是已声明/设置的变量,则 declare -p var 输出一个 declare 命令,将变量 var 设置为其当前类型和值是,并返回状态码0(成功)。如果var 未声明,则declare -p varstderr 输出错误消息并返回状态码1。使用&>/dev/null,将常规stdoutstderr 输出重定向到/dev/null,永远不会被看到,并且不更改状态代码。因此该函数只返回状态码。

为什么其他方法(有时)在 Bash 中失败

  • [ -n "$var" ]: 这仅检查${var[0]} 是否为非空。 (在 Bash 中,$var${var[0]} 相同。)
  • [ -n "${var+x}" ]: 这只检查是否设置了${var[0]}
  • [ "${#var[@]}" != 0 ]: 这只检查是否设置了至少一个$var 的索引。

当这个方法在 Bash 中失败时

这仅适用于命名变量(包括$_),不适用于某些特殊变量($!$@$#$$$*$?$-、@ 987654352@,$1$2,...,以及任何我可能已经忘记的)。由于这些都不是数组,POSIX 风格的[ -n "${var+x}" ] 适用于所有这些特殊变量。但请注意不要将其包装在函数中,因为许多特殊变量在调用函数时会更改值/存在。

外壳兼容性说明

如果您的脚本包含数组,并且您正试图使其与尽可能多的 shell 兼容,那么请考虑使用 typeset -p 而不是 declare -p。我读过 ksh 只支持前者,但无法对此进行测试。我确实知道 Bash 3.0+ 和 Zsh 5.5.1 都支持 typeset -pdeclare -p,只是其中一个可以替代另一个。但是我没有测试过这两个关键字之外的差异,也没有测试过其他的shell。

如果您需要脚本与 POSIX sh 兼容,则不能使用数组。没有数组,[ -n "{$var+x}" ] 可以工作。

Bash 中不同方法的比较代码

此函数取消设置变量varevals 传递的代码,运行测试以确定var 是否由evald 代码设置,最后显示不同测试的结果状态代码。

我将跳过 test -v var[ -v var ][[ -v var ]],因为它们产生的结果与 POSIX 标准 [ -n "${var+x}" ] 相同,但需要 Bash 4.2+。我也跳过了 typeset -p,因为它与我测试过的 shell(Bash 3.0 到 5.0 和 Zsh 5.5.1)中的 declare -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 ]declare -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

总结

  • declare -p var &>/dev/null 对于在 Bash 中测试命名变量至少是 3.0 版(100%?)可靠。
  • [ -n "${var+x}" ] 在符合 POSIX 的情况下是可靠的,但不能处理数组。
  • 存在用于检查变量是否为非空以及检查其他 shell 中声明的变量的其他测试。但这些测试既不适合 Bash 也不适合 POSIX 脚本。

【讨论】:

  • 在我尝试过的答案中,这个 (declare -p var &>/dev/null) 是唯一有效的答案。谢谢!
  • @mark-haferkamp 我试图编辑你的答案,将关键的前面的“/”添加到你的函数代码中,这样它就显示为... &> /dev/null,但是我不允许我进行单个字符编辑?(必须大于 6 个字符 - 甚至尝试添加空格,但没有成功)。此外,可能值得更改函数以将$1 切换为示例命名变量(例如OUTPUT_DIR),因为正如您所指出的,$1 等特殊变量在这里不起作用。无论如何,这是一个关键的编辑,否则代码会在名为dev 的相对目录中创建一个名为null 的文件。顺便说一句 - 很好的答案!
  • 另外,使用不带 $ 前缀的变量名也可以:declare -p PATH &> /dev/null 返回 0,而 declare -p ASDFASDGFASKDF &> /dev/null 返回 1。(在 bash 5.0.11(1)-release 上)跨度>
  • 干得好! 1 个警告:declare var 声明 var 一个变量,但它没有根据 set -o nounset 设置它:使用 declare foo bar=; set -o nounset; echo $bar; echo $foo 进行测试
  • @joehanna 感谢您发现丢失的/!我解决了这个问题,但我认为逻辑混乱到足以保证一个功能,特别是考虑到@xebeche 的评论。 @xebeche 这对我的回答很不方便……看来我得试试declare -p "$1" | grep =test -v "$1" 之类的东西。
【解决方案10】:
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

【讨论】:

  • 第一个测试是向后的;我想你想要[ "$1" != "" ](或[ -n "$1" ])...
  • 如果$1 设置为空字符串,这将失败。
  • 答案的第二部分(计数参数)是一个有用的解决方法,当$1 设置为空字符串时,答案的第一部分不起作用。
  • 如果set -o nounset 开启,这将失败。
【解决方案11】:

如果未设置,您想退出

这对我有用。如果未设置参数,我希望我的脚本退出并显示错误消息。

#!/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

仅检查,不退出 - Empty 和 Unset 无效

如果您只想检查值是 set=VALID 还是 unset/empty=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"

只检查,不退出——只有空是无效的

这就是问题的答案。 如果您只想检查值是 set/empty=VALID 还是 unset=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 (cmets),他们要求我准确回答问题。

参考http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02

【讨论】:

    【解决方案12】:

    总结

    • 使用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 command 和 POSIX shell 参数扩展 (spec),因此让我们介绍理解答案所需的绝对基础知识。

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

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

    现在到第二个用例:检查变量var 是否已定义,是否为空。这是一个不太常见的用例,并且稍微复杂一些,我建议您阅读Lionels's great answer 以更好地理解它。

    【讨论】:

      【解决方案13】:

      对于那些希望在带有set -u 的脚本中检查未设置或空

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

      常规的[ -z "$var" ] 检查将失败,var; unbound variable 如果set -u[ -z "${var-}" ] expands 如果var 未设置且未失败,则为空字符串。

      【讨论】:

      • 您缺少一个冒号。它应该是${var:-},而不是${var-}。但是在 Bash 中,即使没有冒号,我也可以确认它可以工作。
      • ${var:-}${var-} 表示不同的东西。如果var 未设置或null${var:-} 将扩展为空字符串,而${var-} 仅在未设置时扩展为空字符串。
      • 刚学到新东西,谢谢! 省略冒号会导致仅对未设置的参数进行测试。换句话说,如果包含冒号,则运算符会测试两个参数的存在以及它的值是否不为空;如果省略冒号,则运算符仅测试是否存在。 这里没有区别,但很高兴知道。
      【解决方案14】:

      阅读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做任何事情,如果没有设置就退出)

      【讨论】:

      • 我很困惑,其他答案都没有提到简单而优雅的: ${var?message}
      • 你从哪里拿到的?精彩的!它只是有效!它又短又优雅!谢谢!
      【解决方案15】:

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

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

      要检查是否传递了参数,请测试$#,它是传递给函数(或脚本,如果不在函数中)的参数数量(请参阅Paul's answer)。

      【讨论】:

        【解决方案16】:

        在 bash 中,您可以在 [[ ]] 内置函数中使用 -v

        #! /bin/bash -u
        
        if [[ ! -v SOMEVAR ]]; then
            SOMEVAR='hello'
        fi
        
        echo $SOMEVAR
        

        【讨论】:

          【解决方案17】:

          我很惊讶没有人尝试编写 shell 脚本以编程方式生成 the infamously hard to grok table。既然我们在这里尝试学习编码技术,为什么不用代码来表达答案呢? :) 这是我的看法(应该适用于任何 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          |            |
          +------------+------------+------------+------------+
          

          该脚本缺少一些功能,例如显示副作用分配何时发生(或不发生),但也许其他一些更有野心的人想以此为起点并运行它。

          【讨论】:

          • 干得好,迈克。看起来不错。正如您所说,它并不全面,但可以很快找到并进行比较。
          【解决方案18】:

          为了明确回答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       |
          +-----------------------+-------------+---------+------------+
          

          顺便说一下,这些表达式是等价的: test &lt;expression&gt; [ &lt;expression&gt; ]

          谨慎使用的其他模棱两可的表达方式:

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

          【讨论】:

            【解决方案19】:

            使用[[ -z "$var" ]] 是了解变量是否已设置的最简单方法,但该选项-z 不区分未设置的变量和设置为空字符串的变量:

            $ set=''
            $ [[ -z "$set" ]] && echo "Set" || echo "Unset" 
            Unset
            $ [[ -z "$unset" ]] && echo "Set" || echo "Unset"
            Unset
            

            最好根据变量的类型来检查:env变量、参数或常规变量。

            对于环境变量:

            [[ $(env | grep "varname=" | wc -l) -eq 1 ]] && echo "Set" || echo "Unset"
            

            对于一个参数(例如,检查参数$5是否存在):

            [[ $# -ge 5 ]] && echo "Set" || echo "Unset"
            

            对于常规变量(使用辅助函数,以优雅的方式进行):

            function declare_var {
               declare -p "$1" &> /dev/null
            }
            declare_var "var_name" && echo "Set" || echo "Unset"
            

            注意事项:

            • $#: 为您提供位置参数的数量。
            • declare -p: 为您提供作为参数传递的变量的定义。如果存在,则返回 0,如果不存在,则返回 1 并打印错误消息。
            • &amp;&gt; /dev/null 抑制来自declare -p 的输出,而不影响其返回码。

            【讨论】:

              【解决方案20】:

              你可以这样做:

              function a {
                      if [ ! -z "$1" ]; then
                              echo '$1 is set'
                      fi
              }
              

              【讨论】:

              • 或者你可以做-n而不否定-z
              • 您需要在$1 周围加上引号,即[ ! -z "$1" ]。或者你可以在 bash/ksh/zsh 中写[[ ! -z $1 ]]
              • 如果$1 设置为空字符串,这将失败。
              【解决方案21】:

              当启用 Bash 选项 set -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.
              }
              

              相关:In Bash, how do I test if a variable is defined in "-u" mode

              【讨论】:

              • 我很想知道为什么这个答案被否决...对我来说很好。
              • 在 Bash 中,您可以在没有 eval 的情况下执行相同的操作:将 eval "[ ! -z \${$1:-} ]" 更改为间接评估:[ ! -z ${!1:-} ];
              • @BinaryZebra:有趣的想法。我建议您作为单独的答案发布。
              • 使用“eval”会使这个解决方案容易受到代码注入:$ is_var_defined 'x} ];回声“陷阱”>&2; #' 明白了
              【解决方案22】:

              我的首选方式是这样的:

              $ var=10
              $ if ! ${var+false};then echo "is set";else echo "NOT set";fi
              is set
              $ unset -v var
              $ if ! ${var+false};then echo "is set";else echo "NOT set";fi
              NOT set
              

              因此,基本上,如果设置了一个变量,它就会变成“对结果 false 的否定”(true = “已设置”)。

              而且,如果未设置,它将变成“对结果 true 的否定”(因为空结果的计算结果为 true)(因此将以 false = “未设置”结束)。

              【讨论】:

                【解决方案23】:

                测试是否设置了变量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,它可以保护我们免受常见错误的影响,例如变量名中的拼写错误。我推荐它的用法,但这意味着未设置变量和使用空字符串设置的变量之间的差异得到了正确处理。

                【讨论】:

                  【解决方案24】:

                  这是我每天都用的:

                  #
                  # Check if a variable is set
                  #   param1  name of the variable
                  #
                  function is_set() { [[ $(eval echo "\${${1}+x}") ]]; }
                  

                  这在 Linux 和 Solaris 下运行良好,直到 bash 3.0。

                  bash-3.00$ myvar="TEST"
                  bash-3.00$ is_set myvar ; echo $?
                  0
                  bash-3.00$ myvar=
                  bash-3.00$ is_set myvar ; echo $?
                  0
                  bash-3.00$ unset myvar
                  bash-3.00$ is_set myvar ; echo $?
                  1
                  

                  【讨论】:

                  • 自 Bash 2.0+ 间接扩展可用。您可以将冗长而复杂的(还包括 eval)test -n "$(eval "echo "\${${1}+x}"")" 替换为等效项:[[ ${!1+x} ]]
                  • 这也是我使用的,eval 是邪恶的,但这适用于我必须使用的所有版本的 bash 和 zsh。
                  【解决方案25】:

                  在 shell 中,您可以使用 -z 运算符,如果字符串的长度为零,则该运算符为 True。

                  一个简单的单线设置默认MY_VAR如果它没有设置,否则你可以选择显示消息:

                  [[ -z "$MY_VAR" ]] && MY_VAR="default"
                  [[ -z "$MY_VAR" ]] && MY_VAR="default" || echo "Variable already set."
                  

                  【讨论】:

                    【解决方案26】:
                    [[ $foo ]]
                    

                    或者

                    (( ${#foo} ))
                    

                    或者

                    let ${#foo}
                    

                    或者

                    declare -p foo
                    

                    【讨论】:

                      【解决方案27】:
                      if [[ ${1:+isset} ]]
                      then echo "It was set and not null." >&2
                      else echo "It was not set or it was null." >&2
                      fi
                      
                      if [[ ${1+isset} ]]
                      then echo "It was set but might be null." >&2
                      else echo "It was was not set." >&2
                      fi
                      

                      【讨论】:

                      • $1, $2$N 始终设置。抱歉,这次失败了。
                      • 我试过了,还是不行,可能是我的 bash 版本不支持。 Idk,对不起,如果我错了。 :)
                      【解决方案28】:

                      如果您想检查 $@ 中的任何内容,我发现了一个(很多)更好的代码来执行此操作。

                      如果 [[ $1 = "" ]] 然后 echo '$1 为空白' 别的 echo '$1 已满' 菲

                      为什么会这样? $@ 中的所有内容都存在于 Bash 中,但默认情况下它是空白的,所以 test -ztest -n 帮不了你。

                      更新:您还可以计算参数中的字符数。

                      如果 [ ${#1} = 0 ] 然后 echo '$1 为空白' 别的 echo '$1 已满' 菲

                      【讨论】:

                      • 我知道,但仅此而已。如果有东西是空的,你会得到一个错误!对不起。 :P
                      【解决方案29】:
                      if [[ ${!xx[@]} ]] ; then echo xx is defined; fi
                      

                      【讨论】:

                      • 欢迎来到 StackOverflow。虽然感谢您的回答,但最好提供的不仅仅是代码。您能否在答案中添加推理或小解释?有关扩展答案的其他想法,请参阅 this page
                      【解决方案30】:

                      我喜欢辅助函数来隐藏 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} ]
                      }
                      

                      因为我首先在这个实现中遇到了错误(受 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
                      

                      测试用例还显示local var 确实NOT 声明了 var(除非后跟 '=')。有一段时间我以为我是这样声明变量的,现在才发现我只是表达了我的意图……我猜这是一个空操作。

                      IsDeclared_Tricky xyz:1
                      IsDeclared_Tricky foo:1
                      IsDeclared_Tricky 栏:0
                      IsDeclared_Tricky baz: 0
                      IsDeclared xyz: 1
                      IsDeclared foo: 1
                      IsDeclared 栏:0
                      IsDeclared 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 error
                        exampleFunction
                        echo $outputStr
                      }
                      
                      bonus
                      

                      如果调用时声明了所有需要的变量:

                      世界你好!

                      其他:

                      错误:变量未声明:outputStr

                      【讨论】:

                        猜你喜欢
                        • 2014-09-15
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2015-07-14
                        • 2015-06-23
                        • 1970-01-01
                        • 1970-01-01
                        相关资源
                        最近更新 更多