Shell脚本的实质,只是把一系列的shell命令写入文件,然后给予该文件执行权限。执行脚本文件时,脚本中的命令按照自上而下的顺序一条接一条地被执行,直到脚本结束。那么可以改变脚本的执行顺序吗?答案是肯定的。如果你接触过编程类语言,应该对ifforwhile不陌生吧(当然,没接触过也丝毫没有关系)。shell也有这些结构,它们被称为控制流结构。Shell中控制流具体有哪些呢,这就是本篇文章要讨论的全部内容:
判断 if
分支判断case
循环 for
循环 while
循环 until
其实概括起来,只是2类结构而已:判断和循环。
 

一、判断 if

if结构的一般格式为:
if command
then
command1
command2
fi
其中,if后面的命令command,需要检测其退出状态:如果退出状态为0,则执行thenfi之间的命令;退出状态不为0,则跳过这些命令。
退出状态
linux系统中,每个命令执行完成后,都会给系统返回一个退出状态。该状态是个数值,用来告诉系统命令执行成功了没有。按照约定,退出状态为0表示成功,不为0表示失败。命令运行失败的常见原因之一,就是传递给命令的参数不对,但是也可能有其他原因。拿grep做为例子,当给grep传递的参数数目不对,或者传递的参数文件不存在,或者grep没有找到指定的模式,都被认为是失败的,返回一个不为0的退出状态。只有当grep在指定文件中找到了指定的模式,才返回退出状态0
$ who | grep root
root     tty2         2009-08-17 16:59
root     pts/0        2009-09-29 16:25 (192.168.1.130)
$ echo $?
0
$ who | grep licong
$ echo $?
1
$
符号$?表示上一个命令的退出状态。当我们在登陆用户中找licong时,因为没找到返回给系统一个值为1的退出状态,我们用ehco把这个状态显示了出来。注意,成功的命令退出状态值总是1,而失败的非0状态值可能因系统而异(并不一定总是1)。
我们现在就用if编写一个名为on的简单脚本,作用是告诉我们某指定的用户是否登陆到当前系统。如果用户登陆了,就显示一条信息反映这种状况,否则什么也不做,脚本如下:
$ cat on
#
# 判断用户是否登陆到当前系统,脚本名:on
 
user=”$1”
if who | grep “$user”
then
   echo “$user is logged on”
