用好 if 中的 test 命令
什么是 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
从这个例子中,我们要注意的细节如下
- 括号内侧要有空格,前面讲过原理。
[ -z "$str1" ]
中,获取变量 str1 的值是,一定要加引号。因为如果 str1 为空,那么这个test命令是变成了[ -z ]
,这就变成了判断字符串(这里指-z)不为空的情况,很显然违背了原始的逻辑。[ $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
表示less
,g
表示greater
,e
表示equal
,n
表示not
,t
表示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
可以注意到(())
有如下几点好处
- 获取变量时,不需要美元符号
- 括号内侧不需要空白字符。但是通常为了养成一个好习惯,在双括号内侧还是加上空白字符。
- 可以使用
&&
,|
,!
这样的逻辑操作符,而不需要再使用-a
,-o
,!
这样的复杂的逻辑操作符。
用这个脚本,对比下前面的 test 命令的整型表达式部分所使用的脚本,你会发现这简直是让人爽多了。
[[]]命令
凡是可以在 test 命令中使用的表达式,都可以在 bash 的 [[ expression ]]
中使用,但是 bash 的 [[]]
比[]
强在下面两点
==
和!=
支持通配符匹配。=~
支持 POSIX 正则表达式匹配。
#!/bin/bashFILE=~/.bashrc# == 或 != 使用模式进行匹配
if [[ "$FILE" == *rc ]]; thenecho "$FILE is rc file".
fiINT=-10# #~ 是使用正则表达式进行匹配的
if [[ "$INT" =~ ^-?[0-9]+$ ]]; thenecho "$INT is a number"
fi
通过这个例子,我们需要注意以下几点
- 由于
[[]]
是一个命令,因此与test的[]
形式一样,括号内侧需要括号。 - 由于
[[]]
命令不支持 shell 文件展开,因此模式*rc
中的星号,不会被展开。如果被加上引号或者被转义,那么将按原字符进行匹配。因此,上面的例子不能写成[["$FILE" == "*rc"]]
。同理,正则表达式^-?[0-9]+$
也下能加引号。 [[]]
中的逻辑操作符也高级语言一样,使用&&
,||
,!
。
关于兼容性的一些想法
在日常工作中,我都是使用 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 命令相关推荐
- python执行linux命令返回结果_Python中调用Linux命令并获取返回值
方法一.使用os模块的system方法:os.system(cmd),其返回值是shell指令运行后返回的状态码,int类型,0表示shell指令成功执行,256/512表示未找到,该方法适用于she ...
- python中执行linux命令(调用linux命令)_Python调用Linux bash命令
import subprocess as sup # 以下注释很多(为了自己以后不忘), 如果只是想在python中执行Linux命令, 看前5行就够了 # 3.5版本之后官方推荐使用sup.run ...
- linux history存放位置,Linux中history历史命令使用方法详解
当你在玩Linux的时候,如果你经常使用命令行来控制你的Linux系统,那么有效地使用命令历史机制将会使效率获得极大提升.事实上,一旦你掌握了我在下面给出的15个有关Linux history历史命令 ...
- AWS上创建的notebook实例提示没有导入pytorch模块的解决办法 直接在编辑块中执行如下命令,最后重启kernel问题解决
AWS上创建的notebook实例提示没有导入pytorch模块的解决办法 直接在编辑块中执行如下命令,最后重启kernel问题解决 conda install pytorch torchvision ...
- android音视频工程师,音视频学习 (十三) Android 中通过 FFmpeg 命令对音视频编辑处理(已开源)...
## 音视频学习 (十三) Android 中通过 FFmpeg 命令对音视频编辑处理(已开源) ## 视音频编辑器 ## 前言 有时候我们想对音视频进行加工处理,比如视频编辑.添加字幕.裁剪等功能处 ...
- 在ubuntu系统中使用dpkg命令安装后缀名为deb的软件包
在ubuntu系统中使用dpkg命令安装后缀名为deb的软件包: dpkg命令常用格式如下: #查看文件结构(其中-c等价于--contents) sudo dpkg -c xx.deb #安装软件包 ...
- Linux中升级更新命令yum upgrade和yum update的区别
这篇文章主要介绍了Linux中升级更新命令yum upgrade和yum update的区别,Linux升级命令有两个分别是yum upgrade和yum update, 这个两个命令是有区别的,本文 ...
- linux shell 中的sleep命令
开始还以为是这样的语法: sleep(1), 后面发现是: linux shell 中的sleep命令 分类: LINUX 在有的shell(比如linux中的bash)中sleep还支持睡眠(分,小 ...
- Linux中的In命令
2019独角兽企业重金招聘Python工程师标准>>> ln是linux中一个非常重要命令.它的功能是为某一个文件在另外一个位置建立一个同步的链接,这个命令最常用的参数是-s,具体用 ...
- 解决idea中执行maven命令失败的问题
解决idea中执行maven命令失败的问题 参考文章: (1)解决idea中执行maven命令失败的问题 (2)https://www.cnblogs.com/qyf404/p/4839479.htm ...
最新文章
- linq2db.mysql_Linq to SQlite简单示例(linq2db版)
- PHP 年龄计算函数
- 娃哈哈困境:做好了生意,没做好品牌
- 实录分享 | 计算未来轻沙龙:“法律+AI”前沿研讨会(PPT下载)
- biztalk在用户代码中构造多部分消息
- springboot启动过程_spring5/springboot2源码学习 -- spring boot 应用的启动过程
- WAMP下解决localhost可以访问但IP地址无法访问的问题
- 17.立体匹配——匹配两个图 Matlab实战,立体效果_3
- 【论文笔记】多智能体强化学习值分解基础论文5篇
- 免费课程:Java高级教程-项目部分视频——私塾在线提供
- 身份证号码识别(golang)
- 在vue中在线查看pdf
- 我在华为工作十年的感悟
- 网易2019:矩形重叠
- TCP滑动窗口原理终于清楚了!
- android登陆界面ui设计,UI设计师必备技能:常用字体规范
- mysql 解决全角半角 问题
- Error: EIO: i/o error, read如何解决
- 点云数据笔记:点云与生成鸟瞰图
- pytorch随机种子无法复现