什么是 test 命令

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

#!/bin/bashif [ -f ~/.bashrc ]; thenecho ...
fi

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

#!/bin/bashif test -f ~/.bashrc; thenecho ...
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/bashstr1=Z
str2=a# 获取变量值要加引号,为了防止变量为空
if [ -z "$str1" ]; thenecho "str1 is empty."exit 1;
fiif [ -z "$str2" ]; thenecho "str2 is empty."exit 1;
fi# 大于号一定要转义,否则会被当作重定向符
if [ "$str1" \> "$str2" ]; thenecho "$str1 > $str2"
elif [ "$str1" == "$str2" ]; thenecho "$str1 == $str2"
elseecho "$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/bashif [ -e ~/.bashrc ]; thenecho "exist"if [ -s ~/.bashrc ]; thenecho "size > 0"fi
elseecho "no exist"
fi

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

#!/bin/bash
# 这是一个错误脚本的写法,双引号阻止了波浪线的shell扩展
if [ -e "~/.bashrc" ] ; thenecho "exist"
elseecho "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 file1 file1和file2拥有相同的i节点号(硬链接创建的文件具有相同的i节点)
file1 -nt file2 file1的修改日期要新于file2的,或者file1存在,file2不存在
file1 -ot file2 file1的修改日期要旧于file2的,或者file1存在,file2不存在

变量判断

表达式 何时为真
-v variable 如果变量已经被赋值
-R variable 如果变量已经被赋值,并且是名称引用(name reference)
#!/bin/bashvar=hello
if [ -v var ]; thenecho "var has been set"
fi

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

整型表达式

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

#!/bin/bashint1=110
int2=911if [ "$int1" -lt "$int2" ]; thenecho "$int1 less then $int2"
fi

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

test命令中的逻辑操作符

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

#!/bin/basha=5
b=11
FILE="test.txt"if [ "$a" -ge 0 -a "$a" -le 10 ]; thenecho "$a is in range [0, 10]"
fiif [ "$b" -lt 0 -o "$b" -gt 10 ]; thenecho "$b is not in range [0, 10]"
fi# 注意,感叹号前后都要有空格,因为它是一个参数
if [ ! -e "$FILE" ]; thenecho "$FILE not exist"
fi

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

#!/bin/bashINT=5if [ ! \( "$INT" -ge 0 -a "$INT" -le 10 \) ]; thenecho "INT not in range [0, 10]"
elseecho "INT in range [0, 10]"
fi

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

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

bash 中的 test 命令

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

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

(())命令

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

#!/bin/bashINT=5if ((!(INT >= 0 && INT <= 10))); thenecho "INT not in range [0, 10]"
elseecho "INT in range [0, 10]"
fi

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

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

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

[[]]命令

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

  1. ==!=支持通配符匹配。
  2. =~支持 POSIX 正则表达式匹配。
#!/bin/bashFILE=~/.bashrc# == 或 != 使用模式进行匹配
if [[ "$FILE" == *rc ]]; thenecho "$FILE is rc file".
fiINT=-10# #~ 是使用正则表达式进行匹配的
if [[ "$INT" =~ ^-?[0-9]+$ ]]; thenecho "$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

