第三章 Shell的判断结构

前言

在 Bash 里有这样的常量(实际上是两个内置命令,在这里我们姑且这么认为,后面将介绍),即 true 和 false,一个表示真,一个表示假。对它们可以进行与、或、非运算等常规的逻辑运算,在这一节,我们除了讨论这些基本逻辑运算外,还将讨论Shell编程中的条件测试命令列表,并介绍它们和布尔运算的关系。

常规的布尔运算

这里主要介绍 Bash 里头常规的逻辑运算,与、或、非。

在 Shell 下如何进行逻辑运算

范例:true or false

单独测试 truefalse,可以看出 true 是真值,false 为假

$ if true;then echo "YES"; else echo "NO"; fi
YES
$ if false;then echo "YES"; else echo "NO"; fi
NO

范例:与运算

$ if true && true;then echo "YES"; else echo "NO"; fi
YES
$ if true && false;then echo "YES"; else echo "NO"; fi
NO
$ if false && false;then echo "YES"; else echo "NO"; fi
NO
$ if false && true;then echo "YES"; else echo "NO"; fi
NO

范例:或运算

$ if true || true;then echo "YES"; else echo "NO"; fi
YES
$ if true || false;then echo "YES"; else echo "NO"; fi
YES
$ if false || true;then echo "YES"; else echo "NO"; fi
YES
$ if false || false;then echo "YES"; else echo "NO"; fi
NO

范例:非运算,即取反

$ if ! false;then echo "YES"; else echo "NO"; fi
YES
$ if ! true;then echo "YES"; else echo "NO"; fi
NO

可以看出 truefalse 按照我们对逻辑运算的理解进行着,但是为了能够更好的理解 Shell 对逻辑运算的实现,我们还得弄清楚,truefalse 是怎么工作的?

Bash 里头的 true 和 false 是我们通常认为的 1 和 0 么?

回答是:否。

范例:返回值 v.s. 逻辑值

truefalse 它们本身并非逻辑值,它们都是 Shell 的内置命令,只是它们的返回值是一个“逻辑值”:

$ true
$ echo $?
0
$ false
$ echo $?
1

可以看到 true 返回了 0,而 false 则返回了 1 。跟我们离散数学里学的真值 1 和 0 并不是对应的,而且相反的。

范例:查看 true 和 false 帮助和类型

$ help true false
true: true
     Return a successful result.
false: false
     Return an unsuccessful result.
$ type true false
true is a shell builtin
false is a shell builtin

说明:$? 是一个特殊变量,存放有上一次进程的结束状态(退出状态码)。

从上面的操作不难联想到在 C 语言程序设计中为什么会强调在 main 函数前面加上 int,并在末尾加上 return 0 。因为在 Shell 里,将把 0 作为程序是否成功结束的标志,这就是 Shell 里头 truefalse 的实质,它们用以反应某个程序是否正确结束,而并非传统的真假值(1 和 0),相反地,它们返回的是 0 和 1 。不过庆幸地是,我们在做逻辑运算时,无须关心这些。

条件测试

从上节中,我们已经清楚地了解了 Shell 下的“逻辑值”是什么:是进程退出时的返回值,如果成功返回,则为真,如果不成功返回,则为假。

而条件测试正好使用了 test 这么一个指令,它用来进行数值测试(各种数值属性测试)、字符串测试(各种字符串属性测试)、文件测试(各种文件属性测试),我们通过判断对应的测试是否成功,从而完成各种常规工作,再加上各种测试的逻辑组合后,将可以完成更复杂的工作。

条件测试基本使用

范例:数值测试

$ if test 5 -eq 5;then echo "YES"; else echo "NO"; fi
YES
$ if test 5 -ne 5;then echo "YES"; else echo "NO"; fi
NO

范例:字符串测试

$ if test -n "not empty";then echo "YES"; else echo "NO"; fi
YES
$ if test -z "not empty";then echo "YES"; else echo "NO"; fi
NO
$ if test -z "";then echo "YES"; else echo "NO"; fi
YES
$ if test -n "";then echo "YES"; else echo "NO"; fi
NO

范例:文件测试

