用好 if 中的 test 命令

什么是 test 命令

说起 shell 中的 test 命令,你可能有点陌生,但是说到 if 中的条件测试命令,你可能就很熟悉了

#!/bin/bash

if [ -f ~/.bashrc ]; then
	echo ...
fi

其中 [ -f ~/.bashrc ] 就是一个完整的 test 命令,其实这个脚本可以用 test 命令进行改写

#!/bin/bash

if test -f ~/.bashrc; then
	echo ...
fi

通常我们更习惯使用 [] 这种形式的 test 命令,但是要掌握 shell 的 test 命令不是一件容易的事情,至少对于初学者来说。

bash shell 对 shell 的 test 命令进行了功能增加,提供了一个更方便、更简洁、更符合现代的高级语言用法,并且我觉得最重要的一点,相对于 shell 的 test 命令,它就是可以避免很多语法错误。通过本文讲解,你将会爱上 bash shell 的 test 命令。

理解test命令

在深入学习 test 命令之前,我们需要理解 test 命令的括号形式语法,这是解开初学者所有困惑的关键所在。

以前面的[ -f ~/.bashrc ]为例,请记住以下一句话 :

开括号[ 表示 test 命令,括号中间的都是参数,闭括号 ] 也是 test 命令的参数。

当我们执行一个普通的命令时,命令和它的参数之间需要以一个空白字符分隔,因此 test 命令的 [] 形式也是如此,前后括号的内侧都需要一个空白字符。

test 命令的表达式

test 命令的表达式有很多种,例如字符串表达式,整型表达式,文件表达式。下面我们来依次看看这些表达式如何使用,以及有哪些注意事项。

字符串表达式

关于字符串表达式总结如下

表达式何时为真
string字符中不为空
-n string字符串长度大于0
-z string字符串长为0
string1 = string2 或 string1 == string2字符串相等
string != string2字符串不相等
string1 > string2以ASCII码的顺序,如果string1大于string2
string1 < string2以ASCII码的顺序,如果string1小于string2

字符串表达式看似非常简单,其实也许多小细节需要注意,否则可能导致语法错误。我用一个脚本来解释这些细节

#!/bin/bash
  
str1=Z
str2=a

# 获取变量值要加引号,为了防止变量为空
if [ -z "$str1" ]; then
    echo "str1 is empty."
    exit 1;
fi

if [ -z "$str2" ]; then
    echo "str2 is empty."
    exit 1;
fi

# 大于号一定要转义,否则会被当作重定向符
if [ "$str1" \> "$str2" ]; then
    echo "$str1 > $str2"
elif [ "$str1" == "$str2" ]; then
    echo "$str1 == $str2"
else
    echo "$str1 < $str2"
fi

从这个例子中,我们要注意的细节如下

  1. 括号内侧要有空格,前面讲过原理。
  2. [ -z "$str1" ]中,获取变量 str1 的值是,一定要加引号。因为如果 str1 为空,那么这个test命令是变成了[ -z ],这就变成了判断字符串(这里指-z)不为空的情况,很显然违背了原始的逻辑。
  3. [ $str1 \> $str2 ]中,大于号一定要转义或者加括号(小于号也一样)。记住,[] 就是一个 test 命令,而在一个命令中,大于号、小于号,都会被当成重定向操作符,因此这里需要进行转义。

想不到吧,一个小小的 test 字符串表达式,居然有这么多需要注意的事项。但是只要我们记住一点就可以避免这些错误,这一点就是[]是一个 test 命令,其余全部为参数。

判断字符串相等为何有两个操作符,= 是 shell 用的,== 是 bash 用的。

文件表达式

test 命令的文件表达式有很多,我把它们进行了分类讲解,这样方便理解与记忆。

判断文件存在性,以及长度

表达式何时为真
-a file 或 -e file文件存在
-s file文件存在,并且长度大于0

e 是 exist 的首字符,s 是 size 的首字符,这样就方便记忆了。

#!/bin/bash
  
if [ -e ~/.bashrc ]; then
    echo "exist"
    if [ -s ~/.bashrc ]; then
        echo "size > 0"
    fi
else
    echo "no exist"
fi

这段脚本,很显示可以把~/.bashrc提取为一个变量。我这里这样写,是为了说明一个问题,这也是很多新手困惑的问题,看下面的脚本

#!/bin/bash
# 这是一个错误脚本的写法,双引号阻止了波浪线的shell扩展
if [ -e "~/.bashrc" ] ; then
	echo "exist"
else
	echo "no exist"
fi

这个输出结果是no exist!没错,我没有写错,你也没有看错。为什么呢?

所有的错误都是源于对 [] 形式的 test 命令的不理解,无论是 ~/.bashrc 还是 "~/.bashrc" ,它们都是参数。而作为参数,双引号阻止了 shell 的波浪线展开。现在你能明白上面的错误了吗?

判断文件类型

表达式何时为真
-f file文件存在,且是普通文件
-d file文件存在,且是目录
-L file 或 -h file文件存在,且是符号链接
-c file文件存在,且是字符特殊文件
-b file文件存在,且是块特殊文件
-p file文件存在,且是一个命名管道文件
-S file文件存在,且是一个套接字文件

判断文件权限

表达式何时为真
-r file文件存在,且可读
-w file文件存在,且可写
-x file文件存在,且可执行
-u file文件存在,且设置用户ID被设置
-g file文件存在,且设置组ID被设置
-k file文件存在,且粘着位被设置
-G file文件存在,且被有效用户组ID拥有
-O file文件存在,且被有效用户ID拥有

文件之间的比较