fi
$
从命令行键入的第一个参数($1)存在shell变量user中,然后if判断命令
who | grep “$user”
的退出状态。如果退出状态为0,说明找到了$user,执行then后面的echo语句显示用户已登陆;如果退出状态为1,说明没找到$user,什么也不做,脚本也结束了。这个脚本我们还加入了注释和空行,增加脚本的易读性。其中注释用了中文,方便读者理解,但是不建议实际脚本中用中文注释。我们来看一下脚本的执行情况:
$ who
root     tty2         2009-08-17 16:59
root     pts/0        2009-09-29 16:25 (192.168.1.130)
$ on root
root is logged on
$ on licong
$
或许你对结果还算满意吧,它满足了我们的要求。这个脚本还有很多缺陷,比如使用脚本的人不给on提供任何参数,或者提供不只一个参数,会出现什么情况呢?这个脚本没有给这些情况给予处理,只是当什么也没发生。后续的内容我们还会再来完善这个脚本,别着急。
test命令
shell有一条名为test的内部命令,经常用于测试一种或几种条件,其格式为
test expression
其中expression是要测试的条件。test命令大多数时候都是和if搭配使用的:
if test expression
如果你对这个形式感到陌生,相信下面的格式你会觉得熟悉一点(如果你曾经看过脚本的话):
if [ expression ]
没错,test[ ]的作用是完全相同的,以至于你几乎在任何脚本中到看不到if test,而if [ ]却随处可见。[ ]也是shell的一个内部命令,既然是命令,一定要用空格把[ ]和其他参数分开,而且他们一定要成对使用的。
显而易见,上面的2种格式中,条件expression也是重要的组成部分。我们简要概括一下expression的几种常见形式(每行后面的内容是退出状态为0的条件)。
字符串比较
[ string1 = string2 ]   string1string2相同
[ string1 != string2 ]   string1string2不相同
[ -n string ]          string不为空
[ -z string ]          string为空
再次提醒一下,[ ]里面的所有词都要用空格分开(包括=号),因为他们都是命令[ ]的参数。
整数比较
[ 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
[ int –le int2 ]           int1小于等于int2
这么多操作符,是不是很难记住啊。其实这些符号都是英文的缩写字母:
eq                  equal等于
n                   not
g                   greater大于
l                    less小于
ngl后面的e      equal等于
文件判断
[ -e file ]               file存在
[ -d file ]               file为目录
[ -f file ]               file为普通文件
[ -r file ]               file可读
[ -w file ]               file可写
[ -x file ]               file可执行
[ -s file ]               file内容不为空
[ -L file]                file为符号链接
逻辑操作符
[ express1 –a express2 ]   条件1和条件2同时满足
[ express1 –o express2 ]   条件1和条件2至少有一个满足
[ express1 ]           条件1不满足
结合这些判断条件的形式,我们来看一些例子:
$ x1="005"
$ x2="  10"
$ [ "$x1" = 5 ]
$ echo $?
1
$ [ "$x1" -eq 5 ]
$ echo $?
0
$ [ "$x2" = 10 ]
$ echo $?
1
$ [ "$x2" -eq 10 ]
$ echo $?
0
$
我们注意到,用字符串比较符=来比较“005”和5以及“  10”和10,得到的结果是他们都不相等;而用整数比较符-eq来比较时,“005”和5是相等的,“  10”和10也是相等的。很多人经常搞错等号=的意义,认为=是用来比较数值是否相等的。其实=只用来比较字符串是否相同,上面的例子应该能帮助你理解=-eq的用法了。
$ ls /home/
licong  lost+found  pub  share  steve  test  www
$ [ -d /home/steve ]
$ echo $?
0
$ [ -f /home/steve ]
$ echo $?
1
$ [ -r /home/steve ]
$ echo $?
0
$ [ -w /home/steve ]
$ echo $?
0
$
以上结果表明,/home/steve是一个目录,而不是普通文件,而且可读可写。
$ [ -d /home/steve -a -x /home/steve ]
$ echo $?
0
$
这个结果表明,/home/steve同时满足“是目录+可执行”。我们可以把这些条件全部写在一条命令中:
$ [ -d /home/steve -a -r /home/steve -a -w /home/steve -a -x /home/steve ]
$ echo $?
0
$
else结构
可以给if结构加上else,其一般格式为:
if command
then
command1
command2
else
    command3
    command4
   
fi
如果command退出状态为0,则执行then后面的command1command2。。。;否则,执行else后面的command3command4。。。任何情况下,只执行then或者else其中的一组命令。我们用这个结构来完善一下前面的脚本on
$ cat on.v1
#
# 判断用户是否登陆到当前系统,脚本名:on.v1
# 版本1,增加用户未登陆时的信息提示
 
user=”$1”
if who | grep “$user”
then
   echo “$user is logged on”
else
   echo “$user is not logged on”
fi
$
我们加入了一个else结构,当指定用户未登陆到当前系统时,告诉用户这个信息(总是能得到通知的用户是幸福的)。我们看一下新脚本的执行情况:
$ on licong
licong is not logged on
$
之前我们还提到了,如果用户输入的参数不是一个话,脚本什么也不做。这个做发并不好,我们应该提示用户,参数不对,并提示脚本正确的使用方法(好的脚本总是会做这件事情):
$ cat on.v2
#
# 判断用户是否登陆到当前系统,脚本名:on.v2
# 版本2,增加对参数的判断
 
if [ $# -ne 1 ]
then
    echo "Incorrect number of arguments.  "
    echo "Usage: $0 user."
    exit 1
else
user=”$1”
if who | grep “$user”
then
            echo “$user is logged on”
else
            echo “$user is not logged on”
fi
       fi
$
先来看一下脚本的执行情况:
$ on
Incorrect number of arguments.
Usage: ./on user.
$ on licong
licong is not logged on
$
当我们没有给脚本提供任何参数的时候,我们看到了一句错误提示Incorrect number of arguments.和一句正确用法提示Usage: ./on user.
判断参数个数主要由if [ $# -ne 1 ]$#代表传递给程序的参数的个数,这个我们后面还会详细介绍。只要脚本参数个数不等于1,我们就报错并提示正确的用法。改进的脚本还有一个新的看点,那就是exit命令,该命令的一般格式为:
exit n
脚本到达exit命令位置,便会无条件退出脚本,并把脚本的退出状态设置为整数n。如果不指定,则采用exit之前最后所执行的命令的返回状态。需要特别指出的一点是,直接在shell下键入exit会退出当前shell
if判断结构还有偶一点没讲到,那便是elif,结构如下:
if command1
then
    command
   
elif command2
then
    command
   
elif commandn
then
    command
   
else
    command
   
fi
由于我们花了太多的篇幅介绍if了,这里就不详细介绍了也不举例子了。简单说一下,这里else也是可选的,elifif类似一定要和then配套,这个是比较容易忘记的。
 

二、分支判断case