$ if test -f /boot/System.map; then echo "YES"; else echo "NO"; fi
YES
$ if test -d /boot/System.map; then echo "YES"; else echo "NO"; fi
NO

各种逻辑测试的组合

范例:如果 a,b,c 都等于下面对应的值,那么打印 YES,通过 -a 进行"与"测试

$ a=5;b=4;c=6;
$ if test $a -eq 5 -a $b -eq 4 -a $c -eq 6; then echo "YES"; else echo "NO"; fi
YES

范例:测试某个“东西”是文件或者目录,通过 -o 进行“或”运算

$ if test -f /etc/profile -o -d /etc/profile;then echo "YES"; else echo "NO"; fi
YES

范例:测试某个“东西”是否为文件,测试 ! 非运算

$ if test ! -f /etc/profile; then echo "YES"; else echo "NO"; fi
NO

上面仅仅演示了 test 命令一些非常简单的测试,你可以通过 help test 获取 test 的更多用法。需要注意的是,test 命令内部的逻辑运算和 Shell 的逻辑运算符有一些区别,对应的为 -a&&-o||,这两者不能混淆使用。而非运算都是 !,下面对它们进行比较。

比较 -a 与 &&, -o 与 ||, ! test 与 test !

范例:要求某文件可执行且有内容,用 -a 和 && 分别实现

$ cat > test.sh
#!/bin/bash
echo "test"
[CTRL+D]  # 按下组合键CTRL与D结束cat输入,后同,不再注明
$ chmod +x test.sh
$ if test -s test.sh -a -x test.sh; then echo "YES"; else echo "NO"; fi
YES
$ if test -s test.sh && test -x test.sh; then echo "YES"; else echo "NO"; fi
YES

范例:要求某个字符串要么为空,要么和某个字符串相等

$ str1="test"
$ str2="test"
$ if test -z "$str2" -o "$str2" == "$str1"; then echo "YES"; else echo "NO"; fi
YES
$ if test -z "$str2" || test "$str2" == "$str1"; then echo "YES"; else echo "NO"; fi
YES

范例:测试某个数字不满足指定的所有条件

$ i=5
$ if test ! $i -lt 5 -a $i -ne 6; then echo "YES"; else echo "NO"; fi
YES
$ if ! test $i -lt 5 -a $i -eq 6; then echo "YES"; else echo "NO"; fi
YES

很容易找出它们的区别,-a-o 作为测试命令的参数用在测试命令的内部,而 &&|| 则用来运算测试的返回值,! 为两者通用。需要关注的是:

  • 有时可以不用 ! 运算符,比如 -eq-ne 刚好相反,可用于测试两个数值是否相等; -z-n 也是对应的,用来测试某个字符串是否为空
  • Bash 里,test 命令可以用[] 运算符取代,但是需要注意,[之后与] 之前需要加上额外的空格
  • 在测试字符串时,所有变量建议用双引号包含起来,以防止变量内容为空时出现仅有测试参数,没有测试内容的情况

