如何在Bash shell脚本中判断一个字符串是否未定义

168

如果我想检查空字符串,我会这样做

[ -z $mystr ]

但是如果我想要检查变量是否被定义了呢?在Bash脚本中有没有区别呢?


31
请养成使用 [ -z "$mystr" ] 的习惯,而不是 [ -z $mystr ]。 - Charles Duffy
1
如何做相反的事情?我的意思是当字符串不为空时 - Open the way
1
@flow: 对于 [ -n "${VAR+x}"] && echo not null 怎么看? - Lekensteyn
2
@CharlesDuffy 我之前在很多在线资源中读到过这个。为什么这是首选? - ffledgling
9
因为如果没有引号,变量的内容会被拆分并进行全局匹配;如果是空字符串,则变成 [ -z ] 而不是 [ -z "" ] ;如果有空格,则会变成 [ -z "my" "test" ] 而不是 [ -z my test ];而如果变量是 [ -z * ] ,则 * 将被替换为目录中文件的名称。 - Charles Duffy
显示剩余2条评论
12个回答

154

我认为你需要的答案已经在Vinkoanswer中暗示(如果没有明确说明),尽管它并没有简单地表述出来。要区分VAR是设置为空还是未设置,您可以使用:

if [ -z "${VAR+xxx}" ]; then echo "VAR is not set at all"; fi
if [ -z "$VAR" ] && [ "${VAR+xxx}" = "xxx" ]; then echo "VAR is set but empty"; fi

您可以通过以下方式将第二行的两个测试组合成一个测试:

if [ -z "$VAR" -a "${VAR+xxx}" = "xxx" ]; then echo "VAR is set but empty"; fi

