Bash技巧:对比 test、[、[[ 判断字符串是否为空的用法

霜鱼片

在 bash 中,test 命令、[ 命令、[[ 命令都可以用于进行一些判断。
例如,这三个命令都可以用来判断字符串是否为空。
实际使用时,这几个命令的用法有一些异同和一些注意事项。
具体说明如下。

test 命令 和 [ 命令的关系

在 bash 中,[ 关键字本身是一个命令,它不是 if 命令的一部分。
执行 help [ 命令,有如下说明:

[: [ arg... ]
Evaluate conditional expression.
This is a synonym for the "test" builtin, but the last argument must be a literal ], to match the opening [.

即,[ 命令是 test 命令的同义词,它对条件表达式的判断结果和 test 命令完全一样。
但是 [ 命令要求该命令的最后一个参数必须是 ],看起来是闭合的方括号效果。

在实际使用中,test 命令 和 [ 命令常常跟 if 命令、while 命令结合使用。
但这并不是必须的。test 命令 和 [ 命令本身都是独立的命令,可以单独执行。

后面会统一用 test 命令来说明它的用法。这些说明都适用于 [ 命令。

注意] 自身不是 bash 的命令,它只是 [ 命令要求的参数,且必须是最后一个参数。

注意:在使用 [ 命令时,最大的误区是在这个命令之后没有加空格。
例如 [string1 != string2] 这种写法是错误的。
要时刻注意 [ 本身是一个命令,这个命令名就是 [
在这个命令之后会跟着一些参数,要用空格把命令名和参数隔开。
[string1 这个写法实际上会执行名为 [string1 的命令,不是执行 [ 命令。

类似的,] 本身是一个参数,它也要用空格来隔开其他参数。
string2] 这个写法实际上是一个名为 "string2]" 的参数。
而不是 string2] 两个参数。

用 test 命令判断字符串是否为空

执行 help test 命令,有如下说明:

test: test [expr]
    Evaluate conditional expression.
    Exits with a status of 0 (true) or 1 (false) depending on
    the evaluation of EXPR.

    The behavior of test depends on the number of arguments.
    Read the  bash manual page for the complete specification.

    String operators:
      -z STRING      True if string is empty.

      -n STRING
         STRING      True if string is not empty.

即,test 命令使用 -z STRING 操作符来判断 STRING 字符串的长度是否为 0。
如果为 0,就是空字符串,会返回 true。
具体写法是 test -z STRING,使用 [ 命令则写为 [ -z STRING ]

-n STRING 操作符判断 STRING 字符串的长度是否为 0。
如果不为 0,就不是空字符串,会返回 true。
具体写法是 test -n STRING,使用 [ 命令则写为 [ -n STRING ]
可以省略 -n 操作符,直接写为 test STRING、或者 [ STRING ]

注意:在实际使用时,要注意下面几点:

  • 当判断变量值对应的字符串是否为空时,一定要用双引号把变量值括起来。否则变量值为空、或者带有空格时,会返回异常的结果。
    例如,要写为 test -n "$string",不建议写为 test -n $string
  • bash 是以 0 作为 true,以 1 作为 false。上面 test 命令的说明也是如此。
    而大部分编程语言是以 1 作为 true,0 作为 false。要注意区分,避免搞错判断条件的执行关系。
  • 上面 help test 的说明提到,test 命令的参数个数会影响它的行为。具体要参考 man bash 的说明。
    不同的参数个数会导致 test 命令返回很多不预期的结果。

下面用一个 empty_string.sh 脚本来举例说明 test 命令和 [ 命令判断字符串是否为空的方法,其内容如下:

#!/bin/bash

function empty_string()
{
    if test -n $1; then
        echo '(1) -n $1  :' "No quote: not empty."
    fi

    if [ -z $1 ]; then
        echo '(2) -z $1  :' "No quote: empty."
    fi

    if test -n "$1"; then
        echo '(3) -n "$1":' "Quote   : not empty."
    fi

    if [ -z "$1" ]; then
        echo '(4) -z "$1":' "Quote   : empty."
    fi
}

empty_string "$1"

这个脚本使用 test 命令的 -n-z 操作符来判断传入脚本的第一个参数是否为空字符串,并对比加双引号和不加双引号把变量值括起来的测试结果。

具体执行结果如下:

$ ./empty_string.sh go
(1) -n $1  : No quote: not empty.
(3) -n "$1": Quote   : not empty.
$ ./empty_string.sh "go on"
./empty_string.sh: line 5: test: go: binary operator expected
./empty_string.sh: line 9: [: go: binary operator expected
(3) -n "$1": Quote   : not empty.
$ ./empty_string.sh
(1) -n $1  : No quote: not empty.
(2) -z $1  : No quote: empty.
(4) -z "$1": Quote   : empty.

可以看到,执行 ./empty_string.sh go 命令,传入的第一个参数值没有包含空格,$1 变量值加不加双引号的判断结果都正确。

执行 ./empty_string.sh "go on" 命令,传入的第一个参数值包含空格。
$1 变量值不加双引号的语句执行报错,提示 "binary operator expected"。
test 命令在 -n-z 操作符后面预期只有一个参数。
而这里的 test -n $1 扩展为 test -n test string
-n 后面提供了两个参数,加上 -n 总共是三个参数,导致执行报错。
使用双引号把 $1 变量值括起来,整个变量值就会被当成一个参数,执行 test -n "$1" 命令不会报错。

执行 ./empty_string.sh 命令,没有提供第一个参数,测试结果比较奇怪。
-n $1 认为 $1 不为空。
-z $1 又认为 $1 为空。
只有 -z "$1" 正确地判断出第一个参数值为空。

原因在于,没有提供第一个参数时,这里的 $1 的值是空,相当于什么都没有。
test -n $1 语句经过 bash 处理后,得到的是 test -n
[ -z $1 ] 语句经过 bash 处理后,得到的是 [ -z ],相当于 test -z
test -ntest -z 的返回结果都是 true。
所以才打印出来 $1 即为空,又不为空,判断结果不符合预期。

可以再次看到,-z "$1" 用双引号把变量值括起来,得到了预期的判断结果。
添加双引号可以避免很多异常的现象。

使用 bash -x ./empty_string.sh 打印执行脚本时的调试信息,可以看到 [ -z $1 ][ -z "$1" ] 扩展结果的区别:

$ bash -x ./empty_string.sh
+ empty_string ''
+ test -n
+ echo '(1) -n $1  :' 'No quote: not empty.'
(1) -n $1  : No quote: not empty.
+ '[' -z ']'
+ echo '(2) -z $1  :' 'No quote: empty.'
(2) -z $1  : No quote: empty.
+ test -n ''
+ '[' -z '' ']'
+ echo '(4) -z "$1":' 'Quote   : empty.'
(4) -z "$1": Quote   : empty.

结合上面的代码,可以看到 [ -z $1 ] 扩展得到的调试信息是 '[' -z ']',在 -z 后面没有任何参数。
[ -z "$1" ] 扩展得到的结果是 '[' -z '' ']'
-z 后有一个参数 '',这个参数的值是空字符串。

用 [[ 命令判断字符串是否为空

查看 help [[[[ 命令说明如下:

[[ ... ]]: [[ expression ]]
    Execute conditional command.

Returns a status of 0 or 1 depending on the evaluation of the conditional
expression EXPRESSION. Expressions are composed of the same primaries used
by the 'test' builtin, and may be combined using the following operators:
    ( EXPRESSION )    Returns the value of EXPRESSION
    ! EXPRESSION      True if EXPRESSION is false; else false
    EXPR1 && EXPR2    True if both EXPR1 and EXPR2 are true; else false
    EXPR1 || EXPR2    True if either EXPR1 or EXPR2 is true; else false

即,[[ 命令可以使用 test 命令所支持的条件表达式来进行判断。
它们之间的一些区别具体说明如下。

上面提到,[ 命令要求最后一个参数必须是 ]] 本身不是一个命令。
类似的,[[ 命令也要求跟 ]] 同时出现。但是 ]] 本身也是一个命令,而不是一个参数。
所以 [[ expression ]] 被称为复合命令 (compound command)。

如下面例子所示:

$ ]
]: command not found
$ ]]
-bash: syntax error near unexpected token `]]'

可以看到,试图执行 ] 命令,提示命令没有找到,说明没有这个命令。

而执行 ]] 命令,没有提示找不到命令,只是提示语法错误,预期在该命令之前要有 [[ 命令。
由于 [[]] 都是命令,需要用空格把它们和其他参数隔开。

查看 man bash 里面对 [[ 有如下说明:

Word splitting and pathname expansion are not performed on the words between the [[ and ]]; tilde expansion, parameter and variable expansion, arithmetic expansion, command substitution, process substitution, and quote removal are performed.

即,在 [[]] 里面引用变量值时,不会对变量值进行单词拆分 (Word splitting)。
即使变量值带有空格,不用双引号括起来也不会被拆分成多个参数。

test 命令 和 [ 命令会进行单词拆分,可能会导致参数个数发生变化。
可以参考前面几个例子的说明。

使用 [[ 判断字符串是否为空的一些例子如下所示:

$ value=
$ [[ -n $value ]]; echo $?
1
$ [[ -z $value ]]; echo $?
0
$ value="go on"
$ [[ -n $value ]]; echo $?
0
$ [[ -n go on ]]; echo $?
-bash: syntax error in conditional expression
-bash: syntax error near 'on'

可以看到,将 value 变量值设成空,[[ -n $value ]] 返回为 1,确认该变量值不为空是 false。

[[ -z $value ]] 返回为 0,确认该变量值为空是 true。
即使 $value 不加双引号,也能正确判断。
如果是用 [ 命令就会判断异常。

value 变量值包含空格时,[[ -n $value ]] 可以正确判断,但是如果直接写为 [[ -n go on ]] 会执行报错。

阅读 6.3k

南木阁
考据党一枚,力求讲述的每个知识点都有出处,有理有据。如能恰好地解答您的疑问,欢迎点赞,谢谢!

解读权威文档,编写易懂文章。如有恰好解答您的疑问,多谢赞赏支持~

358 声望
319 粉丝
0 条评论

解读权威文档,编写易懂文章。如有恰好解答您的疑问,多谢赞赏支持~

358 声望
319 粉丝
文章目录
宣传栏