下面我们用实例来演示上面三个注意事项:

  • -ne-eq 对应的,我们有时候可以免去 ! 运算

    $ i=5
    $ if test $i -eq 5; then echo "YES"; else echo "NO"; fi
    YES
    $ if test $i -ne 5; then echo "YES"; else echo "NO"; fi
    NO
    $ if test ! $i -eq 5; then echo "YES"; else echo "NO"; fi
    NO
    
  • [ ] 可以取代 test,这样看上去会“美观”很多

    $ if [ $i -eq 5 ]; then echo "YES"; else echo "NO"; fi
    YES
    $ if [ $i -gt 4 ] && [ $i -lt 6 ]; then echo "YES"; else echo "NO"; fi
    YES
    
  • 记得给一些字符串变量加上 "",记得 [ 之后与 ] 之前多加一个空格

    $ str=""
    $ if [ "$str" = "test"]; then echo "YES"; else echo "NO"; fi
    -bash: [: missing `]'
    NO
    $ if [ $str = "test" ]; then echo "YES"; else echo "NO"; fi
    -bash: [: =: unary operator expected
    NO
    $ if [ "$str" = "test" ]; then echo "YES"; else echo "NO"; fi
    NO
    

到这里,条件测试就介绍完了,下面介绍命令列表,实际上在上面我们已经使用过了,即多个test命令的组合,通过 &&||! 组合起来的命令序列。这种命令序列可以有效替换 if/then 的条件分支结构。这不难想到我们在 C 语言程序设计中经常做的如下的选择题(很无聊的例子,但是有意义):下面是否会打印 j,如果打印,将打印什么?

#include <stdio.h>
int main()
{
    int i, j;

    i=5;j=1;
    if ((i==5) && (j=5))  printf("%d\n", j);

    return 0;
}

很容易知道将打印数字 5,因为 i==5 这个条件成立,而且随后是 &&,要判断整个条件是否成立,我们得进行后面的判断,可是这个判断并非常规的判断,而是先把 j 修改为 5,再转换为真值,所以条件为真,打印出 5 。因此,这句可以解释为:如果 i 等于 5,那么把 j 赋值为 5,如果 j 大于 1 (因为之前已经为真),那么打印出 j 的值。这样用 && 连结起来的判断语句替代了两个 if 条件分支语句。

正是基于逻辑运算特有的性质,我们可以通过 &&|| 来取代 if/then 等条件分支结构,这样就产生了命令列表。

命令列表

命令列表的执行规律

命令列表的执行规律符合逻辑运算的运算规律,用 && 连接起来的命令,如果前者成功返回,将执行后面的命令,反之不然;用 || 连接起来的命令,如果前者成功返回,将不执行后续命令,反之不然。

范例:如果 ping 通 www.lzu.edu.cn,那么打印连通信息

$ ping -c 1 www.lzu.edu.cn -W 1 && echo "=======connected======="

非常有趣的问题出来了,即我们上面已经提到的:为什么要让 C 程序在 main() 函数的最后返回 0 ?如果不这样,把这种程序放入命令列表会有什么样的结果?你自己写个简单的 C 程序,然后放入命令列表看看。

命令列表的作用

有时用命令列表取代 if/then 等条件分支结构可以省掉一些代码,而且使得程序比较美观、易读,例如:

范例:在脚本里判断程序的参数个数,和参数类型

#!/bin/bash

echo $#
echo $1
if [ $# -eq 1 ] && (echo $1 | grep ^[0-9]*$ >/dev/null);then
    echo "YES"
fi

说明:上例要求参数个数为 1 并且类型为数字。

再加上 exit 1,我们将省掉 if/then 结构

#!/bin/bash

echo $#
echo $1
! ([ $# -eq 1 ] && (echo $1 | grep ^[0-9]*$ >/dev/null)) && exit 1

echo "YES"

这样处理后,对程序参数的判断仅仅需要简单的一行代码,而且变得更美观。

判断结构例子

1 if判断

算数比较

条件通常被放置在一个封闭的中括号内.一定要注意在[]与操作数之间有一个空格.如果忘记这个空格,脚本就会报错.
对变量或值进行算术条件判断:
[ $var -eq 0 ] #当$var等于0时,返回真
[ $var -ne 0 ] #当$var为非0时,返回真
其他重要的操作符如下所示
-gt 大于
-lt 小于
-ge 大于或等于
-le 小于或等于

[$var1 -ne 0 -a $var2 -gt 2] #使用逻辑与-a
[$var1 -ne 0 -o $var2 -gt 2] #使用逻辑或-o

2 文件系统

我们可以使用不同的条件标志测试不同的文件系统相关的属性

[ -a FILE ] 如果 FILE 存在则为真。
[ -b FILE ] 如果 FILE 存在且是一个块文件则返回为真。
[ -c FILE ] 如果 FILE 存在且是一个字符文件则返回为真。
[ -d FILE ] 如果 FILE 存在且是一个目录则返回为真。
[ -e FILE ] 如果 指定的文件或目录存在时返回为真。
[ -f FILE ] 如果 FILE 存在且是一个普通文件则返回为真。
[ -g FILE ] 如果 FILE 存在且设置了SGID则返回为真。
[ -h FILE ] 如果 FILE 存在且是一个符号符号链接文件则返回为真。(该选项在一些老系统上无效)
[ -k FILE ] 如果 FILE 存在且已经设置了冒险位则返回为真。
[ -p FILE ] 如果 FILE 存并且是命令管道时返回为真。
[ -r FILE ] 如果 FILE 存在且是可读的则返回为真。
[ -s FILE ] 如果 FILE 存在且大小非0时为真则返回为真。
[ -u FILE ] 如果 FILE 存在且设置了SUID位时返回为真。
[ -w FILE ] 如果 FILE 存在且是可写的则返回为真。(一个目录为了它的内容被访问必然是可执行的)
[ -x FILE ] 如果 FILE 存在且是可执行的则返回为真。
[ -O FILE ] 如果 FILE 存在且属有效用户ID则返回为真。
[ -G FILE ] 如果 FILE 存在且默认组为当前组则返回为真。(只检查系统默认组)
[ -L FILE ] 如果 FILE 存在且是一个符号连接则返回为真。
[ -N FILE ] 如果 FILE 存在 and has been mod如果ied since it was last read则返回为真。
[ -S FILE ] 如果 FILE 存在且是一个套接字则返回为真。
[ FILE1 -nt FILE2 ] 如果 FILE1 比 FILE2 新, 或者 FILE1 存在但是 FILE2 不存在则返回为真。
[ FILE1 -ot FILE2 ] 如果 FILE1 比 FILE2 老, 或者 FILE2 存在但是 FILE1 不存在则返回为真。
[ FILE1 -ef FILE2 ] 如果 FILE1 和 FILE2 指向相同的设备和节点号则返回为真。

3 字符串比较

[[ $str1 = $str2 ]]当str1等于str2时,返回真.也就是说,str1和str2包含的文本是一模一样的
[[ $str1 == $str2 ]]这是检查字符串是否相等的另一种写法
[[ $str1 != $str2 ]]如果str1和str2不相同,则返回真
[[ $str1 > $str2 ]] 如果str1的字母序比str2大,则返回真
[[ $str1 < $str2 ]] 如果str1的字母序比str2小,则返回真

[[ -z $str1 ]] 如果str1包含的是空字符串,则返回真
[[ -n $str1 ]] 如果str2包含的是非空字符串,则返回真

4 数值判断

[ INT1 -eq INT2 ] INT1和INT2两数相等返回为真 ,=
[ INT1 -ne INT2 ] INT1和INT2两数不等返回为真 ,<>
[ INT1 -gt INT2 ] INT1大于INT2返回为真 ,>
[ INT1 -ge INT2 ] INT1大于等于INT2返回为真,>=
[ INT1 -lt INT2 ] INT1小于INT2返回为真 ,<
[ INT1 -le INT2 ] INT1小于等于INT2返回为真,<=

5 逻辑判断

[ ! EXPR ] 逻辑非,如果 EXPR 是false则返回为真。
[ EXPR1 -a EXPR2 ] 逻辑与,如果 EXPR1 and EXPR2 全真则返回为真。
[ EXPR1 -o EXPR2 ] 逻辑或,如果 EXPR1 或者 EXPR2 为真则返回为真。
[ ] || [ ] 用OR来合并两个条件
[ ] && [ ] 用AND来合并两个条件

其他判断
[ -t FD ] 如果文件描述符 FD (默认值为1)打开且指向一个终端则返回为真
[ -o optionname ] 如果shell选项optionname开启则返回为真

6 IF高级特性:

双圆括号(( )):表示数学表达式
在判断命令中只允许在比较中进行简单的算术操作,而双圆括号提供更多的数学符号,而且在双圆括号里面的'>','<'号不需要转意。

双方括号[[ ]]:表示高级字符串处理函数
双方括号中判断命令使用标准的字符串比较,还可以使用匹配模式,从而定义与字符串相匹配的正则表达式。

双括号的作用:
在shell中,[ $a != 1 || $b = 2 ]是不允许出,要用[ $a != 1 ] || [ $b = 2 ],而双括号就可以解决这个问题的,[[ $a != 1 || $b = 2 ]]。又比如这个[ "$a" -lt "$b" ],也可以改成双括号的形式(("$a" < "$b"))

实例

1:判断目录$doiido是否存在,若不存在,则新建一个

if [ ! -d "$doiido"]; then
  mkdir "$doiido"
fi

2:判断普通文件$doiido是否存,若不存在,则新建一个

if [ ! -f "$doiido" ]; then
  touch "$doiido"
fi

3:判断$doiido是否存在并且是否具有可执行权限

if [ ! -x "$doiido"]; then
mkdir "$doiido"
chmod +x "$doiido"
fi

4:是判断变量$doiido是否有值

if [ ! -n "$doiido" ]; then
  echo "$doiido is empty"
  exit 0
fi

5:两个变量判断是否相等

if [ "$var1" = "$var2" ]; then
  echo '$var1 eq $var2'
else
  echo '$var1 not eq $var2'
fi

6:测试退出状态:

if [ $? -eq 0 ];then
echo 'That is ok'
fi

7:数值的比较:

if [ "$num" -gt "150" ]
echo "$num is biger than 150"
fi

8:a>b且a<c

(( a > b )) && (( a < c ))
[[ $a > $b ]] && [[ $a < $c ]]
[ $a -gt $b -a $a -lt $c ]

9:a>b或a<c

(( a > b )) || (( a < c ))
[[ $a > $b ]] || [[ $a < $c ]]
[ $a -gt $b -o $a -lt $c ]

10:检测执行脚本的用户

if [ "$(whoami)" != 'root' ]; then
echo "You have no permission to run $0 as non-root user."
exit 1;
fi

11:正则表达式

doiido="hero"
if [[ "$doiido" == h* ]];then
echo "hello,hero"
fi

12 查看当前操作系统类型

#!/bin/sh
SYSTEM=`uname -s`
if [ $SYSTEM = "Linux" ] ; then
echo "Linux"
elif [ $SYSTEM = "FreeBSD" ] ; then
echo "FreeBSD"
elif [ $SYSTEM = "Solaris" ] ; then
echo "Solaris"
else
echo "What?"
fi

13 if利用read传参判断

#!/bin/bash
read -p "please input a score:" score
echo -e "your score [$score] is judging by sys now"

if [ "$score" -ge "0" ]&&[ "$score" -lt "60" ];then
echo "sorry,you are lost!"
elif [ "$score" -ge "60" ]&&[ "$score" -lt "85" ];then
echo "just soso!"
elif [ "$score" -le "100" ]&&[ "$score" -ge "85" ];then
echo "good job!"
else
echo "input score is wrong , the range is [0-100]!"
fi

14 判断文件是否存在.

#!/bin/sh
today=`date -d yesterday +%y%m%d`
file="apache_$today.tar.gz"
cd /home/chenshuo/shell
if [ -f "$file" ];then
echo "OK"
else
echo "error $file" >error.log
mail -s "fail backup from test" [email protected] <error.log
fi

15 这个脚本在每个星期天由cron来执行。如果星期的数是偶数,他就提醒你把垃圾箱清理:

#!/bin/bash
WEEKOFFSET=$[ $(date +"%V") % 2 ]

if [ $WEEKOFFSET -eq "0" ]; then
echo "Sunday evening, put out the garbage cans." | mail -s "Garbage cans out" your@your_domain.org
fi

16 挂载硬盘脚本(windows下的ntfs格式硬盘)

#! /bin/sh
dir_d=/media/disk_d
dir_e=/media/disk_e
dir_f=/media/disk_f

a=`ls $dir_d | wc -l`
b=`ls $dir_e | wc -l`
c=`ls $dir_f | wc -l`

echo "checking disk_d..."
if [ $a -eq 0 ]; then
echo "disk_d is not exsit,now creating..."
sudo mount -t ntfs /dev/disk/by-label/software /media/disk_d
else
echo "disk_d exits"
fi

echo "checking disk_e..."
if [ $b -eq 0 ]; then
echo "disk_e is not exsit,now creating..."
sudo mount -t ntfs /dev/disk/by-label/elitor /media/disk_e
else
echo "disk_e exits"
fi

echo "checking disk_f..."
if [ $c -eq 0 ]; then
echo "disk_f is not exsit,now creating..."
sudo mount -t ntfs /dev/disk/by-label/work /media/disk_f
else
echo "disk_f exits"
fi

results matching ""

    No results matching ""