然而,如果你阅读Autoconf的文档,会发现他们不建议将术语与'-a'结合使用,并建议使用单独的简单测试与&&组合。我没有遇到过存在问题的系统;这并不意味着它们以前不存在(但即使它们在遥远的过去不那么罕见,如今也可能极其罕见)。
你可以在Bash手册中找到这些细节,以及其他相关的shell参数扩展, test[命令和条件表达式

最近我收到了一封关于这个答案的邮件,邮件中提到:

You use two tests, and I understand the second one well, but not the first one. More precisely I don't understand the need for variable expansion

if [ -z "${VAR+xxx}" ]; then echo "VAR is not set at all"; fi

Wouldn't this accomplish the same?

if [ -z "${VAR}" ]; then echo "VAR is not set at all"; fi
公平的问题 - 答案是“不,你更简单的替代方案并没有做同样的事情”。
假设在你的测试之前我写了这个:
VAR=

你的测试会显示“VAR根本没有设置”,但我的测试将会(通过暗示,因为它没有输出任何内容)显示“VAR已经设置,但其值可能为空”。试试这个脚本:

(
unset VAR
if [ -z "${VAR+xxx}" ]; then echo "JL:1 VAR is not set at all"; fi
if [ -z "${VAR}" ];     then echo "MP:1 VAR is not set at all"; fi
VAR=
if [ -z "${VAR+xxx}" ]; then echo "JL:2 VAR is not set at all"; fi
if [ -z "${VAR}" ];     then echo "MP:2 VAR is not set at all"; fi
)

输出结果如下:
JL:1 VAR根本没有被设置
MP:1 VAR根本没有被设置
MP:2 VAR根本没有被设置
在第二组测试中,变量被设置了,但是它被设置为空值。这就是${VAR=value}${VAR:=value}的区别所在。同样适用于${VAR-value}${VAR:-value}${VAR+value}${VAR:+value}等等。

正如Gili在他的answer中指出的那样,如果你使用set -o nounset选项运行bash,那么上面的基本答案将失败,并显示unbound variable。解决方法很简单:

if [ -z "${VAR+xxx}" ]; then echo "VAR is not set at all"; fi
if [ -z "${VAR-}" ] && [ "${VAR+xxx}" = "xxx" ]; then echo "VAR is set but empty"; fi

或者您可以使用set +u取消set -o nounset选项(set -u等同于set -o nounset),这样也可以。


7
我对这个答案唯一的问题是它完成任务的方式相当间接和不清晰。 - Swiss
3
想查看在 bash man 手册中上述含义的描述的人,请查找“参数扩展”部分,然后查找此文本:“当不执行子字符串扩展时,在下面记录的形式中使用,bash 测试未设置或为空的参数('null' 意为空字符串)。省略冒号只会测试未设置的参数(...)${parameter:+word}:使用替代值。如果 parameter 为空或未设置,则不会替换任何内容;否则将替换 word 的扩展。” - David Tonhofer
2
@Swiss 这既不含糊也不间接,而是习惯用语。 也许对于不熟悉 ${+}${-} 的程序员来说,这可能不清楚,但如果要成为一个熟练的 shell 用户,熟悉这些结构是必要的。 - William Pursell
如果您想启用set -o nounset,请查看https://dev59.com/R3VC5IYBdhLWcg3woSxW#20003892以实现此功能。 - Gili
我对此进行了大量测试,因为我的脚本结果不一致。我建议大家使用_bash_ v4.2及以上版本中的"[ -v VAR ]"方法。 - will
显示剩余3条评论

44
~> if [ -z $FOO ]; then echo "EMPTY"; fi
EMPTY
~> FOO=""
~> if [ -z $FOO ]; then echo "EMPTY"; fi
EMPTY
~> FOO="a"
~> if [ -z $FOO ]; then echo "EMPTY"; fi
~> 

-z也适用于未定义的变量。为了区分已定义和未定义的变量,可以使用这里列出的内容或者这里提供的更清晰的解释。

最干净的方法是使用扩展,就像这些示例中所示。要获取所有选项,请查看手册中的参数扩展部分。

替代单词:

~$ unset FOO
~$ if test ${FOO+defined}; then echo "DEFINED"; fi
~$ FOO=""
~$ if test ${FOO+defined}; then echo "DEFINED"; fi
DEFINED

默认值:

~$ FOO=""
~$ if test "${FOO-default value}" ; then echo "UNDEFINED"; fi
~$ unset FOO
~$ if test "${FOO-default value}" ; then echo "UNDEFINED"; fi
UNDEFINED

当然,你会不同地使用它们,用你想要的值而不是 "默认值",并直接使用扩展(如果适用)。


1
但是我想区分字符串是“”还是从未定义过。这可能吗? - Setjmp
1
这个答案告诉了如何区分这两种情况;请跟随它提供的Bash FAQ链接以获取更多讨论。 - Charles Duffy
1
我在查尔斯的评论后添加了这个。 - Vinko Vrsalovic
2
在bash man页面中查找“参数扩展”,以获取所有这些“技巧”。例如,${foo:-default} 用于使用默认值,${foo:=default} 用于分配默认值,${foo:?error message} 用于显示错误消息(如果未设置foo),等等。 - Jouni K. Seppänen

18

高级Bash脚本指南,10.2. 参数替换:

  • ${var+blahblah}:如果var已定义,则用'blahblah'替换表达式,否则用null替换
  • ${var-blahblah}:如果var已定义,则用它本身替换,否则用'blahblah'替换
  • ${var?blahblah}:如果var已定义,则用它本身替换,否则函数以'blahblah'作为错误消息退出。

如果你想要根据变量$mystr是否被定义来构建程序逻辑,你可以按照以下方式进行:

isdefined=0
${mystr+ export isdefined=1}

现在,如果isdefined=0,则变量未定义,如果isdefined=1,则变量已定义。

这种检查变量的方式比以前的答案更好,因为它更优雅、可读,并且如果您的Bash shell配置为在使用未定义变量时出错(set -u),则脚本将过早终止。

其他有用的东西:

如果$mystr未定义,则将默认值7分配给$mystr,并保持其不变:

mystr=${mystr- 7}

如果变量未定义,打印错误消息并退出函数:

: ${mystr? not defined}

注意在这里我使用了“:”,以防$mystr的内容在定义时被作为命令执行。

我之前不知道 BASH 变量中的 ? 语法。这个真是个好发现。 - Swiss
这也适用于间接变量引用。这个通过:var= ; varname=var ; : ${!varname?not defined},而这个终止:varname=var ; : ${!varname?not defined}。但是使用 set -u 是一个好习惯,它可以更轻松地完成相同的任务。 - ceving

17

测试摘要。

[ -n "$var" ] && echo "var is set and not empty"
[ -z "$var" ] && echo "var is unset or empty"
[ "${var+x}" = "x" ] && echo "var is set"  # may or may not be empty
[ -n "${var+x}" ] && echo "var is set"  # may or may not be empty
[ -z "${var+x}" ] && echo "var is unset"
[ -z "${var-x}" ] && echo "var is set and empty"

1
在Bash中的良好实践。有时文件路径中会有空格,或者为了防止命令注入。 - k107
1
我认为${var+x}只有在变量名称允许包含空格时才是必要的。 - Anne van Rossum
不仅是良好的实践;使用 [ 获取正确的结果是必需的。如果 var 未设置或为空,则 [ -n $var ] 简化为 [ -n ],其退出状态为0,而 [ -n "$var" ] 具有预期(和正确的)退出状态1。 - chepner

5

5

检查变量是否已定义的明确方法如下:

[ -v mystr ]

1
在bash或test的man页面中找不到这个,但它对我有效。有任何版本是什么时候添加的的想法吗? - Ash Berlin-Taylor
2
它在条件表达式中有记录,引用自Bourne Shell内置命令一节的test运算符。我不知道它是何时添加的,但它不在苹果版本的bash(基于bash 3.2.51)中,所以它可能是4.x版本的功能。 - Jonathan Leffler
1
这在我的 GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu) 上不起作用。错误是 -bash: [: -v: unary operator expected。根据这里的评论,它需要至少bash 4.2才能工作。 - Asclepius
感谢您检查其可用性。我之前在各种系统上都使用过它,但突然间它就不起作用了。我出于好奇来到这里,当然,这个系统上的bash是3.x bash。 - clacke

1
另一个选项是使用"列表数组索引"扩展
$ unset foo
$ foo=
$ echo ${!foo[*]}
0

$ foo=bar
$ echo ${!foo[*]}
0

$ foo=(bar baz)
$ echo ${!foo[*]}
0 1

唯一情况下,它会扩展为空字符串是当foo未设置时,因此您可以使用字符串条件来检查它。
$ unset foo
$ [[ ${!foo[*]} ]]; echo $?
1

$ foo=
$ [[ ${!foo[*]} ]]; echo $?
0

$ foo=bar
$ [[ ${!foo[*]} ]]; echo $?
0

$ foo=(bar baz)
$ [[ ${!foo[*]} ]]; echo $?
0

应该在任何Bash 3.0或更高版本中可用。


1

不想再多说这辆自行车了,但是想要补充一下

shopt -s -o nounset

在脚本的顶部可以添加一些内容,如果变量在脚本中没有被声明,它将会出现错误。

你会看到的消息是未绑定的变量,但正如其他人提到的那样,它不会捕捉空字符串或null值。

为了确保任何单个值不为空,我们可以在扩展变量时测试变量,使用${mystr:?},也称为美元符号扩展,这将出现错误参数为空或未设置


1

Bash参考手册是关于Bash的权威信息来源。

这里有一个测试变量是否存在的示例:

if [ -z "$PS1" ]; then
        echo This shell is not interactive
else
        echo This shell is interactive
fi

(来自6.3.2章节。)

请注意,在开放的[]之后以及之前的空格是不可选的


Vim 用户小技巧

我有一个脚本,其中有几个声明如下:

export VARIABLE_NAME="$SOME_OTHER_VARIABLE/path-part"

但我希望他们遵从任何现有的价值观。所以,我将它们改写成了这样:

if [ -z "$VARIABLE_NAME" ]; then
        export VARIABLE_NAME="$SOME_OTHER_VARIABLE/path-part"
fi

我能够使用快速的正则表达式在Vim中自动化这个过程:
s/\vexport ([A-Z_]+)\=("[^"]+")\n/if [ -z "$\1" ]; then\r  export \1=\2\rfi\r/gc

这可以通过在可视化选择相关行,然后键入:来应用。命令栏会自动填充为:'<,'>。粘贴上述命令并按下Enter


这是在此版本的Vim上进行测试的:
VIM - Vi IMproved 7.3 (2010 Aug 15, compiled Aug 22 2015 15:38:58)
Compiled by root@apple.com

Windows用户可能需要不同的行尾。


0

这是我认为更清晰的检查变量是否已定义的方法:

var_defined() {
    local var_name=$1
    set | grep "^${var_name}=" 1>/dev/null
    return $?
}

使用方法如下:

if var_defined foo; then
    echo "foo is defined"
else
    echo "foo is not defined"
fi

1
编写函数不是一个坏主意;调用 grep 真的很糟糕。请注意,bash 支持 ${!var_name} 作为获取变量名指定的变量值的一种方式,因此 name=value; value=1; echo ${name} ${!name} 会输出 value 1 - Jonathan Leffler

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