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


151

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

[ -z $mystr ]

但是,如果我想检查变量是否已经定义,该怎么办?还是Bash脚本没有区别?


27
请养成使用[-z“ $ mystr”]而不是[-z $ mystr]的习惯
Charles Duffy

怎么做逆事?我的意思是当字符串不为空时

1
@flow:怎么样[ -n "${VAR+x}"] && echo not null
Lekensteyn

1
@CharlesDuffy我之前已经在很多在线资源中阅读过此内容。为什么要首选它?
2013年

6
@Ayos,因为如果您没有引号,则变量的内容将被字符串分割和分隔;如果它是一个空字符串,就成了[ -z ]代替[ -z "" ]; 如果有空间,就成了[ -z "my" "test" ]代替[ -z my test ]; 如果为[ -z * ],则将*替换为目录中文件的名称。
Charles Duffy 2013年

Answers:


138

我认为Vinko答案暗示(如果没有说明)您所追求的答案,尽管并没有简单地阐明。要区分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[命令以及条件表达式


最近有人通过电子邮件向我询问有关此问题的答案:

您使用了两个测试,我对第二个测试很了解,但对第一个却不太了解。更确切地说,我不了解变量扩展的必要性

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

这样会不会达到相同的目的?

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 is not set at all
MP:1 VAR is not set at all
MP:2 VAR is not set at all

在第二对测试中,设置了变量,但将其设置为空值。这是${VAR=value}${VAR:=value}符号所做出的区别。同样适用于${VAR-value}${VAR:-value},以及${VAR+value}${VAR:+value},依此类推。


正如吉利(Gili)在他的答案中指出的那样,如果bash使用该set -o nounset选项运行,则上述基本答案将失败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 -o nounset使用set +uset -u等于set -o nounset)取消该选项。


7
我对这个答案的唯一问题是,它以一种相当间接和不清楚的方式完成了任务。
瑞士

3
对于那些想要在bash手册页中查找上述含义的人,请查找“参数扩展”部分,然后查找以下文本:“当不执行子字符串扩展时,请使用下面记录的形式,bash测试以下内容:一个未设置或为空的参数['null'表示空字符串]。省略冒号只会对未设置(...)$ {parameter:+ word}的参数进行测试:使用备用值。为null或未设置,则不替换任何内容,否则替换单词的扩展。”
David Tonhofer

2
@Swiss这既不是不清楚也不是间接的,而是惯用的。也许对于一个不熟悉${+}并且${-}还不清楚的程序员来说,但是要成为合格的Shell用户,熟悉这些结构是必不可少的。
威廉·珀塞尔

如果要启用此功能,请参阅stackoverflow.com/a/20003892/14731set -o nounset
吉利2013年

我对此做了很多测试;因为我的脚本中的结果不一致。我建议人们[ -v VAR ]使用bash -s v4.2及更高版本来研究 “ ”方法。

38
~> 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
但我想区分字符串是“”还是从未定义过。那可能吗?
08年

1
这个答案确实说明了如何区分这两种情况;按照bash常见问题解答链接进行更多讨论。
查尔斯·达菲,

1
我补充说,在他发表评论后,查尔斯
Vinko Vrsalovic

2
在bash手册页中查找所有这些“技巧”的“参数扩展”。例如$ {FOO:-default}使用默认值,$ {foo的:=默认}分配缺省值,$ {foo的:错误消息?}如果foo是未设置等来显示错误消息
Jouniķ Seppänen08年

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),则脚本将过早终止。


其他有用的东西:

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

mystr=${mystr- 7}

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

: ${mystr? not defined}

请注意,在这里我使用了':',以免$ mystr的内容在定义时作为命令执行。


我不知道?BASH变量的语法。那是一个很好的收获。
瑞士

这也适用于间接变量引用。这会通过:var= ; varname=var ; : ${!varname?not defined}并终止于:varname=var ; : ${!varname?not defined}。但这是一个好习惯,使用set -u起来容易得多。
2013年

11

测试摘要。

[ -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"

bash的良好做法。有时文件路径包含空格,或者为了防止命令注入
k107 2015年

1
我认为${var+x}没有必要,除非允许变量名称中包含空格。
安妮·范·罗苏姆

不只是好的做法;它需要[得到正确的结果。如果var未设置或为空,[ -n $var ]减小到[ -n ]其中有退出状态0,而[ -n "$var" ]具有为1的预期的(正确)的退出状态
chepner

5

https://stackoverflow.com/a/9824943/14731包含一个更好的答案(一个更具可读性且已set -o nounset启用的答案)。它大致如下所示:

if [ -n "${VAR-}" ]; then
    echo "VAR is set and is not empty"
elif [ "${VAR+DEFINED_BUT_EMPTY}" = "DEFINED_BUT_EMPTY" ]; then
    echo "VAR is set, but empty"
else
    echo "VAR is not set"
fi

4

检查定义的变量的显式方法是:

[ -v mystr ]

1
在任何手册页(用于bash或测试)中都找不到此方法,但是它确实对我有用。.知道添加了什么版本吗?
Ash Berlin-Taylor

1
它在条件表达式中记录testBourne Shell Builtins部分的结尾处由运算符引用。我不知道何时添加它,但是它不是bash基于(基于bash3.2.51的)Apple版本,因此它可能是4.x功能。
乔纳森·莱夫勒

1
这对我不起作用GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)。错误是-bash: [: -v: unary operator expected。根据此处的评论,它至少需要bash 4.2才能运行。
Acumenus 2014年

感谢您检查它何时可用。我以前在各种系统上使用过它,但是突然之间它不起作用。我出于好奇而来到这里,是的,当然,该系统上的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是未设置when ,因此您可以使用条件字符串检查它:

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

应该在任何大于等于3.0的bash版本中可用


1

不要再丢掉这辆自行车了,但想补充一下

shopt -s -o nounset

您可以在脚本顶部添加一些内容,如果未在脚本中的任何位置声明变量,则会出错。您会看到的消息是unbound variable,但正如其他人提到的那样,它不会捕获空字符串或null值。为确保任何单个值都不为空,我们可以在使用进行扩展时测试变量${mystr:?},也称为美元符号扩展,该变量会出错parameter null or not set


1

猛砸参考手册是有关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很糟糕。请注意,bashsupport ${!var_name}作为一种获取名称在中指定的变量的值的方式$var_name,所以name=value; value=1; echo ${name} ${!name}yields value 1
乔纳森·莱夫勒

0

测试未定义变量的简短版本可以是:

test -z ${mystr} && echo "mystr is not defined"

-3

没有任何参数的调用集..它输出所有已定义的变量..
列表中的最后一个将是您的脚本中定义的变量..
因此,您可以将其输出传递给可以弄清楚定义了什么和不知道什么的东西


2
我没有拒绝投票,但是要敲碎一个很小的螺母有点像大锤。
乔纳森·莱夫勒

实际上,我比接受的答案更喜欢这个答案。很明显,这个答案在做什么。被接受的答案就像地狱一样。
瑞士

似乎这个答案更多地是实现“设置”的副作用,而不是期望的结果。
乔纳森·摩根
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.