第七章 Tests
每个完整的、合理的编程语言都具有条件判断的功能。
Bash具有test命令,不同的[]和()操作,和if/then结构
7.1 Test结构
一个if/then结构可以测试命令的返回值是否为0,是的话执行更多命令。
有一个专用的内建命令"["。这个命令与test命令等价,它是一个内建命令。这个命令把它的参数作为比较表达式或者是文件测试,并且根据比较的结果返回一个退出码
在版本2.02的Bash中,推出了一个新的[[…]]扩展test命令。注意:"[["是一个关键字,并不是一个退出码。
Bash把[[ $a -lt $b ]]看做一个单独的元素,并且返回一个退出码。
((…))和let…结果也能够返回一个退出码,当它们所测试的算术表达式的结果为非0的时候,他们的退出码将返回0.这些算术扩展(见第十五章)结构被用来做算术比较。
let "1<2" returns 0
((0 && 1)) returns 1
if命令可以测试任何命令,不仅是括号中的条件
if cmp a.txt b.txt &> /dev/null
then echo "Files a and are identical."
else echo "File a and b differ."
fi
cmp 命令用于比较两个文件是否有差异
当相互比较的两个文件完全一样时,不会有任何输出信息;若发现有所差异,会标识出第一个不同之处的字符和序列编号
语法:
cmp [-clsv][-i <字符数目>][--help][第一个文件][第二个文件]
参数:
-c或–print-chars 除了标明差异处的十进制字码之外,一并显示该字符所对应字符。
-i<字符数目>或–ignore-initial=<字符数目> 指定一个数目。
-l或–verbose 标示出所有不一样的地方。
-s或–quiet或–silent 不显示错误信息。
-v或–version 显示版本信息。
–help 在线帮助。
例如:
a.txt
123
456
b.txt
789
aaa
[root@localhost ~]# cmp a.txt b.txt
a.txt b.txt differ: byte 1, line 1
[root@localhost ~]# cmp -c a.txt b.txt
a.txt b.txt differ: byte 1, line 1 is 61 1 67 7
[root@localhost ~]# cmp -l a.txt b.txt
1 61 67
2 62 70
3 63 71
5 64 141
6 65 141
7 66 141
[root@localhost ~]# cmp -cl a.txt b.txt
1 61 1 67 7
2 62 2 70 8
3 63 3 71 9
5 64 4 141 a
6 65 5 141 a
7 66 6 141 a
[root@localhost ~]# cmp -s a.txt b.txt #不输出
非常有用的"if-grep"结构
if grep -q 123 a.txt
then echo "File contains at least one occurrence of 123."
else echo "File does not contain 123."
fi
grep -q
-q 参数意思为:安静模式,不打印任何标准输出。如果有匹配的内容则立即返回状态值0。
用在if/then结构中最合适不过了
word=Linux
letter_sequence=inu
if echo "$word" | grep -q "$letter_sequence"
then
echo "$letter_squence found in $word"
else
echo "$letter_squence not dound in $word"
fi
Example 7.1 什么情况下为真?
#!/bin/bash
#技巧:如果你不确定一个特定的条件如何判断.
#在一个if-test结构中测试它。
if [ 0 ];then
echo "0 is true."
else
echo "0 is false."
fi
#返回值为true
if [ ];then
echo "NULL is true."
else
echo "NULL is false."
fi
#返回值为false
#测试一个变量是否定义:这里是未定义
if [ "$aaa" ]
then
echo "Unintialized variable is true."
else
echo "Unintialized variable is false."
fi
#返回值为false
#更学究的测试
if [ -n "$aaa" ]
then
echo "Unintialized variable is true."
else
echo "Unintialized variable is false."
fi
#什么时候"false"为true?
if [ false ];then echo "false is true";else echo "false is false";fi
#false is true
if [ $false ];then echo "false is true";else echo "false is false";fi
#false is false
exit 0
else if 和 elif
elif是else if的缩减形式
if [ condition1 ];then
command1
elif [ condition2];then
command2
else
default-comman
fi
使用if test condition 这种形式和if [ condition ]这种形式是等价的。向我们前边所说的"[“是test的标记。并且以”]"结束。
注意:test命令是Bash的内建命令,用来测试文件类型和比较字符串。因此,在Bash脚本中,test并不调用/usr/bin/test的二进制版本。同样的"["并不调用/usr/bin/[,被连接到/usr/bin/test。
[root@localhost aaa]# type test
test is a shell builtin #是shell内置变量
[root@localhost aaa]# type '['
[ is a shell builtin #是shell内置变量
[root@localhost aaa]# type '[['
[[ is a shell keyword #是shell的关键字
[root@localhost aaa]# type ']]'
]] is a shell keyword #是shell的关键字
[root@localhost aaa]# type ']'
-bash: type: ]: not found #找不到
Example 7.2 几个等效命令test(/usr/bin/test),[],/usr/bin/[
#!/bin/bash
#
if test -z "$1"
then
echo "无命令行参数."
else
echo "第一个命令行参数为 $1."
fi
if /usr/bin/test -z "$1" #与内建的test结果相同
then
echo "无命令行参数."
else
echo "第一个命令行参数为 $1."
fi
if [ -z "$1" ]
then
echo "无命令行参数."
else
echo "第一个命令行参数为 $1."
fi
if /usr/bin/[ "$1" ]
then
echo "无命令行参数."
else
echo "第一个命令行参数为 $1."
fi
exit 0
[[]]结构比Bash的[]更加灵活,这是一个扩展的test命令,从ksh88继承过来的。
注意:在[[]]结构中,将没有文件扩展或者是单词分离,但是会发生参数扩展和命令替换
file=/etc/passwd
if [[ -e $file ]];then
echo "Password file exists."
fi
注意:使用[[]]能够阻止脚本中的许多逻辑错误。比如,尽管在[]中将给出一个错误,但是&&,||,<>操作还是能够工作在一个[[]]之中。
注意:在if后面,test命令和[]和[[]]都不是必须的。如下:
dir=/home/bozo
if cd "$dir" 2> /dev/null;then #if 命令将返回if后面命令的退出码。
echo "Now in $dir"
else
echo "Can't change to $dir"
fi
与此相似,当在一个在使用或列表结构的时候,test或中括号的使用,也并不一定非有if不可。
var1=20
var2=22
[ "$var1" -ne "$var2" ] && echo "$var1 is not equal to $var2"
home=/home/bozo
[ -d "$home" ] || echo "$home directory does not exist."
(())结构扩展并计算一个算术表达式的结果。如果表达式的结果为0,它将返回1作为退出码,或是false。而一个非0表达式的结果将返回0作为退出码,或是true。
Example 7.3 算术测试使用(())
#!/bin/bash
#算术测试
#((...))结构计算并测试算数表达式的结果退出码将与[[...]]结构相反
((0))
echo "Exit status of \"((0))\" is $?." #1
((1))
echo "Exit status of \"((1))\" is $?." #0
((5>4))
echo "Exit status of \"((5>4))\" is $?." #0
((5>9))
echo "Exit status of \"((5>9))\" is $?." #1
((5-5))
echo "Exit status of \"((5-5))\" is $?." #1
((5/4))
echo "Exit status of \"((5/4))\" is $?." #0
7.2 文件测试操作
> -e 文件存在
> -a 与-e效果相同,但是已经被弃用
> -f 是一个文件
> -s 文件长度不为0
> -d 是一个目录
> -b 是一个块设备(软盘,光盘等)
> -c 是一个字符设备(键盘,声卡等)
> -p 是一个管道
> -h 是一个符号链接
> -L 是一个符号链接
> -S 是一个socket
> -t 关联到一个终端设备的文件描述符
> 这个选项一般都用来检测是否在一个给定脚本中的stdin[-t0]或[-t1]是一个终端
> -r 文件具有读权限
> -w 文件具有写权限
> -x 文件具有执行权限
> -g set-group-id(sgid)标志到文件或目录上
> 如果一个目录具有sgid标志,那么一个被创建在这个目录里的文件自动继承这个目录的所属组属性
> -u set-user-id(suid)标志到文件上
> -k 设置粘滞位
> 对于"sticky bit",save-text-mode标志是一个文件的特殊类型。
> 如果设置了这个标志,那么这个文件将被保存在交换区,为了达到快速存取的目的。
> 如果设置在目录中,它将限制写权限。对于设置了sticky bit位的文件或目录,权限标志中有"t" 大白话就是:一个777目录设置了t权限,任何的用户都能够在这个目录下创建文档,但只能删除自己创建的文档(root除外)
> -O 你是文件的所有者
> -G 文件的gid和你的相同
> -N 从文件最后阅读到现在,是否被修改 f1 -nt f2 文件f1比f2新 f1 -ot f2 文件f1比f2老 f1 -ef f2 f1和f2都硬连接到同一文件
Example 7.4 test死的链接文件(就是原文件丢失的软连接)
#!/bin/bash
#
#如果没有对这个脚本传递参数,那么就使用当前目录
[ $# -eq 0 ] && directory=`pwd` ||directory=$@
#建立函数linkchk来检查传进来的目录或者文件是不是链接和是否存在,并且打印出它们的引用
#如果传进来的目录有子目录,那么把子目录也发送到linkchk函数中处理(就是递归)
linkck(){
for element in $1/*
do
[ -h "$element" -a ! -e "$element" ] && echo \"$element\"
#-h是测试链接,-a是并且的意思,! -e 是不存在时
#大白话就是:$element链接存在,但是原文件不存在
[ -d "$element" ] && linkchk $element
done
}
#如果是个可用目录,那就把每个参数都送到linkchk函数;如果不是,打印错误信息和使用信息
for direcotry in $directorys
do
if [ -d $directory ];then
linkchk $directory
else
echo "$directory is not a directory"
echo "Usage:$0 dir1 dir2 ..."
fi
done
exit 0
#######################示例(便于理解死链接):
#创建一个软链接:ln -s b.txt b.link
#[root@localhost ~]# cat b.txt
#123456789
#[root@localhost ~]# cat b.link
#123456789
#删除原文件:rm b.txt
#[root@localhost ~]# ls b.link
#b.link
#[root@localhost ~]# echo $? #说明软连接还存在
#0
#[root@localhost ~]# cat b.link
#cat: b.link: No such file or directory
Example 28.1,Example 10.7,Example 10.3,Exapmle 28.3,Example A-1也会说明文件测试操作的使用流程。
7.3 其它比较操作
整数比较
-eq 等于;如:[ "$a" -eq "$b" ]
-ne 不等于;如:[ "$a" -ne "$b" ]
-gt 大于;如:[ "$a" -gt "$b" ]
-ge 大于等于;如:[ "$a" -ge "$b" ]
-lt 小于;如:[ "$a" -lt "$b" ]
-le 小于等于;如:[ "$a" -le "$b" ]
< 小于(需要双括号),如:(("$a" < "$b"))
<= 小于等于(需要双括号),如:(("$a" <= "$b"))
> 大于(需要双括号),如:(("$a" > "$b"))
>= 大于等于(需要双括号),如:(("$a" >= "$b"))
字符串比较
= 等于;如:[ "$a" = "$b" ]
== 等于;如:[ "$a" == "$b" ],与=等价
注意:==的功能在[[]]和[]中的行为是不同的,如下:
[[ $a == z* ]] #如果$a以z开头,那么将为true
[[ $a == "z*" ]] #如果$a等于z*,那么将为true
[ $a == z* ] #File globbing(文件全局处理)和word splitting(单词分割)将会发生
[ "$a" == "z*" ] #如果$a等于z*,那么将为true
!= 不等于,如:[ "$a" != "$b" ]
-z 字符串为null。就是长度为0
-n 字符串不为null
注意:使用-n在[]结构中测试,必须把变量用"“引起来
使用未被”“的字符串来使用! -z,放在[]结构中,虽然一般情况下可以工作,但是不安全。
习惯使用”"来测试字符串是一种好习惯
在ASCII字母顺序下
< 小于
如:[[ "$a" < "$b" ]]
[ "$a" \< "$b" ]
注意:在[]结构中<需要被转义
> 大于
如:[[ "$a" > "$b" ]]
[ "$a" \> "$b" ]
注意:在[]结构中<需要被转义
Example 7.5 数字和字符串比较
#!/bin/bash
#
a=4
b=5
#这里变量a和b既可以当做整型也可以当做字符串。
#这里在算术比较和字符串比较之间有些混淆,因为Bash变量并不是强类型的。
#Bash允许对整型变量操作和比较,当然变量中只包含数字。
echo
if [ "$a" -ne "$b" ];then
echo "$a is not equal to $b"
echo "(算术比较)"
fi
echo
if [ "$a" != "$b" ];then
echo "$a is not equal to $b"
echo "(字符串比较)"
fi
echo
exit 0
Example 7.6 测试字符串是否为null
#!/bin/bash
#str-test.sh
#测试nul字符串和非引用字符串
if [ -n $string ];then #$string1 没有被声明和初始化
echo "\$string is not null"
else
echo "\$string is null"
fi
#结果为:"$string is not null";这是错误的结果
if [ -n "$string" ];then #这次$string1被引用了
echo "\$string is not null"
else
echo "\$string is null"
fi
#结果为:"$string is null"
#判断字符串一般都会使用这种:""
if [ $string ];then #这次$string1变成了"裸体"的
echo "\$string is not null"
else
echo "\$string is null"
fi
#结果为:"$string is null"
string1=initialized
if [ $string ];then #再来$string1"裸体"的
echo "\$string is not null"
else
echo "\$string is null"
fi
#结果为:"$string is not null"
string1="a = b"
if [ $string ];then #再来$string1"裸体"的
echo "\$string is not null"
else
echo "\$string is null"
fi
#非引用的"$string1"现在给出了一个错误的结果
exit 0
Example 7.7 zmore
#!/bin/bash
#使用more来查看gzip文件
NOARGS=65
NOTFOUND=66
NOTGZIP=67
if [ $# -eq 0 ] #与if [ -z "$1" ]效果相同
then
echo "Usage: `basename $0` filename" >&2
exit $NOARGS
fi
filename=$1
if [ ! -f "$filename" ] #将$filename""起来,来允许可能的空白
then
echo "File $filename not found" >&2
exit $NOFOUND
fi
if [ ${filename##*.} != "gz" ] #在变量替换中使用中括号
then
echo "File $1 is not a gzipped file"
exit $NOGZIP
fi
zcat $1 | more
exit $?
变量替换(便于理解)
var1="I love you,Do you love me?"
${变量名#匹配规则} 从变量的开头进行规则匹配,将符合的最短数据删掉
var2=${var1#*ove}
echo $var2 #you,Do you love me?
${变量名##匹配规则} 从变量的开头进行规则匹配,将符合的最长数据删掉
var2=${var1##*ove}
echo $var2 #me?
${变量名%匹配规则} 从变量的尾部进行规则匹配,将符合的最短数据删掉
var2=${var1%ove*}
echo $var2 #I love you,Do you l
${变量名%%匹配规则} 从变量的尾部进行规则匹配,将符合的最长数据删掉
var2=${var1%%ove*}
echo $var2 #I l
${变量名/旧字符串/新字符串} 变量中包含旧字符串,则第一个旧字符串被替换为新字符串
var2=${var1/love/LOVE}
echo $var2 #I LOVE you,Do you love me?
${变量名//旧字符串/新字符串} 变量中包含旧字符串,则全部旧字符串被替换为新字符串
var2=${var1//love/LOVE}
echo $var2 #I LOVE you,Do you LOVE me?
混合比较
-a 逻辑与
exp1 -a exp2 #如果exp1和exp2都为true的话,这个表达式将返回true
-o 逻辑或
exp1 -o exp2 #如果exp1和exp2中有一个为true的话,这个表达式将返回true
请参考Example 8.3,Example 26.16和Example A.28来查看混合比较操作
7.4 嵌套的if/then条件test
可以使用if/then来进行嵌套的条件test。最终的结果和上边的使用&&混合比较操作是相同的
if [ condition1 ];then
if [ condition2 ];then
do-something
fi
fi
具体请查看Example 34.4