ChatGPT解决这个技术问题 Extra ChatGPT

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

如何知道 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 没有解决这个问题。

B
BSMP

(通常)正确的方式

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 来说甚至不明显),但有时最好将带有引号的解决方案编写为 { 2},以 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 ]
J
Joshua Pinter

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

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 解决方案并且没有提及任何可移植性要求。
问题是如何查看是否设置了变量。那么你不能假设变量已设置。
我同意,[] 特别适用于 shell(非 bash)脚本。
请注意,-n 是默认测试,因此更简单的 [ "$1" ][[ $1 ]] 也可以工作。另请注意,使用 [[ ]] quoting is unnecessary
似乎它不是真的相反......真正的相反是if [ ! -z "$1" ]
P
Peter Mortensen

以下是如何测试参数是否未设置、为空(“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       |  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           | ""              |
+--------------------+----------------------+-----------------+-----------------+

@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}
P
Peter Mortensen

虽然此处所述的大多数技术都是正确的,但 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 将返回一个错误并终止。 $# 非常适合检查位置参数。但是,命名变量需要其他方式,而不是破坏,将它们标识为未设置。不幸的是,这个功能来得太晚了,因为许多人注定要兼容早期版本,在这种情况下他们不能使用它。唯一的其他选择是使用技巧或“黑客”来绕过它,如上所示,但这是最不雅的。
这就是为什么我在接受/最高投票的答案之后继续阅读。
这是标量的正确方法,并且在 ksh 中也可用。注意:如果您不想浪费半个小时盯着屏幕想知道发生了什么,它是 [[ -v foo ]] 而不是 [[ -v $foo ]]。除了验证它是否已设置之外,您通常还会寻找一个特定的值,例如 123。在这种情况下,我更喜欢使用 [[ ${foo-123} -eq 123 ]] 来压缩 [[ -v foo && $foo -eq 123 ]]
P
Peter Mortensen

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

if [ -z "$1" ]

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


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

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

参数扩展 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 退出 127 退出 127 ${VARIABLE:+default} "default" "" ""

请注意,每个组(带和不带前冒号)具有相同的设置和未设置案例,因此唯一不同的是如何处理空案例。

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

标题:

set 表示 VARIABLE 非空 (VARIABLE="something")

empty 表示 VARIABLE 为空/null (VARIABLE="")

unset 表示 VARIABLE 不存在(未设置 VARIABLE)

价值观:

$VARIABLE 表示结果是变量的原始值。

“默认”表示结果是提供的替换字符串。

"" 表示结果为 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"
使用新 table feature 的候选者。
p
phkoester

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

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 如果 $variable 已设置)。它还有更多详细信息解释了为什么它是正确的。
M
Mark Haferkamp

笔记

由于 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 中失败时

这仅适用于命名变量(包括 $_),不适用于某些特殊变量($!$@$#$$$*$?$-$0$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中不同方法的比较代码

此函数取消设置变量 vareval 是通过的代码,运行测试以确定 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 至少从 3.0 开始,对于在 Bash 中测试命名变量是(100%?)可靠的。 [ -n "${var+x}" ] 在符合 POSIX 的情况下是可靠的,但不能处理数组。存在其他测试来检查变量是否为非空,以及检查其他 shell 中声明的变量。但是这些测试既不适合 Bash 也不适合 POSIX 脚本。


在我尝试过的答案中,这个(declare -p var &>/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" 这样的东西。
@xebeche 经过更多测试,似乎 set -o nounset; (echo "$var") &>/dev/null[ -n "${var+x}" ] 具有相同的真值,因此唯一的区别是 set -o nounset 导致脚本/子shell退出。也就是说,我同意我的回答 应该 解释变量被 set 与仅 declared 之间的区别,而不是将这些情况混为一谈。随意编辑。在下一次常年通知之前,我不会回到这个答案。
P
Peter Mortensen

在现代版本的 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
P
Paul Creasey
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 打开,这将失败。
i
infokiller

概括

使用 test -n "${var-}" 检查变量是否不为空(因此也必须定义/设置)。用法: if test -n "${var-}";然后 echo "var is set to <$var>" else echo "var is not set or empty" fi

使用 test -n "${var+x}" 检查变量是否已定义/设置(即使它是空的)。用法: if test -n "${var+x}";然后 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 以更好地理解它。


看起来您在对 test -z 的解释中不小心添加了“非”。来自测试的手册页:-z STRING 如果 STRING 的长度为零,则返回 true。因此,如果 var 未定义或为空,test -z "${var-}" 将返回 true。
P
Peter Mortensen

如果未设置,您想退出

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

#!/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(评论),他要求我准确回答问题。

参考:2.6.2 Parameter Expansion


R
RubenLaguna

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

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

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


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

阅读 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}
你从哪里拿来这个的?精彩的!它只是有效!它又短又优雅!谢谢!
C
Community

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

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

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


M
Mike Clark

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

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


干得好,迈克。看起来不错。就像您说的那样,它并不全面,但可以很快找到并进行比较。
P
Peter Mortensen

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

#! /bin/bash -u

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

echo $SOMEVAR

它应该做什么?
P
Peter Mortensen

为了清楚地回答 OP 关于如何确定是否设置变量的问题,Lionel's answer 是正确的:

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

M
Mark Haferkamp

使用 [[ -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 并打印错误消息。 &> /dev/null: 抑制来自 declare -p 的输出而不影响其返回码。


c
codaddict

你可以做:

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

或者你可以做 -n 而不是否定 -z
您需要在 $1 周围加上引号,即 [ ! -z "$1" ]。或者您可以在 bash/ksh/zsh 中编写 [[ ! -z $1 ]]
如果 $1 设置为空字符串,这将失败。
P
Peter Mortensen

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


C
Community

启用 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; #' 明白了
j
jarno

我的首选方式是这样的:

$ 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 =“未设置”)。


P
Peter Mortensen

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

如果未设置,则设置默认 MY_VAR 的简单单行,否则您可以选择显示消息:

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

P
Peter Mortensen

这是我每天使用的:

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

这在 Linux 和 Solaris 到 Bash 3.0 下运行良好。

$ myvar="TEST"
$ is_set myvar ; echo $?
0

$ myvar=
$ is_set myvar ; echo $?
0

$ unset myvar
$ is_set myvar ; echo $?
1

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

或者

(( ${#foo} ))

或者

let ${#foo}

或者

declare -p foo

佚名

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

if [[ $1 = "" ]]
then
  echo '$1 is blank'
else
  echo '$1 is filled up'
fi

为什么这一切? $@ 中的所有内容都存在于 Bash 中,但默认情况下它是空白的,因此 test -ztest -n 无法帮助您。

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

if [ ${#1} = 0 ]
then
  echo '$1 is blank'
else
  echo '$1 is filled up'
fi

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

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

浏览所有答案后,这也有效:

if [[ -z $SOME_VAR ]]; then read -p "Enter a value for SOME_VAR: " SOME_VAR; fi
echo "SOME_VAR=$SOME_VAR"

如果你不放 SOME_VAR 而不是我所拥有的 $SOME_VAR,它会将其设置为空值; $ 是必要的。


当您使 set -u 生效时将无法工作,这将打印 unboud variable 错误
P
Peter Mortensen

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

因为我首先在这个实现中遇到了错误(受 answers of Jens and 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 确实声明 var(除非后跟 '=')。有一段时间我以为我是这样声明变量的,现在才发现我只是表达了我的意图……我猜这是一个空操作。

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

bonus

如果在声明所有必需变量的情况下调用:

你好世界!

别的:

错误:未声明变量:outputStr