用好 if 中的 test 命令相关推荐

  1. python执行linux命令返回结果_Python中调用Linux命令并获取返回值

    方法一.使用os模块的system方法:os.system(cmd),其返回值是shell指令运行后返回的状态码,int类型,0表示shell指令成功执行,256/512表示未找到,该方法适用于she ...

  2. python中执行linux命令(调用linux命令)_Python调用Linux bash命令

    import subprocess as sup  # 以下注释很多(为了自己以后不忘), 如果只是想在python中执行Linux命令, 看前5行就够了 # 3.5版本之后官方推荐使用sup.run ...

  3. linux history存放位置,Linux中history历史命令使用方法详解

    当你在玩Linux的时候,如果你经常使用命令行来控制你的Linux系统,那么有效地使用命令历史机制将会使效率获得极大提升.事实上,一旦你掌握了我在下面给出的15个有关Linux history历史命令 ...

  4. AWS上创建的notebook实例提示没有导入pytorch模块的解决办法 直接在编辑块中执行如下命令,最后重启kernel问题解决

    AWS上创建的notebook实例提示没有导入pytorch模块的解决办法 直接在编辑块中执行如下命令,最后重启kernel问题解决 conda install pytorch torchvision ...

  5. android音视频工程师,音视频学习 (十三) Android 中通过 FFmpeg 命令对音视频编辑处理(已开源)...

    ## 音视频学习 (十三) Android 中通过 FFmpeg 命令对音视频编辑处理(已开源) ## 视音频编辑器 ## 前言 有时候我们想对音视频进行加工处理,比如视频编辑.添加字幕.裁剪等功能处 ...

  6. 在ubuntu系统中使用dpkg命令安装后缀名为deb的软件包

    在ubuntu系统中使用dpkg命令安装后缀名为deb的软件包: dpkg命令常用格式如下: #查看文件结构(其中-c等价于--contents) sudo dpkg -c xx.deb #安装软件包 ...

  7. Linux中升级更新命令yum upgrade和yum update的区别

    这篇文章主要介绍了Linux中升级更新命令yum upgrade和yum update的区别,Linux升级命令有两个分别是yum upgrade和yum update, 这个两个命令是有区别的,本文 ...

  8. linux shell 中的sleep命令

    开始还以为是这样的语法: sleep(1), 后面发现是: linux shell 中的sleep命令 分类: LINUX 在有的shell(比如linux中的bash)中sleep还支持睡眠(分,小 ...

  9. Linux中的In命令

    2019独角兽企业重金招聘Python工程师标准>>> ln是linux中一个非常重要命令.它的功能是为某一个文件在另外一个位置建立一个同步的链接,这个命令最常用的参数是-s,具体用 ...

  10. 解决idea中执行maven命令失败的问题

    解决idea中执行maven命令失败的问题 参考文章: (1)解决idea中执行maven命令失败的问题 (2)https://www.cnblogs.com/qyf404/p/4839479.htm ...

最新文章

  1. linq2db.mysql_Linq to SQlite简单示例(linq2db版)
  2. PHP 年龄计算函数
  3. 娃哈哈困境:做好了生意,没做好品牌
  4. 实录分享 | 计算未来轻沙龙:“法律+AI”前沿研讨会(PPT下载)
  5. biztalk在用户代码中构造多部分消息
  6. springboot启动过程_spring5/springboot2源码学习 -- spring boot 应用的启动过程
  7. WAMP下解决localhost可以访问但IP地址无法访问的问题
  8. 17.立体匹配——匹配两个图 Matlab实战,立体效果_3
  9. 【论文笔记】多智能体强化学习值分解基础论文5篇
  10. 免费课程:Java高级教程-项目部分视频——私塾在线提供
  11. 身份证号码识别(golang)
  12. 在vue中在线查看pdf
  13. 我在华为工作十年的感悟
  14. 网易2019:矩形重叠
  15. TCP滑动窗口原理终于清楚了!
  16. android登陆界面ui设计,UI设计师必备技能:常用字体规范
  17. mysql 解决全角半角 问题
  18. Error: EIO: i/o error, read如何解决
  19. 点云数据笔记:点云与生成鸟瞰图
  20. pytorch随机种子无法复现

热门文章

  1. LaTeX关于section和paragraph的用法
  2. 数据自动备份解决方案
  3. 【魔方网表】魔方网表涉及到的公式整理
  4. 最好的PPP协议详解
  5. Windows沙盒技术调研
  6. 十大常用算法之佛洛依德算法
  7. 视频(4)---视频编码的硬件平台HiSI3516
  8. 海图(基于http服务器的图片管理工具)
  9. 深入解析:如何修复SSL / TLS握手失败错误(中)
  10. 输入一个大写字母,输出小写字母和ASCII值