Shell基础之控制流结构
一、控制结构
几乎所有的脚本里都有某种流控制结构,很少有例外。流控制是什么?假定有一个脚本,包含下列几个命令:
#!/bin/sh
# make a directory
mkdir /home/dave/mydocs
# copy all doc files
cp *.docs /home/dave/docs
# delete all doc files
rm *.docs
上述脚本问题出在哪里?如果目录创建失败或目录创建成功文件拷贝失败,如何处理?这里需要从不同的目录中拷贝不同的文件。必须在命令执行前或最后的命令退出前决定处理方法。shell会提供一系列命令声明语句等补救措施来帮助你在命令成功或失败时,或需要处理一个命令清单时采取正确的动作。这些命令语句大概分两类:
1、循环和流控制
if 语句
提供条件测试。测试可以基于各种条件。例如文件的权限、长度、数值或字符串的比较。这些测试返回值或者为真(0),或者为假(1)。基于此结果,可以进行相关操作。在讲到条件测试时已经涉及了一些测试语法。case语句
允许匹配模式、单词或值。一旦模式或值匹配,就可以基这个匹配条件作其他声明。
2、循环
循环或跳转是一系列命令的重复执行过程,本书提到了3种循环语句:
for 循环
每次处理依次列表内信息,直至循环耗尽。Until 循环
此循环语句不常使用, until循环直至条件为真。条件部分在循环末尾部分。While 循环
while循环当条件为真时,循环执行,条件部分在循环头。
流控制语句的任何循环均可嵌套使用,例如可以在一个for循环中嵌入另一个for循环。
二、实例讲解
现在开始讲解循环和控制流,并举一些脚本实例。
从现在起,脚本中语句使用LINUX或BSD版本,也就是说使用echo方法echo -e -n,意即从echo结尾中下一行执行命令。
1、grep输出检查
不必拘泥于变量或数值测试,也可以测知系统命令是否成功返回。对grep使用if语句找出,grep是否成功返回信息。下面的例子中grep用于查看Dave是否在数据文件data.file中,注意’Dave>‘用于精确匹配。
[root@localhost ~]# cat grepif.sh
#!/bin/sh
# grepif.sh
if grep 'Dave\>' data.file > /dev/null 2>&1
then
echo "Great Dave is in the file"
else
echo "No Dave is not in the file"
fi
[root@localhost ~]# ./grepif.sh
No Dave is not in the file
2、用变量测试grep输出
正像前面看到的,可以用grep作字符串操作。下面的脚本中,用户输入一个名字列表,grep在变量中查找,要求其包含人名Peter
[root@localhost ~]# cat grepstr.sh
#!/bin/sh
# grepstr
echo -n "Enter a list of names:"
read list
if echo $list | grep "Peter" > /dev/null 2>&1
then
echo "Peter is here"
# could do some processing here...
else
echo "Peter's not in the list. No comment!"
fi
[root@localhost ~]# ./grepstr.sh
Enter a list of names:John Louise Peter James
Peter is here
3、文件拷贝输出检查
下面测试文件拷贝是否正常,如果cp命令并没有拷贝文件myfile到myfile.bak,则打印错误信息。注意错误信息中
basename $0
打印脚本名。如果脚本错误退出,一个好习惯是显示脚本名并将之定向到标准错误中。用户应该知道产生错误的脚本名。
[root@localhost ~]# cat ifcp.sh
#!/bin/sh
# ifcp.sh
if cp myfile myfile.bak; then
echo "good copy"
else
echo "`basename $0`: error could not copy the file" >&2
fi
[root@localhost ~]# ./ifcp.sh
cp: cannot stat `myfile': No such file or directory
ifcp.sh: error could not copy the file
注意,文件可能没找到,系统也产生本身的错误信息,这类错误信息可能与输出混在一起。既然已经显示系统错误信息获知脚本失败,就没必要显示两次。要去除系统产生的错误和系统输出,只需简单的将标准错误和输出重定向即可。修改脚本为: >/dev/null 2>&1。
[root@localhost ~]# cat ifcp.sh
#!/bin/sh
# ifcp.sh
if cp myfile myfile.bak > /dev/null 2>&1; then
echo "good copy"
else
echo "`basename $0`: error could not copy the file" >&2
fi
[root@localhost ~]# ./ifcp.sh
ifcp.sh: error could not copy the file
上面当中>/dev/null表示任何标准输出都定向到那个无尽的“黑洞”/de/null中,然后2>&1表示错误输出也是到/dev/null中,&1表示前面的那个/dev/null,脚本运行时,所有输出包括错误重定向至系统垃圾堆。
4、当前目录测试
当运行一些管理脚本时,可能要在根目录下运行它,特别是移动某种全局文件或进行权限改变时。一个简单的测试可以获知是否运行在根目录下。下面脚本中变量DIRECTORY使用当前目录的命令替换操作,然后此变量值与” / “字符串比较( /为根目录)。如果变量值与字符串不等,则用户退出脚本,退出状态为1意味错误信息产生。
[root@localhost ~]# cat ifpwd.sh
#!/bin/sh
# ifpwd.sh
DIRECTORY=`pwd`
# grab the current dirctory
if [ "$DIRECTORY" != "/" ]; then
# is it the root directory ?
# no, the direct output to standard error, which is the screen
# by default.
echo "You need to be in the root directory no $DIRECTORY to run
this script" >&2
# exit with a value of 1, an error
exit 1
fi
[root@localhost ~]# ./ifpwd.sh
You need to be in the root directory no /root to run
this script
5、文件权限测试
可以用i f语句测试文件权限,下面简单测试文件test.txt是否被设置到变量LOGNAME,测试test.txt文件是否具有写的权限。下面的脚本先建立一个test.txt的空白文档,列出它的相关权限。然后执行脚本测试其是否可以写入,然后显示相关信息。
[root@localhost ~]# touch test.txt
[root@localhost ~]# ls -l test.txt
-rw-r--r-- 1 root root 0 Nov 21 15:21 test.txt
[root@localhost ~]# chmod u+x ifwr.sh
[root@localhost ~]# cat ifwr.sh
#!/bin/sh
# ifwr.sh
LOGFILE=test.txt
echo $LOGFILE
if [ ! -w "$LOGFILE" ]; then
echo " You cannot write to $LOGFILE" >&2
else
echo " You can write to $LOGFILE" >&2
fi
[root@localhost ~]# ./ifwr.sh
test.txt
You can write to test.txt
6、测试传递到脚本中的参数
if语句可用来测试传入脚本中参数的个数。使用特定变量$#,表示调用参数的个数。可以测试所需参数个数与调用参数个数是否相等。以下测试确保脚本有三个参数。如果没有,则返回一个可用信息到标准错误,然后代码退出并显示退出状态。如果参数数目等于3,则显示所有参数。
[root@localhost ~]# cat ifparam.sh
#!/bin/sh
# ifparam
if [ $# -lt 3 ]; then
# less than 3 parameters called, echo a usage message and exit
# 如果少于三个参数则显示使用的信息,然后退出。
echo "Usage: `basename $0`arg1 arg2 arg3" >&2
exit 1
fi
# good, received 3 params, let's echo them
# 好,现在接受了三个参数,让我们开始显示他们
echo "arg1: $1"
echo "arg2: $2"
echo "arg3: $3"
[root@localhost ~]# ./ifparam.sh cup medal
Usage: ifparam.sharg1 arg2 arg3
[root@localhost ~]# ./ifparam.sh cup medal trophy
arg1: cup
arg2: medal
arg3: trophy
从上面的运行信息可以看出,如果只传入两个参数,则显示一可用信息,然后脚本退出。只有正确传入了三个参数了,才显示所有的参数然后退出。
7、决定脚本是否为交互模式
有时需要知道脚本运行是交互模式(终端模式)还是非交互模式(cron或at)。脚本也许需要这个信息以决定从哪里取得输入以及输出到哪里,使用test命令并带有-t选项很容易确认这一点。如果test返回值为1,则为交互模式。假如我是在一个终端下运行下面这个脚本。
[root@localhost ~]# cat ifinteractive.sh
#!/bin/sh
# ifinteractive.sh
if [ -t ]; then
echo "We are interactive with a terminal"
else
echo "We must be running from some background process probably
cron or at"
fi
[root@localhost ~]# ./ifinteractive.sh
We are interactive with a terminal
8、变量设置测试
下面的例子测试环境变量EDITOR是否已设置。如果EDITOR变量为空,将此信息通知用户。如果已设置,在屏幕上显示编辑类型。
[root@localhost ~]# cat ifeditor.sh
#!/bin/sh
# ifeditor.sh
if [ -z $EDITOR ]; then
# the variable has not been set
# 变量没有设置
echo "Your EDITOR environment is not set"
else
# let's see what it is
# 如果设置了,让我们来看看它到底是什么
echo "Using $EDITOR as the default editor"
fi
[root@localhost ~]# ./ifeditor.sh
Your EDITOR environment is not set
9、将脚本参数传入系统命令
可以向脚本传递位置参数,然后测试变量。这里,如果用户在脚本名字后键入目录名,脚本将重设$1特殊变量为一更有意义的名字。即DIRECTORY。这里需测试目录是否为空,如果目录为空,ls -A将返回空,然后对此返回一信息。
# ifdirec.sh
# assigning $1 to DIRECTORY variable
DIRECTORY=$1
if [ "`ls -A $DIRECTORY`" == "" ]; then
# if it's an empty string, then it's empty
echo "$DIRECTORY is indeed empty"
else
# otherwise it is not
echo "$DIRECTORY is not empty"
fi
也可以使用下面的脚本替代上面的例子并产生同样的结果
[root@localhost ~]# cat ifdirec2.sh
#!/bin/sh
# ifdirec2
DIRECTORY=$1
if [ -z "`ls -A $DIRECTORY`" ]
then
echo "$DIRECTORY is indeed empty"
else
echo "$DIRECTORY is not empty"
fi
10、null命令用法
到目前为止,条件测试已经讲完了then和else部分,有时也许使用者并不关心条件为真或为假。不幸的是if语句各部分不能为空—一些语句已经可以这样做。为解决此问题, shell提供了:
空命令
。空命令永远为真(也正是预想的那样)。回到前面的例子,如果目录为空,可以只在then部分加入命令。
[root@localhost ~]# cat ifdirectory.sh
#!/bin/sh
# ifdirectory.sh
DIRECTORY=$1
if [ "`ls -A $DIRECTORY`" == "" ]
then
echo "$DIRECTORY is indeed empty"
else :
# do nothing
fi
[root@localhost ~]# ./ifdirectory.sh testd
testd is indeed empty