表达式何时为真
file1 -ef file1file1和file2拥有相同的i节点号(硬链接创建的文件具有相同的i节点)
file1 -nt file2file1的修改日期要新于file2的,或者file1存在,file2不存在
file1 -ot file2file1的修改日期要旧于file2的,或者file1存在,file2不存在

变量判断

表达式何时为真
-v variable如果变量已经被赋值
-R variable如果变量已经被赋值,并且是名称引用(name reference)
#!/bin/bash

var=hello
if [ -v var ]; then
	echo "var has been set"
fi

注意,这里是测试变量,不是测试变量的值,因此不需要加 var 前加美元符号。

整型表达式

test 命令的整型表达式的形式为int1 op int2,其中op-eq, -ne, -lt, -le, -gt, -ge。其中 l表示lessg表示greatere表示equaln表示nott表示than

#!/bin/bash
  
int1=110
int2=911

if [ "$int1" -lt "$int2" ]; then
    echo "$int1 less then $int2"
fi

整型比较的操作符为何这么复杂呢? 为何不能用大于号、小于号等等的操作符呢? 很可惜,shell 并不支持,而 bash 的复合命令 (()) 支持这种写法,我们将在后面会看到。

test命令中的逻辑操作符

test 命令的表达式之间支持逻辑操作符,与或非分别由-a, -o, ! 表示。

#!/bin/bash
  
a=5
b=11
FILE="test.txt"

if [ "$a" -ge 0 -a "$a" -le 10 ]; then
    echo "$a is in range [0, 10]"
fi

if [ "$b" -lt 0 -o "$b" -gt 10 ]; then
    echo "$b is not in range [0, 10]"
fi

# 注意,感叹号前后都要有空格,因为它是一个参数
if [ ! -e "$FILE" ]; then
    echo "$FILE not exist"
fi

我们还可以使用括号把某些表达式结合到一起,但是使用起来要注意一些事项

#!/bin/bash

INT=5

if [ ! \( "$INT" -ge 0 -a "$INT" -le 10 \) ]; then
    echo "INT not in range [0, 10]"
else
    echo "INT in range [0, 10]"
fi

我们可以注意到,括号使用了转义,并且由于括号也是 test 命令的参数,因此需要用空白字符分隔。

说实话,这段代码写的真的是很累,在后面我们可以看到 bash 的复合命令 (()) 会有更方便的写法。

bash 中的 test 命令

前面我们已经介绍了 shell 中的 test 命令的用法,我们可以发现使用起来还是有难度的,难度之一在于陷阱太多,难度之二在于语法晦涩。

bash 对 shell 的 test 命令进行了增强,避免了很多陷阱,也使语法更简单易用,只是它不兼容 POSIX 而已。

(())命令

bash 中的复合命令 (()) 是针对算术表达式的,凡事能在高级语言( 例如 C,Java )中使用的算术表达式,都能在这个命令中使用,脚本代码如下

#!/bin/bash

INT=5

if ((!(INT >= 0 && INT <= 10))); then
    echo "INT not in range [0, 10]"
else
    echo "INT in range [0, 10]"
fi

可以注意到(())有如下几点好处

  1. 获取变量时,不需要美元符号
  2. 括号内侧不需要空白字符。但是通常为了养成一个好习惯,在双括号内侧还是加上空白字符。
  3. 可以使用 &&, |, ! 这样的逻辑操作符,而不需要再使用 -a, -o, ! 这样的复杂的逻辑操作符。

用这个脚本,对比下前面的 test 命令的整型表达式部分所使用的脚本,你会发现这简直是让人爽多了。

[[]]命令

凡是可以在 test 命令中使用的表达式,都可以在 bash 的 [[ expression ]] 中使用,但是 bash 的 [[]][] 强在下面两点

  1. ==!=支持通配符匹配。
  2. =~支持 POSIX 正则表达式匹配。
#!/bin/bash

FILE=~/.bashrc

# == 或 != 使用模式进行匹配
if [[ "$FILE" == *rc ]]; then
    echo "$FILE is rc file".
fi

INT=-10

# #~ 是使用正则表达式进行匹配的
if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
    echo "$INT is a number"
fi

通过这个例子,我们需要注意以下几点

  1. 由于[[]]是一个命令,因此与test的[]形式一样,括号内侧需要括号。
  2. 由于[[]]命令不支持 shell 文件展开,因此模式 *rc 中的星号,不会被展开。如果被加上引号或者被转义,那么将按原字符进行匹配。因此,上面的例子不能写成 [["$FILE" == "*rc"]]。同理,正则表达式 ^-?[0-9]+$ 也下能加引号。
  3. [[]]中的逻辑操作符也高级语言一样,使用&&||!

关于兼容性的一些想法

在日常工作中,我都是使用 bash 的 test 命令,因为简洁而且不容易出错。但是它破坏了 POSIX 兼容。

其实事物都有两面性,例如,Linux 平台中默认的 shell 为 bash,而 Mac 默认为 zsh,Android 源码为了能在两个平台下编译,必须写出 POSIX 的代码,而不能使用 bash 独有的功能代码。

但是如果我们平时中不必考虑这样的 POSIX 问题,我们可以随意使用 bash shell 独有的高级功能,加快开发效率,殊不知,浪费时间就是谋财害命。

参考

[[]]复合命令
https://www.gnu.org/software/bash/manual/bash.html#Conditional-Constructs

Linux命令行中对test命令的的说明

https://www.kancloud.cn/thinkphp/linux-command-line/39459

GNU Bash Reference Manual

https://www.gnu.org/software/bash/manual/bash.html#Invoking-Bash

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值