如何在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个回答

3293

(通常)正确的方法

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

${var+x} 是一个参数展开,如果 var 未设置,则计算结果为空,否则替换为字符串 x

引号离题

可以省略引号(因此我们可以说 ${var+x} 而不是 "${var+x}"),因为这种语法和用法保证只会展开为不需要引号的内容(因为它要么展开为 x(它不包含单词间断,因此不需要引号),要么展开为空(这将导致 [ -z ],方便地计算出与 [ -z "" ] 相同的值(true))。

然而,虽然可以安全地省略引号,但并非所有人都能立即意识到这一点(甚至对于这个引号解释的第一作者,他也是一个重要的Bash编码者),有时最好使用带引号的解决方案[ -z "${var+x}" ],即使会付出微不足道的O(1)速度惩罚成本。该解决方案的第一作者还在使用此解决方案的代码旁添加了评论,并提供了指向此答案的URL,现在该答案还包括了为什么可以安全省略引号的解释。

(常常)错误的方式

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

这种方法通常是错误的,因为它不能区分未设置变量和将变量设置为空字符串的情况。也就是说,如果var='',那么上述解决方案将输出“var为空白”。
在用户必须指定扩展名或其他属性列表并且不指定它们会默认使用非空值的情况下,未设置和“设置为空字符串”之间的区别至关重要,而指定空字符串应使脚本使用空扩展名或附加属性列表。
然而,在某些情况下,这种区别可能并不重要。在这些情况下,[ -z "$var" ]就足够了。

53
@Garrett,你的编辑使得这个答案不正确了,${var+x}是正确的替换方式。使用 [ -z ${var:+x} ] 和使用 [ -z "$var" ] 没有任何区别。 - Graeme
18
无论var是否设置了值(通过"unset var"清除后,"echo $var"无输出),都会得到“not set”的结果。这个方法不起作用。 - Brent212
18
关于解决方案语法中的 ${parameter+word},官方手册在 http://www.gnu.org/software/bash/manual/bashref.html#Shell-Parameter-Expansion 中有相关章节;然而,存在一个错误,它并没有非常清楚地提到这个语法,只是简单地说:“省略冒号[“:”]只会针对未设置变量的情况进行测试 .. ${parameter:+word} [的意思是] 如果 parameter 为空或未设置,则不会替换任何内容,否则会替换为 word 的扩展内容。”。引用的文献 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02(值得了解)在此提供了更清晰的文档。 - Destiny Architect
11
当您使用多个表达式时,似乎需要使用引号,例如在bash 4.3-ubuntu中使用[ -z "" -a -z ${var+x} ]会出现bash: [: argument expected的错误提示(但在zsh中则不会)。我非常不喜欢使用某些情况下恰好有效的语法来回答问题。 - FauxFaux
73
使用简单的[ -z $var ]与省略引号一样,并不更加“错误”。无论哪种方式,你都在对输入作出假设。如果你可以将空字符串视为未设置的内容,那么[ -z $var ]就足够了。 - nshew13
显示剩余44条评论

1324

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

if [ -n "$1" ]

这是与-z相反的选项。我发现自己使用-n的频率比使用-z更高。

你可以像这样使用它:

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

114
我通常更喜欢使用[[ ]]而不是[ ],因为在某些情况下[[ ]]更加强大且会引起较少的问题(有关两者之间区别的解释,请参见[此问题](https://dev59.com/O3A75IYBdhLWcg3wMl8Z))。该问题明确要求使用*bash*解决方案,并未提及任何可移植性要求。 - Flow
26
问题是如何确定一个变量是否已经“设置”。因此,您不能假设该变量已经被设置。 - HelloGoodbye
10
我同意,[] 在非 bash 的脚本中表现更好。 - Hengjie
85
请注意,-n 是默认测试,因此更简单的写法是 [ "$1" ][[ $1 ]]。 同时请注意,在使用 [[ ]]不需要引用 - tne
7
看起来它并不是真正的相反...真正的相反应该是if [ ! -z "$1" ] - The Godfather
显示剩余12条评论

774

以下是如何测试参数是否未设置、为空或已设置值的方法:

+--------------------+----------------------+-----------------+-----------------+
|   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:参数扩展

在所有显示为“替换”(substitute)的情况下,表达式都将被替换为所示的值。在所有显示为“赋值”(assign)的情况下,参数都将被赋以该值,该值也将替换表达式。

为了演示这种操作:

+--------------------+----------------------+-----------------+-----------------+
|   Expression       |  When FOO="world"    |  When 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           | ""              |
+--------------------+----------------------+-----------------+-----------------+

8
是的,它确实有效:set foo; echo ${1:-set but null or unset} 会输出 "foo";set --; echo ${1:-set but null or unset} 则会输出 "set but null"。 - Jens
6
定位参数可以使用 set 命令设置。 - Jens
155
这个回答很令人困惑。能否提供一些实际的例子来说明如何使用这个表格? - Ben Davis
16
详细信息在指向POSIX标准的链接中有解释,该链接引用了表格所在的章节。我在几乎所有编写的脚本中都会使用一个结构 : ${FOOBAR:=/etc/foobar.conf} 来为变量设置默认值,如果变量未设置或为空,则使用默认值。 - Jens
3
parameter 是任何变量名称。word 是根据您使用表格中的哪种语法以及变量 $parameter 是否设置、设置但为空还是未设置而进行替换的某个字符串。不要使事情更加复杂,但是 word 也可以包括变量!这对于通过字符分隔可选参数非常有用。例如:${parameter:+,${parameter}} 如果它被设置但不为空,则输出逗号分隔的 ,$parameter。在这种情况下,word,${parameter} - TrinitronX
显示剩余8条评论

461

虽然这里列出的大部分技巧是正确的,但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


14
在Bash 4.1.2中,无论变量是否已设置,[[ -v aaa ]]; echo $? ==> -bash: 条件二进制运算符预期 -bash:'aaa'附近有语法错误 - Dan
45
在Bash 4.2中,为'test'内置命令添加了'-v'参数。 - Ti Strga
23
-v参数被添加作为-u shell选项(nounset)的补充。当打开'nounset'(set -u)时,如果shell不是交互式的,则会返回错误并终止。$#可以检查位置参数,但是对于命名变量需要其他方法来确定它们是否未设置,而不是覆盖它们。很遗憾,这个特性来得太晚了,因为许多人必须保持早期版本兼容,否则他们将无法使用它。唯一的另一个选择是使用技巧或“hack”来绕过它,如上所示,但这是最不优雅的。 - osirisgothra
61
这就是为什么在接受/得票最高答案之后我还继续阅读的原因。 - Joe
10
这是标量的正确方法,ksh也可用。注意:如果您不想在屏幕上盯着浪费半小时时间想弄明白发生了什么,请使用[[ -v foo]]而不是 [[ -v $foo ]]。除了验证已设置外,通常还要查找特定值,例如 123,在这种情况下,我更喜欢使用[[ ${foo-123} -eq 123 ]] 来压缩 [[ -v foo && $foo -eq 123 ]] - Amit Naidu
显示剩余13条评论

216

有很多方法可以实现这个需求,以下是其中一种方法:

if [ -z "$1" ]

如果$1为null或未设置,则此操作成功。


58
一个未设置的参数和一个值为 null 的参数之间有区别。 - chepner
29
我只是想要一条吹毛求疵的评论,并指出这个答案与问题所提出的相反。问题询问的是变量是否已被设置,而不是变量是否没有被设置。 - Philluminati
50
“[[ ]]”只是一个可移植性的陷阱。此处应该使用“if [ -n "$1" ]”。 - Jens
78
这个答案是错误的。问题要求告诉一个变量是否被定义,而不是检查它是否被设置为非空值。对于 foo=""$foo 有一个值,但 -z 只会报告它是空的。如果你使用了 set -o nounset,那么 -z $foo 将会崩溃。 - Keith Thompson
4
这取决于“变量被设置”的定义。如果您想检查变量是否被设置为一个非零字符串,那么mbranning的回答是正确的。但是,如果您想检查变量是否已声明但未初始化(即foo=),那么Russel的回答是正确的。 - Flow
显示剩余5条评论

178

我总觉得在其他答案中的POSIX表格不易理解,所以这是我的解释:

参数扩展 VARIABLE已设置 VARIABLE为空 VARIABLE未设置
${VARIABLE-default} $VARIABLE "" "default"
${VARIABLE=default} $VARIABLE "" $(VARIABLE="default")
${VARIABLE?default} $VARIABLE "" 退出码为127
${VARIABLE+default} "default" "default" ""
${VARIABLE:-default} $VARIABLE"默认值" "默认值"
${VARIABLE:=默认值} $VARIABLE $(VARIABLE="默认值") $(VARIABLE="默认值")
${VARIABLE:?默认值} $VARIABLE exit 127 exit 127
${VARIABLE:+默认值} "默认值" "" ""

请注意,每个组(有和没有前置冒号的)都具有相同的设置和取消设置情况,因此唯一的区别在于如何处理空情况。

使用前置冒号时,空和未设置情况是相同的,因此尽可能使用这些情况(即使用:=,而不仅仅是=,因为空情况是不一致的)。

标题:

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

值:

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

1
你只修复了实际的编码错误(在处理变量时出现不必要的间接引用)。我进行了编辑,以修复更多情况,在解释描述时美元符号有所不同。顺便说一下,所有shell变量(除了数组)都是字符串变量;它们可能只包含可以解释为数字的字符串。仅谈论字符串会使信息模糊。引号与字符串无关,它们只是转义的替代方法。VARIABLE =“”可以写成VARIABLE =。但前者更易读。 - Palec
感谢提供这个表格,非常有用。但是我想让脚本在未设置或为空时退出。但是我得到的退出代码是1而不是127。 - Astronaut
1
根据文档所述,“在所有情况下,参数的最终值都将被替换。”因此,也许应该将描述更改为:$(VARIABLE="default")表示结果为"default",并且VARIABLE也将被设置为"default" - Tom
@Astronaut,我错过了你的评论,但听起来你的脚本已经设置了errexit,这掩盖了真正的退出状态。 - deizel.
1
一个适合使用新的表格功能的候选人。 - Peter Mortensen
显示剩余2条评论

76

为了判断变量是否不为空,我使用

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" - user1985657
@Bhaskar 我认为这是因为这个答案已经在半年前提供了基本相同的答案(使用${variable+x}来获取x,当且仅当$variable被设置),而且它有更多详细的解释为什么是正确的。 - Mark Haferkamp

54

注意

由于标签为bash,因此我将提供一个重点关注Bash的答案。

简短回答

只要您在Bash中处理命名变量,即使它是一个空数组,此函数也应始终告诉您变量是否已设置。

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

为什么这个方法有效

在Bash中(至少从3.0版本开始),如果var是一个已声明/设置的变量,那么declare -p var输出一个declare命令,该命令将变量var设置为其当前类型和值,并返回状态码0(成功)。如果var未声明,则declare -p var将向stderr输出错误消息,并返回状态码1。使用&>/dev/null,将普通的stdoutstderr输出重定向到/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兼容性说明

如果您的脚本包含数组,并且您想要使其与尽可能多的shell兼容,请考虑使用typeset -p而不是declare -p。我读到ksh仅支持前者,但无法测试。我知道Bash 3.0+和Zsh 5.5.1都支持typeset -pdeclare -p,只是在另一个上提供替代。但我没有测试除这两个关键字之外的差异,也没有测试其他shell。
如果您需要使您的脚本与POSIX sh兼容,则不能使用数组。没有数组,[ -n "${var+x}" ]有效。

Bash中不同方法的比较代码

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

我跳过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脚本。

5
我尝试过多种答案,但只有这个 (declare -p var &>/dev/null) 能正常工作。谢谢! - user1387866
1
干得好!一个警告:declare var声明了一个变量,但它并没有在set -o nounset方面设置它:使用declare foo bar =; set -o nounset; echo $bar; echo $foo进行测试。 - xebeche
@joehanna 感谢你发现那个缺失的 /!我已经修复了,但是考虑到 @xebeche 的评论,我认为这个逻辑足够混乱,需要一个函数来处理。@xebeche 这对我的答案很不方便...看起来我得尝试一些像 declare -p "$1" | grep = 或者 test -v "$1" 这样的东西。 - Mark Haferkamp
经过更多的测试,似乎 set -o nounset; (echo "$var") &>/dev/null[ -n "${var+x}" ] 具有相同的真值,因此唯一的区别是 set -o nounset 导致脚本/子shell退出。话虽如此,我认为我的答案应该解释变量被 设置 和仅仅 声明 的区别,而不是混淆这些情况。请随意编辑。在下一个永久通知之前,我不会回到这个答案。 - Mark Haferkamp
请注意,如果您想检查数组的元素是否已设置,则declare无法工作;您可以使用${var[idx]+x}来实现。您还可以使用"${var[*]+x}",但在某些情况下会失败,这一点与#@相同;请注意,"${var[@]+x}"很奇怪,因为它会扩展为多个单词,在这种情况下是0,从而改变了所调用的test形式(如果您使用[[而不是[则没有问题)。我认为test -v的优点在于它是唯一适用于整个变量及其元素的方法。 - o11c
显示剩余4条评论

44
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
第一个测试是反向的;我认为你想要 [ "$1" != "" ](或者 [ -n "$1" ])... - Gordon Davisson
12
如果$1被设置为空字符串,这将会失败。 - HelloGoodbye
2
答案的第二部分(计算参数)是对第一部分答案无法在$1设置为空字符串时工作的有用解决方法。 - Mark Berry
如果打开了 set -o nounset,这将失败。 - Flimm
我是不是疯了?使用[[ -n $1 ]] && FOO=$1从bash中获取脚本参数未设置FOO变量。而使用[[ "$1" != "" ]] && FOO=$1则可以正常工作。bash 4.2.46(2)-release (x86_64-redhat-linux-gnu)。 - gaoithe

38

在现代版本的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

5
请注意,[ -v "$VARNAME" ] 不是错误的写法,只是进行了不同的测试。假设 VARNAME=foo,那么它检查是否存在一个名为 foo 的变量并且被赋值了。 - chepner
5
想要可移植性的注意事项,-v 不符合 POSIX 标准。 - kzh
如果出现“[: -v:unexpected operator”错误,则必须确保使用的是“bash”而不是“sh”。 - koppor

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