Keep learning, keep living...

0%

Bash test和[命令分析

近期我们的BASH脚本中遇到一个BUG,最终分析到是变量值为空时没有用""包裹导致的。

我们使用一个测试脚本来进行分析。测试脚本t.sh内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

kof=""

if [ ! -f $kof ]; then
echo "1"
fi

if [ ! -f "$kof" ]; then
echo "2"
fi

echo "END"

测试脚本的执行结果如下:

1
2
3
[root@default ~]# bash t.sh
2
END

可以看到当变量$kof为空时,第一个if语句块中的语句没有被执行,而第二个if语句块中的语句被执行了。这两个语句块的差别就在于第二个语句块中的$kof变量用""进行了包裹。

来看一下执行时的差别:

1
2
3
4
5
6
7
8
[root@default ~]# bash -x t.sh
+ kof=
+ '[' '!' -f ']'
+ '[' '!' -f '' ']'
+ echo 2
2
+ echo END
END

可以看到第一个语句块执行的是[ ! -f ], 而第二个语句块执行的是[ ! -f '' ]

从这里也可以看出,[不是bash中的语法关键字,而是执行的命令, 因而它必须与参数用空格分隔开。比如,[!-f]就是不合法的表达,必须写成[ ! -f ]

通过type命令查看:

1
2
[root@default ~]# type [
[ is a shell builtin

可以看到[BASH的一个内置命令。

我们可以使用man builtin查看bash内置命令的说明。从中可知,test[的作用是一样的,用来评估一个表达式为true还是false:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
test and [ evaluate conditional expressions using a set of rules based on the number of arguments.

0 arguments
The expression is false.
1 argument
The expression is true if and only if the argument is not null.
2 arguments
If the first argument is !, the expression is true if and only if the second argument is null. If the first argument is one of the unary conditional
operators listed above under CONDITIONAL EXPRESSIONS, the expression is true if the unary test is true. If the first argument is not a valid unary con‐
ditional operator, the expression is false.
3 arguments
The following conditions are applied in the order listed. If the second argument is one of the binary conditional operators listed above under CONDI‐
TIONAL EXPRESSIONS, the result of the expression is the result of the binary test using the first and third arguments as operands. The -a and -o opera‐
tors are considered binary operators when there are three arguments. If the first argument is !, the value is the negation of the two-argument test
using the second and third arguments. If the first argument is exactly ( and the third argument is exactly ), the result is the one-argument test of
the second argument. Otherwise, the expression is false.

当表达式是两个参数时, 如果第一个参数是!, 那么只有第二个参数为null时,表达式结果才为真。在[ ! -f ]表达式中,第二个参数为-f, 它不为null, 因而表达式结果为false, 因而不会执行if块中的语句。

这里需要注意的是: test或者[命令判断的表达式为true时,命令的返回值$?0, 而当表达式为false的时候$?1

例如,根据test[命令的描述,[ ]没有参数,表达式结果为false, $?1:

1
2
3
[root@default ~]# [ ]
[root@default ~]# echo $?
1

我们再从源码中确认一下具体实现。因为[bash中内置命令,我们从下载CentOS7bash源码包:

从源码中的test.c中可以知道表达式包含两个参数时会调用two_arguments函数进行处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static int
two_arguments ()
{
if (argv[pos][0] == '!' && argv[pos][1] == '\0')
return (argv[pos + 1][0] == '\0');
else if (argv[pos][0] == '-' && argv[pos][2] == '\0')
{
if (test_unop (argv[pos]))
return (unary_operator ());
else
test_syntax_error (_("%s: unary operator expected"), argv[pos]);
}
else
test_syntax_error (_("%s: unary operator expected"), argv[pos]);

return (0);
}

可以看到当第一个参数为!, 第二个参数为一个空字符串,也就是第一个字符就为'\0',才会返回1。文档中所说的null也就是空字符串"\0"

最初的测试脚本中的两个表达式在变量$kof为空串的情况下, 传递的参数个数不同。在[ ! -f $kof ]表达式中,传递到[命令只有!-f两个参数,而[ ! -f "$kof" ]的情况下传递的是三个参数: !, -f, ""

在第一种情况下,代码中原意是判断普通文件是否存在的逻辑变成了一个固定的false语句,而[ ! -f "" ]中是三个参数,-f的语义是正常判断的,符合逻辑。BUG的根因就在这里。

因而需要注意,在bash脚本中的字符串变量一定要使用""进行包裹。

此外,大多数操作系统还提供了独立的二进制程序test[

CentoOS7上执行查看程序位置:

1
2
[root@default src]# which [
/bin/[
1
2
[root@default src]# which test
/bin/test

它们位于coreutilsRPM包中:

1
2
[root@default src]# rpm -qf /bin/[
coreutils-8.22-24.el7.x86_64

一般情况下,bash内置命令的优先级更高,但可以使用enable -n命令关闭内置命令。因而有所需要时,可根据场景可对独立的test[命令源码进行分析。

源码RPM位于:

参考: