免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
123下一页
最近访问板块 发新帖
查看: 290163 | 回复: 21

shell基础第十八篇-控制流结构 [复制链接]

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2010-11-20 14:44 |显示全部楼层
本帖最后由 L_kernel 于 2010-11-20 14:46 编辑

所有功能脚本必须有能力进行判断,也必须有能力基于一定条件处理相关命令。本章讲
述这方面的功能,在脚本中创建和应用控制结构。
本章内容有:
• 退出状态。
• while、f o r和until loops循环。
• if then else语句。
• 脚本中动作。
• 菜单。

退出状态
在书写正确脚本前,大概讲一下退出状态。任何命令进行时都将返回一个退出状态。如
果要观察其退出状态,使用最后状态命令:
$ echo $?
主要有4种退出状态。前面已经讲到了两种,即最后命令退出状态$ ?和控制次序命令( $ $、
| |)。其余两种是处理s h e l l脚本或s h e l l退出及相应退出状态或函数返回码。在第1 9章讲到函数
时,也将提到其返回码。
要退出当前进程,s h e l l提供命令e x i t,一般格式为:
exit n
其中,n为一数字。
如果只在命令提示符下键入e x i t,假定没有在当前状态创建另一个s h e l l,将退出当前s h e l l。
如果在脚本中键入e x i t,s h e l l将试图(通常是这样)返回上一个命令返回值。有许多退出脚本
值,但其中相对于脚本和一般系统命令最重要的有两种,即:
退出状态0 退出成功,无错误。
退出状态1 退出失败,某处有错误。
可以在s h e l l脚本中加入自己的退出状态(它将退出脚本)。本书鼓励这样做,因为另一个
s h e l l脚本或返回函数可能要从s h e l l脚本中抽取退出脚本。另外,相信加入脚本本身的退出脚
本值是一种好的编程习惯。
如果愿意,用户可以在一个用户输入错误后或一个不可覆盖错误后或正常地处理结束后
退出脚本。
注意从现在起,本书所有脚本都将加入注释行。注释行将解释脚本具体含义,帮助用户
理解脚本。可以在任何地方加入注释行,因为其本身被解释器忽略。注释行应以#开头。

控制结构
几乎所有的脚本里都有某种流控制结构,很少有例外。流控制是什么?假定有一个脚本
包含下列几个命令:
  1. #!/bin/sh
  2. # make a directory
  3. mkdir /home/dave/mydocs
  4. # copy all doc files
  5. cp *.docs /home/dave/docs
  6. # delete all doc files
  7. rm *.docs
复制代码
上述脚本问题出在哪里?如果目录创建失败或目录创建成功文件拷贝失败,如何处理?
这里需要从不同的目录中拷贝不同的文件。必须在命令执行前或最后的命令退出前决定处理
方法。s h e l l会提供一系列命令声明语句等补救措施来帮助你在命令成功或失败时,或需要处
理一个命令清单时采取正确的动作。
这些命令语句大概分两类:
循环和流控制。


流控制
i f、t h e n、e l s e语句提供条件测试。测试可以基于各种条件。例如文件的权限、长度、数
值或字符串的比较。这些测试返回值或者为真( 0),或者为假( 1)。基于此结果,可以进行
相关操作。在讲到条件测试时已经涉及了一些测试语法。
c a s e语句允许匹配模式、单词或值。一旦模式或值匹配,就可以基于这个匹配条件作其他
声明。


循环
循环或跳转是一系列命令的重复执行过程,本书提到了3种循环语句:
for 循环每次处理依次列表内信息,直至循环耗尽。
Until 循环此循环语句不常使用, u n t i l循环直至条件为真。条件部分在循环末尾部分。
While 循环w h i l e循环当条件为真时,循环执行,条件部分在循环头。
流控制语句的任何循环均可嵌套使用,例如可以在一个f o r循环中嵌入另一个f o r循环。
现在开始讲解循环和控制流,并举一些脚本实例。
从现在起,脚本中e c h o语句使用L I N U X或B S D版本,也就是说使用e c h o方法echo -e -n,
意即从e c h o结尾中下一行执行命令。应用于U N I X(系统V和B S D)的统一的e c h o命令参阅1 9
章s h e l l函数。

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2010-11-20 15:38 |显示全部楼层
grep输出检查
不必拘泥于变量或数值测试,也可以测知系统命令是否成功返回。对g r e p使用i f语句找出
g r e p是否成功返回信息。下面的例子中g r e p用于查看D a v e是否在数据文件d a t a . f i l e中,注意
‘D a v e \ >’用于精确匹配。
  1. [root@localhost ~]# cat grepif.sh
  2. #!/bin/sh
  3. # grepif.sh
  4. if grep 'Dave\>' data.file > /dev/null 2>&1
  5. then
  6.                 echo "Great Dave is in the file"
  7. else
  8.                 echo "No Dave is not in the file"
  9. fi
  10. [root@localhost ~]# ./grepif.sh
  11. No Dave is not in the file
复制代码
用变量测试grep输出
正像前面看到的,可以用g r e p作字符串操作。下面的脚本中,用户输入一个名字列表,
g r e p在变量中查找,要求其包含人名P e t e r。
下面是具体的代码情况和运行信息。
  1. [root@localhost ~]# cat grepstr.sh
  2. #!/bin/sh
  3. # grepstr
  4. echo -n "Enter a list of names:"
  5. read list
  6. if echo $list | grep "Peter" > /dev/null 2>&1
  7. then
  8.                 echo "Peter is here"
  9.                 # could do some processing here...
  10. else
  11.                 echo "Peter's not in the list. No comment!"
  12. fi
  13. [root@localhost ~]# ./grepstr.sh
  14. Enter a list of names:John Louise Peter James
  15. Peter is here
复制代码
文件拷贝输出检查
下面测试文件拷贝是否正常,如果c p命令并没有拷贝文件m y f i l e到m y f i l e . b a k,则打印错
误信息。注意错误信息中` basename $0`打印脚本名。
如果脚本错误退出,一个好习惯是显示脚本名并将之定向到标准错误中。用户应该知道
产生错误的脚本名。
  1. [root@localhost ~]# cat ifcp.sh
  2. #!/bin/sh
  3. # ifcp.sh
  4. if cp myfile myfile.bak; then
  5.                 echo "good copy"
  6. else
  7.                 echo "`basename $0`: error could not copy the file" >&2
  8. fi
  9. [root@localhost ~]# ./ifcp.sh
  10. cp: cannot stat `myfile': No such file or directory
  11. ifcp.sh: error could not copy the file
复制代码
注意,文件可能没找到,系统也产生本身的错误信息,这类错误信息可能与输出混在一
起。既然已经显示系统错误信息获知脚本失败,就没必要显示两次。要去除系统产生的错误
和系统输出,只需简单的将标准错误和输出重定向即可。修改脚本为: >/dev/null 2>&1。
  1. [root@localhost ~]# cat ifcp.sh
  2. #!/bin/sh
  3. # ifcp.sh
  4. if cp myfile myfile.bak > /dev/null 2>&1; then
  5.                 echo "good copy"
  6. else
  7.                 echo "`basename $0`: error could not copy the file" >&2
  8. fi
  9. [root@localhost ~]# ./ifcp.sh
  10. ifcp.sh: error could not copy the file
复制代码
上面当中>/dev/null表示任何标准输出都定向到那个无尽的“黑洞”/de/null中,然后2>&1表示
错误输出也是到/dev/null中,&1表示前面的那个/dev/null,脚本运行时,所有输出包括错误重定向至系统垃圾堆。

当前目录测试
当运行一些管理脚本时,可能要在根目录下运行它,特别是移动某种全局文件或进行权
限改变时。一个简单的测试可以获知是否运行在根目录下。下面脚本中变量D I R E C TO RY使用
当前目录的命令替换操作,然后此变量值与" / "字符串比较( /为根目录)。如果变量值与字符
串不等,则用户退出脚本,退出状态为1意味错误信息产生。
  1. [root@localhost ~]# cat ifpwd.sh
  2. #!/bin/sh
  3. # ifpwd.sh
  4. DIRECTORY=`pwd`
  5. # grab the current dirctory
  6. if [ "$DIRECTORY" != "/" ]; then
  7.                 # is it the root directory ?
  8.                 # no, the direct output to standard error, which is the screen
  9.                 # by default.
  10.                 echo "You need to be in the root directory no $DIRECTORY to run
  11.                 this script" >&2
  12.                 # exit with a value of 1, an error
  13.                 exit 1
  14. fi
  15. [root@localhost ~]# ./ifpwd.sh
  16. You need to be in the root directory no /root to run
  17.                 this script
复制代码
文件权限测试
可以用i f语句测试文件权限,下面简单测试文件t e s t . t x t是否被设置到变量L O G N A M E,测试test.txt文件是否具有写的权限。下面的脚本先建立一个test.txt的空白文档,列出它的相关权限。然后执行脚本测试其是否可以写入,然后显示相关信息。
  1. [root@localhost ~]# touch test.txt
  2. [root@localhost ~]# ls -l test.txt
  3. -rw-r--r-- 1 root root 0 Nov 21 15:21 test.txt
  4. [root@localhost ~]# chmod u+x ifwr.sh
  5. [root@localhost ~]# cat ifwr.sh
  6. #!/bin/sh
  7. # ifwr.sh
  8. LOGFILE=test.txt
  9. echo $LOGFILE
  10. if [ ! -w "$LOGFILE" ]; then
  11.                 echo " You cannot write to $LOGFILE" >&2
  12. else
  13.                 echo " You can write to $LOGFILE" >&2
  14. fi
  15. [root@localhost ~]# ./ifwr.sh
  16. test.txt
  17. You can write to test.txt
复制代码
测试传递到脚本中的参数
i f语句可用来测试传入脚本中参数的个数。使用特定变量$ #,表示调用参数的个数。可以
测试所需参数个数与调用参数个数是否相等。
以下测试确保脚本有三个参数。如果没有,则返回一个可用信息到标准错误,然后代码
退出并显示退出状态。如果参数数目等于3,则显示所有参数。
  1. [root@localhost ~]# cat ifparam.sh
  2. #!/bin/sh
  3. # ifparam
  4. if [ $# -lt 3 ]; then
  5.                 # less than 3 parameters called, echo a usage message and exit
  6.                 # 如果少于三个参数则显示使用的信息,然后退出。
  7.                 echo "Usage: `basename $0`arg1 arg2 arg3" >&2
  8.                 exit 1
  9. fi
  10. # good, received 3 params, let's echo them
  11. # 好,现在接受了三个参数,让我们开始显示他们
  12. echo "arg1: $1"
  13. echo "arg2: $2"
  14. echo "arg3: $3"
  15. [root@localhost ~]# ./ifparam.sh cup medal
  16. Usage: ifparam.sharg1 arg2 arg3
  17. [root@localhost ~]# ./ifparam.sh cup medal trophy
  18. arg1: cup
  19. arg2: medal
  20. arg3: trophy
复制代码
从上面的运行信息可以看出,如果只传入两个参数,则显示一可用信息,然后脚本退出。
只有正确传入了三个参数了,才显示所有的参数然后退出。

决定脚本是否为交互模式
有时需要知道脚本运行是交互模式(终端模式)还是非交互模式( c r o n或a t)。脚本也许
需要这个信息以决定从哪里取得输入以及输出到哪里,使用t e s t命令并带有- t选项很容易确认
这一点。如果t e s t返回值为1,则为交互模式。假如我是在一个终端下运行下面这个脚本。
  1. [root@localhost ~]# cat ifinteractive.sh
  2. #!/bin/sh
  3. # ifinteractive.sh
  4. if [ -t ]; then
  5.                 echo "We are interactive with a terminal"
  6. else
  7.                 echo "We must be running from some background process probably
  8.                 cron or at"
  9. fi
  10. [root@localhost ~]# ./ifinteractive.sh
  11. We are interactive with a terminal
复制代码
简单的if else语句
下一个i f语句有可能是使用最广泛的:
if 条件
t h e n
命令1
e l s e
命令2
f i
使用i f语句的e l s e部分可在条件测试为假时采取适当动作。

变量设置测试
下面的例子测试环境变量E D I TO R是否已设置。如果E D I TO R变量为空,将此信息通知用
户。如果已设置,在屏幕上显示编辑类型。
  1. [root@localhost ~]# cat ifeditor.sh
  2. #!/bin/sh
  3. # ifeditor.sh
  4. if [ -z $EDITOR ]; then
  5.                 # the variable has not been set
  6.                 # 变量没有设置
  7.                 echo "Your EDITOR environment is not set"
  8. else
  9.                 # let's see what it is
  10.                 # 如果设置了,让我们来看看它到底是什么
  11.                 echo "Using $EDITOR as the default editor"
  12. fi
  13. [root@localhost ~]# ./ifeditor.sh
  14. Your EDITOR environment is not set
复制代码

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2010-11-20 16:10 |显示全部楼层
将脚本参数传入系统命令
可以向脚本传递位置参数,然后测试变量。这里,如果用户在脚本名字后键入目录名,
脚本将重设$ 1特殊变量为一更有意义的名字。即D I R E C TO RY。这里需测试目录是否为空,如
果目录为空,ls -A将返回空,然后对此返回一信息。
  1. # ifdirec.sh
  2. # assigning $1 to DIRECTORY variable
  3. DIRECTORY=$1
  4. if [ "`ls -A $DIRECTORY`" == "" ]; then
  5.         # if it's an empty string, then it's empty
  6.         echo "$DIRECTORY is indeed empty"
  7. else   
  8.         # otherwise it is not
  9.         echo "$DIRECTORY is not empty"
  10. fi
复制代码
也可以使用下面的脚本替代上面的例子并产生同样的结果。
  1. [root@localhost ~]# cat ifdirec2.sh
  2. #!/bin/sh
  3. # ifdirec2
  4. DIRECTORY=$1
  5. if [ -z "`ls -A $DIRECTORY`" ]
  6. then
  7.                 echo "$DIRECTORY is indeed empty"
  8. else
  9.                 echo "$DIRECTORY is not empty"
  10. fi
复制代码
null:命令用法
到目前为止,条件测试已经讲完了t h e n和e l s e部分,有时也许使用者并不关心条件为真或
为假。
不幸的是i f语句各部分不能为空—一些语句已经可以这样做。为解决此问题, s h e l l提供
了:空命令。空命令永远为真(也正是预想的那样)。回到前面的例子,如果目录为空,可以
只在t h e n部分加入命令。
  1. [root@localhost ~]# cat ifdirectory.sh
  2. #!/bin/sh
  3. # ifdirectory.sh
  4. DIRECTORY=$1
  5. if [ "`ls -A $DIRECTORY`" == "" ]
  6. then
  7.                 echo "$DIRECTORY is indeed empty"
  8. else :
  9.                 # do nothing
  10. fi
  11. [root@localhost ~]# ./ifdirectory.sh testd
  12. testd is indeed empty
复制代码
测试目录创建结果
现在继续讨论目录,下面的脚本接受一个参数,并用之创建目录,然后参数被传入命令
行,重设给变量D I R E C TO RY,最后测试变量是否为空。
if ["$DIRECTORY"=""]
也可以用
if[$# -lt 1]
来进行更普遍的参数测试。
如果字符串为空,返回一可用信息,脚本退出。如果目录已经存在,脚本从头至尾走一
遍,什么也没做。
创建前加入提示信息,如果键入Y或y,则创建目录,否则使用空命令表示不采取任何动作。
使用最后命令状态测试创建是否成功执行,如果失败,返回相应信息。
  1. [root@localhost ~]# cat ifmkdir.sh
  2. #!/bin/sh
  3. # ifmkdir.sh
  4. # parameter is passed as $1 but reassigned to DIRECTORY
  5. DIRECTORY=$1
  6. # is the string empty ??
  7. if [ "$DIRECTORY" == "" ]
  8. then
  9.                 echo "Usage :`basename $0` directory to create" >&2
  10.                 exit 1
  11. fi
  12. if [ -d $DIRECTORY ]
  13. then : # do nothing
  14. else
  15.                 echo "The directory does not exist"
  16.                 echo -n "Create it now? [y..n] :"
  17.                 read ANS
  18.                 if [ "$ANS" == "y" ] || [ "$ANS" == "Y" ]
  19.                 then
  20.                                 echo "creating now"
  21.                                 # create directory and send all output to /dev/null
  22.                                 mkdir $DIRECTORY > /dev/null 2>&1
  23.                                 if [ $? != 0 ]; then
  24.                                                 echo "Errors creating the directory $DIRECTORY" >&2
  25.                                                 exit 1
  26.                                 fi
  27.                 else : # do nothing
  28.                 fi
  29. fi
  30. [root@localhost ~]# ./ifmkdir.sh dt
  31. The directory does not exist
  32. Create it now? [y..n] :y
  33. creating now
复制代码
另一个拷贝实例
在另一个拷贝实例中,脚本传入两个参数(应该包含文件名),系统命令c p将$ 1拷入$ 2,
输出至/ d e v / n u l l。如果命令成功,则仍使用空命令并且不采取任何动作。
另一方面,如果失败,在脚本退出前要获知此信息。
  1. [root@localhost ~]# cat ifcp2.sh
  2. #!/bin/sh
  3. # ifcp2.sh
  4. if cp $1 $2 > /dev/null 2>&1
  5.                 # successful, great do nothing
  6. then :
  7. else
  8.                 # oh dear, show the user what files they were.
  9.                 echo "`basename $0`: ERROR failed to copy $1 to $2"
  10.                 exit 1
  11. fi
  12. [root@localhost ~]# ./ifcp2.sh myfile.lex myfile.lex.bak
  13. ifcp2.sh: ERROR failed to copy myfile.lex to myfile.lex.bak
  14. [root@localhost ~]# touch myfile.lex
  15. [root@localhost ~]# ./ifcp2.sh myfile.lex myfile.lex.bak
复制代码
上面展现了脚本运行成功和脚本运行失败的情况。

下面的脚本用s o r t命令将文件a c c o u n t s . q t r分类,并输出至系统垃圾堆。没人愿意观察屏幕
上3 0 0行的分类页。成功之后不采取任何动作。如果失败,通知用户。
  1. [root@localhost ~]# cat ifsort.sh
  2. #!/bin/sh
  3. # ifsort
  4. if sort accounts.qtr > /dev/null
  5.                 # sorted. Great
  6. then :
  7. else
  8.                 # better let the user know
  9.                 echo "`basename $0`: Oops..errors could not sort accounts.qtr"
  10. fi
复制代码
多个if语句
可能有时要嵌入i f语句。为此需注意i f和f i的相应匹配使用。

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2010-11-20 16:40 |显示全部楼层
测试和设置环境变量
前面已经举例说明了如何测试环境变量E D I TO R是否被设置。现在如果未设置,则进一步
为其赋值,脚本如下:
  1. #!/bin/sh
  2. # ifseted.sh
  3. # is the EDITOR set?
  4. if [ -z $EDITOR ]; then
  5.         echo "Your EDITOR environment is not set"
  6.         echo "I will assum you want to use vi .. OK"
  7.         echo -n "Do you wish to change it now? [y..n] :"
  8.         read ANS

  9.         # check for an upper or lower case 'y'
  10.         if [ "$ANS" == "Y" ] || [ "$ANS" == "y" ]; then
  11.                 echo "enter you  editor type :"
  12.                 read EIDTOR
  13.                 if [ -z $EDITOR ] || [ "$EDITOR" == "" ]; then
  14.                         # if EDITOR not set and no value in variable EDITOR,
  15.                         # then set it to vi
  16.                         echo "No, editor entered, using vi as default"
  17.                         EDITOR=vi
  18.                         export EDITOR
  19.                 fi
  20.                 # got a value use it for EDITOR
  21.                 EDITOR=$EDITOR
  22.                 export EDITOR
  23.                 echo "setting $EDITOR"
  24.         fi
  25. else
  26.         # user
  27.         echo "Using vi as the default editor"
  28.         EDITOR=vi
  29.         export EDITOR
  30. fi
复制代码
脚本工作方式如下:首先检查是否设置了该变量,如果已经赋值,输出信息提示使用v i作
为缺省编辑器。v i被设置为编辑器,然后脚本退出。
如果未赋值,则提示用户,询问其是否要设置该值。检验用户输入是否为大写或小写y,
输入为其他值时,脚本退出。
如果输入Y或y,再提示输入编辑类型。使用$ E D I TO R =“”测试用户是否未赋值和未点
击r e t u r n键。一种更有效的方法是使用-z $EDITO R方法,本文应用了这两种方法。如果测试
失败,返回信息到屏幕,即使用v i做缺省编辑器,因而E D I TO R赋值为v i。
如果用户输入了一个名字到变量E D I TO R,则使用它作为编辑器并马上让其起作用,即导
出变量E D I TO R。

检测最后命令状态
前面将目录名传入脚本创建了一个目录,脚本然后提示用户是否应创建目录。下面的例
子创建一个目录,并从当前目录将所有* . t x t文件拷入新目录。但是这段脚本中用最后状态命
令检测了每一个脚本是否成功执行。如果命令失败则通知用户。
  1. #!/bin/sh
  2. # ifmkdir2.sh
  3. DIR_NAME=testdirec
  4. # where are we?
  5. THRER=`pwd`
  6. # send all output to the system dustbin
  7. mkdir $DIR_NAME > /dev/null 2>&1
  8. # is it a directory ?
  9. if [ -d $DIR_NAME ]; then
  10.         # can we cd to the directory
  11.         cd $DIR_NAME
  12.         if [ $? == 0 ]; then
  13.                 # yes we can
  14.                 HERE=`pwd`
  15.                 cp $THERE/*.txt $HERE
  16.         else
  17.                 echo "Cannot cd to $DIR_NAME" >&2
  18.                 exit 1
  19.         fi
  20. else
  21.         echo "Cannnot create directory $DIR_NAME" >&2
  22.         exit 1
  23. fi
复制代码
增加和检测整数值
下面的例子进行数值测试。脚本包含了一个计数集,用户将其赋予一个新值就可改变它。
脚本然后将当前值1 0 0加入一个新值。工作流程如下:
用户输入一个新值改变其值,如果键入回车键,则不改变它,打印当前值,脚本退出。
如果用户用y或Y响应新值,将提示用户输入增量。如果键入回车键,原值仍未变。键入
一个增量,首先测试是否为数字,如果是,加入计数C O U N TO R中,最后显示新值。
  1. #!/bin/sh
  2. # ifcounter.sh
  3. COUNTER=100
  4. echo "Do you wish to change the counter value currently set at $COUNTER
  5.     [y..n] :"
  6. read ANS
  7. if [ "$ANS" == "y" ] || [ "$ANS" == "Y" ]; then
  8.         # yes user wants to change the value
  9.         echo "Enter a sensible value "
  10.         read VALUE
  11.         # simple test to see if it's numeric, add any number to VALUE,
  12.         # then check out return
  13.         # code
  14.         expr $VALUE + 10 > /dev/null 2>&1
  15.         STATUS=$?
  16.         # check return code of expr
  17.         if [ "$VALUE" == "" ] || [ "$STATUS" != "0" ]; then
  18.                 # send errors to standard error
  19.                 echo " You either entered nothing or a non-numeric " >&2
  20.                 echo " Sorry now exiting...counter stays at $COUNTER" >&2
  21.                 exit 1
  22.         fi      
  23.         # if we are here, then it's a number, so add it to COUNTER
  24.         COUNTER=`expr $COUNTER + $VALUE`
  25.         echo " Counter now set to $COUNTER"
  26. else   
  27.         # if we are here user just hit return instead of entering a number
  28.         # or anssered n to the change a value prompt
  29.         echo " Counter stays at $COUNTER"
  30. fi      
复制代码
下面是程序的运行结果:
  1. [root@localhost ~]# ./ifcounter.sh
  2. Do you wish to change the counter value currently set at 100
  3.         [y..n] :
  4. n
  5. Counter stays at 100
  6. [root@localhost ~]# ./ifcounter.sh
  7. Do you wish to change the counter value currently set at 100
  8.         [y..n] :
  9. y
  10. Enter a sensible value
  11. fdg
  12. You either entered nothing or a non-numeric
  13. Sorry now exiting...counter stays at 100
  14. [root@localhost ~]# ./ifcounter.sh
  15. Do you wish to change the counter value currently set at 100
  16.         [y..n] :
  17. y
  18. Enter a sensible value
  19. 250
  20. Counter now set to 350
复制代码

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2010-11-20 17:08 |显示全部楼层
以下是用户登录时启动应用前加入相应安全限制功能的基本框架。首先提示输入用户名
和密码,如果用户名和密码均匹配脚本中相应字符串,用户登录成功,否则用户退出。
脚本首先设置变量为假—总是假定用户输入错误, s t t y当前设置被保存,以便隐藏
p a s s w d域中字符,然后重新保存s t t y设置。
如果用户I D和密码正确(密码是easypasswd),明亮I N VA L I D U S E R和I N VA L I D PA S S W D设
置为n o表示有效用户或密码,然后执行测试,如果两个变量其中之一为y e s,缺省情况下,脚
本退出用户。
键入有效的I D和密码,用户将允许进入。这是一种登录脚本的基本框架。下面的例子中
有效用户I D为root。
提示:在实际中千万不要这么做,任何人都知道用root身份进行测试是很危险的。
  1. #!/bin/sh
  2. # ifpass.sh
  3. # set the variables to false
  4. INVALID_USER=yes
  5. INVALID_PASSWD=yes
  6. # save the current stty settings
  7. SAVEDSTTY=`stty -g`
  8. echo "You are logging into a sensitive area"
  9. echo -n "Enter your ID name :"
  10. read NAME
  11. # hide the characters typed in
  12. stty -echo
  13. echo "Enter your password :"
  14. read PASSWORD
  15. # back on again
  16. stty $SAVEDSTTY
  17. if [ "$NAME" == "root" ]; then
  18.         # if a valid then set variable
  19.         INVALID_USER=no
  20. fi      
  21. if [ "$PASSWORD" == "easypasswd" ]; then
  22.         # if valid password then set variable
  23.         INVALID_PASSWD=no
  24. fi      
  25. if [ "$INVALID_USER" == "yes" -o "$INVALID_PASSWD" == "yes" ]; then
  26.         echo "`basename $0 :` Sorry wrong password or userid"
  27.         exit 1
  28. fi      
  29. # if we get here then their ID and password are OK.
  30. echo "correct user id an password given"
复制代码
下面是对应两种不同情况的输出结果。
  1. [root@localhost ~]# ./ifpass.sh
  2. You are logging into a sensitive area
  3. Enter your ID name :root
  4. Enter your password :
  5. correct user id an password given
  6. [root@localhost ~]# ./ifpass.sh
  7. You are logging into a sensitive area
  8. Enter your ID name :root
  9. Enter your password :
  10. ifpass.sh Sorry wrong password or userid
复制代码
elif用法
if then else语句的e l i f部分用于测试两个以上的条件。

使用elif进行多条件检测
使用一个简单的例子,测试输入脚本的用户名。脚本首先测试是否输入一个名字,如果
没有,则什么也不做。如果输入了,则用e l i f测试是否匹配r o o t、l o u i s e或d a v e,如果不匹配其
中任何一个,则打印该名字,通知用户不是r o o t、l o u i s e或d a v e。
  1. #!/bin/sh
  2. # ifelif.sh
  3. echo  -n "enter your login name :"
  4. read NAME
  5. # no name entered do not carry on
  6. if [ -z $NAME ] || [ "$NAME" == "" ]; then
  7.         echo "You did not enter a name"
  8. elif   
  9.         # is the name root
  10.         [ "$NAME" == "root" ]; then
  11.         echo "Hello root"
  12. elif   
  13.         # or is it louise
  14.         [ $NAME == "louise" ]; then
  15.         echo "Hello louise"
  16. elif   
  17.         # or is it dave
  18.         [ "$NAME" == "dave" ]; then
  19.         echo "Hello dave"
  20. else   
  21.         # no it's somebody else
  22.         echo "You are not root or louise or dave but hi $NAME"
  23. fi
  24. 运行上述脚本,给出不同信息,得结果如下:                              
  25. [root@localhost ~]# chmod +x ifelif.sh
  26. [root@localhost ~]# ./ifelif.sh
  27. enter your login name :dave
  28. Hello dave
  29. [root@localhost ~]# ./ifelif.sh
  30. enter your login name :
  31. You did not enter a name
  32. [root@localhost ~]# ./ifelif.sh
  33. enter your login name :Peter
  34. You are not root or louise or dave but hi Peter
复制代码
多文件位置检测
假定要定位一个用户登录文件,已知此文件在/ u s r / o p t s / a u d i t / l o g s或/ u s r / l o c a l / a u d i t / l o g s中,
具体由其安装人决定。在定位此文件前,首先确保文件可读,此即脚本测试部分。如果未找
到文件或文件不可读,则返回错误信息。脚本如下:
  1. #!/bin/sh
  2. # ifcataudit.sh
  3. # locations of the log file
  4. LOCAT_1=/usr/opts/audit/logs/audit.log
  5. LOCAT_2=/usr/local/audit/audit.logs
  6. if [ -r $LOCAT_1 ]; then
  7.         # if it is in this directory and is readable then cat is
  8.         echo "Using LOCAT_1"
  9.         cat $LOCAT_1
  10. elif   
  11.         # else it then must be in this direcotory, and is it readable
  12.         [ -r $LOCAT_2 ]
  13. then   
  14.         echo "Using LOCAT_2"
  15.         cat $LOCAT_2
  16. else   
  17.         # not in any of the directories...
  18.         echo "`basename $0`: Sorry the audit file is not readable or cannot be
  19.         localted." >&2
  20.         exit 1
  21. fi      
复制代码
运行上面脚本,如果文件在上述两个目录之一中并且可读,将可以找到它。如果不是,
返回错误并退出,下面结果失败,因为假想的文件并不存在
  1. [root@localhost ~]# ./ifcataudit.sh
  2. ifcataudit.sh: Sorry the audit file is not readable or cannot be
  3.                 localted.
复制代码

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2010-11-20 19:24 |显示全部楼层
提示键入y或n
c a s e的一个有效用法是提示用户响应以决定是否继续进程。这里提示输入y以继续处理,n
退出。如果用户输入Y、y或y e s,处理继续执行c a s e语句后面部分。如果用户输入N、n或n o或
其他响应,用户退出脚本。
  1. #!/bin/sh
  2. # caseans.sh
  3. echo -n "Do you wish to proceed [y..n] :"
  4. read ANS
  5. case $ANS in
  6.         y|Y|yes|Yes) echo "yes is selected"
  7.         ;;
  8.         n|N) echo "no is selected"
  9.         exit 0  # no error so only use exit 0 to terminate
  10.         ;;
  11.         *) echo "`basename $0` : Unknow response" >&2
  12.         exit 1
  13.         ;;
  14. esac   
  15. # if we are here then a y|Y|yes|Yes was selected only.
复制代码
运行脚本,输入无效响应,得结果:
  1. [root@localhost ~]# ./caseans.sh
  2. Do you wish to proceed [y..n] :df
  3. caseans.sh : Unknow response
复制代码
给出有效响应:
  1. [root@localhost ~]# ./caseans.sh
  2. Do you wish to proceed [y..n] :y
  3. yes is selected
复制代码
case与命令参数传递
可以使用c a s e控制到脚本的参数传递。
下面脚本中,测试特定变量$ #,它包含传递的参数个数,如果不等于1,退出并显示可用
信息。
然后c a s e语句捕获下列参数: p a s s w d、s t a r t、s t o p或h e l p,相对于每一种匹配模式执行进
一步处理脚本。如果均不匹配,显示可用信息到标准错误输出。

  1. #!/bin/sh
  2. # caseparam.sh
  3. if [ $# != 1 ]; then
  4.         echo "Usage:`basename $0`[start|stop|help]" >&2
  5.         exit 1
  6. fi      
  7. # assign the parameter to the variable OPT
  8. OPT=$1
  9. case $OPT in
  10.         start) echo "starting.. `basename $0`"
  11.         # code here to start a process
  12.         ;;
  13.         stop) echo "stopping.. `basename $0`"
  14.         # code here to stop a process
  15.         ;;
  16.         help)
  17.         # code here to display a help page
  18.         ;;
  19.         *) echo "Usage:`basename $0`[start|stop|help]"
  20.         ;;
  21. esac   
复制代码
运行脚本,输入无效参数。
  1. [root@localhost ~]# ./caseparam.sh what
  2. Usage:caseparam.sh[start|stop|help]
复制代码
输入有效参数,结果为:
  1. [root@localhost ~]# ./caseparam.sh stop
  2. stopping.. caseparam.sh
复制代码
捕获输入并执行空命令
不一定要在匹配模式后加入命令,如果你原本不想做什么,只是在进一步处理前过滤出
意外响应,这样做是一种好办法。
如果要运行对应于一个会计部门的帐目报表,必须首先在决定运行报表的类型前确认用
户输入一个有效的部门号,匹配所有可能值,其他值无效。用c a s e可以很容易实现上述功能。
下面的脚本中如果用户输入部门号不是2 3 4、4 5 3、6 5 5或4 5 4,用户退出并返回可用信息。
一旦响应了用户的有效部门号,脚本应用同样的技术取得报表类型,在c a s e语句末尾显示有
效的部门号和报表类型。脚本如下:
  1. #!/bin/sh
  2. # casevaild.sh
  3. echo -n "Enter the account dept No: :"
  4. read ACC
  5. case $ACC in
  6.         234);;
  7.         453);;
  8.         655);;
  9.         454);;
  10.         *) echo "`basename $0`: Unknown dept No:" >&2
  11.         echo "try ... 234,453,655,454"
  12.         exit 1
  13.         ;;
  14. esac
  15. # if we are here, then we have validated the dept no
  16. echo "1 . post"
  17. echo "2 . prior"
  18. echo -n "Enter the type of report:"
  19. read ACC_TYPE
  20. case $ACC_TYPE in
  21.         1) TYPE=post
  22.         ;;
  23.         2) TYPE=prior;;
  24.         *) echo "`basename $0`: Unknown account type." >&2
  25.         exit 1
  26.         ;;
  27. esac
  28. # if we are here the we are validated!
  29. echo "now running report for dept $ACC for the type $TYPE"
  30. # run the command reprot...
复制代码
下面是该脚本不同运行输入情况的显示结果。
输入有效部门号:
  1. [root@localhost ~]# ./casevalid.sh
  2. Enter the account dept No: :234
  3. 1 . post
  4. 2 . prior
  5. Enter the type of report:2
  6. now running report for dept 234 for the type prior
复制代码
输入无效部门号:
  1. [root@localhost ~]# ./casevalid.sh
  2. Enter the account dept No: :432
  3. casevalid.sh: Unknown dept No:
  4. try ... 234,453,655,454
复制代码
输入无效的报表类型:
  1. [root@localhost ~]# ./casevalid.sh
  2. Enter the account dept No: :655
  3. 1 . post
  4. 2 . prior
  5. Enter the type of report:4
  6. casevalid.sh: Unknown account type.
复制代码
缺省变量值
如果在读变量时输入回车键,不一定总是退出脚本。可以先测试是否已设置了变量,如
果未设置,可以设置该值。
下面的脚本中,要求用户输入运行报表日期。如果用户输入回车键,则使用缺省日期星
期六,并设置为变量w h e n的取值。
如果用户输入另外一天,这一天对于c a s e语句是运行的有效日期,即星期六、星期四、星
期一。注意,这里结合使用了日期缩写作为捕获的可能有效日期。
脚本如下:
  1. #!/bin/sh
  2. # caserep.sh
  3. echo "      Weekly Report"
  4. echo -n "What day do you want to run report [Saturday] :"
  5. # if just a return is hit then except default which is Saturday
  6. read WHEN
  7. echo "validating .. ${WHEN:="Saturday"}"
  8. case $WHEN in
  9.         Monday|MONDAY|mon)
  10.         ;;
  11.         Sunday|SUNDAY|sun)
  12.         ;;
  13.         Saturday|SATURDAY|sat)
  14.         ;;
  15.         *)
  16.         echo "Are you nuts! this report can only be run on " >&2
  17.         echo " on a Saturday, Sunday or Monday" >&2
  18.         exit 1
  19.         ;;
  20. esac   
  21. echo "Report to run on $WHEN"
  22. # command here to submitted actual report run
复制代码
对于正确输入:
  1. [root@localhost ~]# ./caserep.sh
  2.                 Weekly Report
  3. What day do you want to run report [Saturday] :
  4. validating .. Saturday
  5. Report to run on Saturday
复制代码
对于错误的输入:
  1. [root@localhost ~]# ./caserep.sh
  2.                 Weekly Report
  3. What day do you want to run report [Saturday] :Tuesday
  4. validating .. Tuesday
  5. Are you nuts! this report can only be run on
  6. on a Saturday, Sunday or Monday
复制代码
可以推断出c a s e语句有时与if then else 语句功能相同,在某些条件下,这种假定是正确
的。

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2010-11-20 20:19 |显示全部楼层
f o r循环一般格式为:
for 变量名i n列表
d o
命令1
命令2⋯
d o n e

当变量值在列表里, f o r循环即执行一次所有命令,使用变量名访问列表中取值。命令可
为任何有效的s h e l l命令和语句。变量名为任何单词。I n列表用法是可选的,如果不用它, f o r
循环使用命令行的位置参数。
i n列表可以包含替换、字符串和文件名,下面看一些例子。

还有一种常见的for循环的格式是:
for ((初值; 循环条件; 执行步长))
do
            执行的程序段
done
也就是括号中的内容是平常我们熟悉的C语言的风格。


简单的for循环
此例仅显示列表1 2 3 4 5,用变量名访问列表。
  1. #!/bin/sh
  2. # for_i.sh
  3. for loop in 1 2 3 4 5
  4. do
  5.         echo $loop
  6. done
复制代码
运行上述脚本,输出:
  1. [root@localhost ~]# ./for_i.sh
  2. 1
  3. 2
  4. 3
  5. 4
  6. 5
复制代码
上面中的1 2 3 4 5序列也可以用下面的方式生成。seq 5或者echo {1..5}的方式。


打印字符串列表
下面f o r循环中,列表包含字符串“ orange red blue grey”,命令为e c h o,变量名为l o o p,
e c h o命令使用$ l o o p反馈出列表中所有取值,直至列表为空。
  1. #!/bin/sh
  2. # forlist.sh
  3. for loop in "orange red blue grey"
  4. do
  5.         echo "$loop"
  6. done
复制代码
运行脚本输出内容是:
orange red blue grey
也可以在循环体中结合使用变量名和字符串。
This is the fruit $loop
其输出结果是:
This is the fruit orange red blue grey
对for循环使用ls命令
这个循环执行l s命令,打印当前目录下所有文件。
  1. #!/bin/sh
  2. # forls.sh
  3. for loop in `ls `
  4. do
  5.         echo $loop
  6. done
复制代码
对for循环使用参数
在f o r循环中省去i n列表选项时,它将接受命令行位置参数作为参数。实际上即指明:
for params in"$@"

for params in"$*"
下面的例子不使用i n列表选项, f o r循环查看特定参数$ @或$ *,以从命令行中取得参数。
  1. #!/bin/sh
  2. # forparam2
  3. for params
  4. do
  5.         echo "You supplied $params as a command line option"
  6. done
复制代码
下面的脚本包含i n"$ @",结果与上面的脚本相同。
  1. #!/bin/sh
  2. # forparam3
  3. for params in "$@"
  4. do
  5.         echo "You supplied $params as a command line option"
  6. done
复制代码
对上述脚本采取进一步动作。如果要查看一系列文件,可在f o r循环里使用f i n d命令,利
用命令行参数,传递所有要查阅的文件。
  1. #!/bin/sh
  2. # forfind.sh
  3. for loop
  4. do
  5.         find / -name $loop -print
  6. done
复制代码
脚本执行时,从命令行参数中取值并使用f i n d命令,这些取值形成- n a m e选项的参数值。

使用for循环连接服务器
因为f o r循环可以处理列表中的取值,现设变量为网络服务器名称,并使用f o r循环连接每
一服务器。
  1. #!/bin/sh
  2. # forping.sh
  3. HOSTS="itserv dnssevr acctsmain ladpd ladwareA"
  4. for loop in $HOSTS
  5. do
  6.         ping -c 2 $loop
  7. done
复制代码
使用for循环备份文件
可以用f o r循环备份所有文件,只需将变量作为c p命令的目标参数。这里有一变量. b a k,
当在循环中使用c p命令时,它作为此命令目标文件名。列表命令为l s。
  1. #!/bin/sh
  2. # forbak.sh
  3. suffix=".bak"
  4. for loop in `ls `
  5. do
  6.         cp $loop $loop$suffix
  7. done
复制代码
多文件转换
匹配所有以L P S O开头文件并将其转换为大写。这里使用了l s和c a t命令。l s用于查询出相
关文件, c a t用于将之管道输出至t r命令。目标文件扩展名为.U C,注意在f o r循环中使用l s命令
时反引号的用法。
  1. #!/bin/sh
  2. # forUC.sh
  3. for files in `ls LPSO*`
  4. do
  5.         cat $files | tr "[a-z]" "[A-Z]" > $file.UC
  6. done
复制代码
多sed删除操作
下面的例子中, s e d用于删除所有空文件,并将输出导至以. H O L D . m v为扩展名的新文件
中,m v将这些文件移至初始文件中。
  1. #!/bin/sh
  2. # forsed.sh
  3. for files in `ls LPSO*`
  4. do
  5.         sed -e "/^$/d" $files >$files.HOLD
  6.         mv $files.HOLD $files
  7. done
复制代码
循环计数
前面讨论e x p r时指出,循环时如果要加入计数,使用此命令。下面使用l s在f o r循环中列出
文件及其数目。
  1. #!/bin/sh
  2. # forcount.sh
  3. counter=0
  4. for files in *
  5. do
  6.         # increment
  7.         counter=`expr $counter + 1`
  8. done
  9. echo "There are $counter files in `pwd` we need to process"
复制代码
脚本的输出结果是:
There are 87 files in /root we need to process
使用w c命令可得相同结果。
[root@localhost ~]# ls | wc -l
87

for循环和本地文档
在f o r循环体中可使用任意命令。下面的例子中,一个变量包含所有当前登录用户。使用
w h o命令并结合a w k语言可实现此功能。然后f o r循环循环每一用户,给其发送一个邮件,邮件
信息部分用一个本地文档完成。
  1. #!/bin/sh
  2. # formailit.sh
  3. WHOS_ON=`who -u | awk '{print $1}'`
  4. for user in $WHOS_ON
  5. do
  6.         mail $user << MAYDAY
  7.         Dear Colleagues,
  8.         It's my birthday today, see you down the
  9.         club at 17:30 for a drink.

  10.         See ya.
  11.         $LOGNAME
  12.         MAYDAY
  13.         Done
复制代码
这个例子无法实现,可能原书上有错误,或者翻译有错误。


for循环嵌入
嵌入循环可以将一个f o r循环嵌在另一个f o r循环内:
for 变量名1 in列表1
d o
for 变量名2 in 列表2
d o
命令1
. . .
d o n e
d o n e
下面脚本即为嵌入f o r循环,这里有两个列表A P P S和S C R I P T S。第一个包含服务器上应用
的路径,第二个为运行在每个应用上的管理脚本。对列表A P P S上的每一个应用,列表
S C R I P T S里的脚本将被运行,脚本实际上为后台运行。脚本使用t e e命令在登录文件上放一条
目,因此输出到屏幕的同时也输出到一个文件。查看输出结果就可以看出嵌入f o r循环怎样使
用列表S C R I P T S以执行列表A P P S上的处理。
  1. #!/bin/sh
  2. # audit_run.sh
  3. APPS="/apps/accts /apps/claims /apps/stock /apps/serv"
  4. SCRIPTS="audit.check report.run cleanup"
  5. LOGFILE=audit.log
  6. MY_DATE=`date +%H:%M" on "%d/%m%Y`
  7. # outer loop
  8. for loop in $APPS
  9. do      
  10.         # inner loop
  11.         for loop2 in $SCRIPTS
  12.         do      
  13.                 echo "system $loop now running $loop2 at $MY_DATE" | tee -a\
  14.                  $LOGFILE $loop $loop2 &
  15.          done   
  16. done   
复制代码
程序的运行结果如下:
  1. tee: /apps/accts: No such file or directory
  2. tee: /apps/accts: No such file or directory
  3. system /apps/accts now running report.run at 20:18 on 21/112010
  4. tee: /apps/accts: No such file or directory
  5. system /apps/accts now running cleanup at 20:18 on 21/112010
  6. tee: /apps/claims: No such file or directory
  7. system /apps/claims now running audit.check at 20:18 on 21/112010
  8. tee: /apps/claims: No such file or directory
  9. system /apps/claims now running report.run at 20:18 on 21/112010
  10. tee: /apps/claims: No such file or directory
  11. system /apps/claims now running cleanup at 20:18 on 21/112010
  12. tee: /apps/stock: No such file or directory
  13. system /apps/stock now running audit.check at 20:18 on 21/112010
  14. tee: /apps/stock: No such file or directory
  15. system /apps/stock now running report.run at 20:18 on 21/112010
  16. tee: /apps/stock: No such file or directory
  17. system /apps/stock now running cleanup at 20:18 on 21/112010
  18. tee: /apps/serv: No such file or directory
  19. system /apps/serv now running audit.check at 20:18 on 21/112010
  20. [root@localhost ~]# system /apps/accts now running audit.check at 20:18 on 21/112010
  21. tee: /apps/serv: No such file or directory
  22. system /apps/serv now running report.run at 20:18 on 21/112010
  23. tee: /apps/serv: No such file or directory
  24. system /apps/serv now running cleanup at 20:18 on 21/112010
复制代码

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2010-11-20 20:27 |显示全部楼层
until循环
u n t i l循环执行一系列命令直至条件为真时停止。u n t i l循环与w h i l e循环在处理方式上刚好
相反。一般w h i l e循环优于u n t i l循环,但在某些时候—也只是极少数情况下, u n t i l循环更加
有用。
u n t i l循环格式为:
until 条件
命令1
. . .
d o n e
条件可为任意测试条件,测试发生在循环末尾,因此循环至少执行一次—请注意这一
点。
下面是一些实例。
简单的until循环
这段脚本不断的搜寻w h o命令中用户r o o t,变量I S - R O O T保存g r e p命令结果。
如果找到了r o o t,循环结束,并向用户s i m o n发送邮件,通知他用户r o o t已经登录,注意
这里s l e e p命令用法,它经常用于u n t i l循环中,因为必须让循环体内命令睡眠几秒钟再执行,
否则会消耗大量系统资源。
  1. #!/bin/sh
  2. # until_who.sh
  3. IS_ROOT=`who | grep root`
  4. until [ "$IS_ROOT" ]
  5. do
  6.         sleep 5
  7. done   
  8. echo "Watch it. roots in " | mail simon
复制代码
监视文件
下面例子中, u n t i l循环不断挂起做睡眠,直至文件/ t m p / m o n i t o r. l c k被删除。文件删除后,
脚本进入正常处理过程。
  1. #!/bin/sh
  2. # until_lck.sh
  3. LOCK_FILE=/tmp/process.LCK
  4. until [ ! -f $LOCK_FILE ]
  5. do
  6.         sleep 1
  7. done   
  8. echo "file deleted "
  9. # normal processing now, file is present
复制代码
上述例子是使脚本与其他处理过程协调工作的一种方法。还有另外一种方法使脚本间互
相通信。假定有另一段脚本p r o c e s s . m a i n用于搜集本地网络所有机器的信息并将之放入一个报
表文件。
当脚本p r o c e s s . m a i n运行时,创建了一个L C K文件(锁文件),上面脚本必须接收
p r o c e s s . m a i n搜集的信息,但是如果p r o c e s s仍然在修改报表文件时试图处理该文件就不太好
了。
为克服这些问题,脚本p r o c e s s . m a i n创建了一个L C K文件,当它完成时,就删除此文件。
上述脚本将挂起,等待L C K文件被删除,一旦L C K文件删除,上述脚本即可处理报表文
件。

监视磁盘空间
u n t i l循环做监视条件也很有用。假定要监视文件系统容量,当它达到一定水平时通知超
级用户。
下面的脚本监视文件系统/ l o g s,不断从变量$L O O K_O U T中抽取信息, $ L O O K _ O U T包
含使用a w k和g r e p得到的/ l o g s容量。
如果容量达到9 0 %,触发命令部分,向超级用户发送邮件,脚本退出。必须退出,如果
不退出,条件保持为真(例如,容量总是保持在9 0 %以上),将会不断的向超级用户发送邮
件。
  1. #!/bin/sh
  2. # until_mon.sh
  3. # get present column and strip off header row from df
  4. LOOK_OUT=`df | grep /logs | awk '{print $5}' | sed 's/%//g'`
  5. echo $LOOK_OUT
  6. until [ "$LOOK_OUT -gt "90" ]
  7. do
  8.         echo "Filesystem..logs is nearly full" | mail root
  9.         exit 0
  10. done
复制代码

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2010-11-20 21:24 |显示全部楼层
while循环
w h i l e循环用于不断执行一系列命令,也用于从输入文件中读取数据,其格式为:
while 命令
d o
命令1
命令2
. . .
d o n e
虽然通常只使用一个命令,但在w h i l e和d o之间可以放几个命令。命令通常用作测试条
件。
只有当命令的退出状态为0时,d o和d o n e之间命令才被执行,如果退出状态不是0,则循
环终止。
命令执行完毕,控制返回循环顶部,从头开始直至测试条件为假。

简单的while循环
以下是一个基本的w h i l e循环,测试条件是:如果C O U N T E R小于5,那么条件返回真。
C O U N T E R从0开始,每次循环处理时, C O U N T E R加1。
  1. #!/bin/sh
  2. # whilecount.sh
  3. COUNTER=0
  4. # does the counter = 5 ?
  5. while [ $COUNTER -lt 5 ]
  6. do
  7.         # add ono to the counter
  8.         COUNTER=`expr $COUNTER + 1`
  9.         echo $COUNTER
  10. done
复制代码
运行上述脚本,返回数字1到5,然后终止。

使用while循环读键盘输入
w h i l e循环可用于读取键盘信息。下面的例子中,输入信息被设置为变量F I L M,按< C t r l -
D >结束循环。
  1. #!/bin/sh
  2. # whileread.sh
  3. echo "type <CTRL-D> to terminate"
  4. echo -n "Enter your most liked file :"
  5. while read FILE
  6. do
  7.         echo "Yeah, great film the $FILE"
  8. done
复制代码
程序的运行结果如下:
type <CTRL-D> to terminate
Enter your most liked file :Sound of Music
Yeah, great film the Sound of Music
<CTRL-D>

用while循环从文件中读取数据
w h i l e循环最常用于从一个文件中读取数据,因此编写脚本可以处理这样的信息。
假定要从下面包含雇员名字、从属部门及其I D号的一个文件中读取信息。names.txt
Louise Conrad:Accounts:ACC8987
Peter JamesayrollR489
Fred Terms:Customer:CUS012
James Lenod:Accounts:ACC887
Frank PavelyayrollR489

可以用一个变量保存每行数据,当不再有读取数据时条件为真。w h i l e循环使用输入重定
向以保证从文件中读取数据。注意整行数据被设置为单变量$ L I N E .。
  1. #!/bin/sh
  2. # whileread.sh
  3. while read LINE
  4. do
  5.         echo $LINE
  6. done < names.txt
复制代码
程序的运行结果如下:
  1. [root@localhost ~]# ./whileread.sh
  2. Louise Conrad:Accounts:ACC8987
  3. Peter James:Payroll:PR489
  4. Fred Terms:Customer:CUS012
  5. James Lenod:Accounts:ACC887
  6. Frank Pavely:Payroll:PR489
复制代码
使用IFS读文件
输出时要去除冒号域分隔符,可使用变量I F S。在改变它之前保存I F S的当前设置。然后
在脚本执行完后恢复此设置。使用I F S可以将域分隔符改为冒号而不是空格或t a b键。这里有3
个域需要加域分隔,即N A M E、D E P T和I D。
为使输出看起来更清晰,对e c h o命令使用t a b键将域分隔得更开一些,脚本如下:
  1. #!/bin/sh
  2. # whilereadifs
  3. # save the setting of IFS
  4. SAVEDIFS=$IFS
  5. # assign new separator to IFS
  6. IFS=:
  7. while read NAME DEPT ID
  8. do
  9.         echo -e "$NAME\t $DEPT\t $ID"
  10. done < names.txt
  11. # restore the settings of IFS
  12. IFS=$SAVEDIFS
复制代码
脚本运行后的输出结果如下,结果清晰多了。
Louise Conrad    Accounts        ACC8987
Peter James      Payroll         PR489
Fred Terms       Customer        CUS012
James Lenod      Accounts        ACC887
Frank Pavely     Payroll         PR489


带有测试条件的文件处理
大部分w h i l e循环里都带有一些测试语句,以决定下一步的动作。
这里从人员文件中读取数据,打印所有细节到一个保留文件中,直至发现James Lenod,
脚本退出。测试前反馈的信息要确保“ James Lenod”加入保留文件中。
注意,所有变量在脚本顶端被设置完毕。这样当不得不对变量进行改动时可以节省时间
和输入。所有编辑都放在脚本顶端,而不是混于整个脚本间。
  1. #!/bin/sh
  2. # whileread_file.sh
  3. # initialise variables
  4. SAVEDIFS=$IFS
  5. IFS=:
  6. HOLD_FILE=hold_file
  7. NAME_MATCH="James Lenod"
  8. INPUT_FILE=names.txt

  9. # create a new HOLD_FILE each time, in case script is continuously run
  10. >$HOLD_FILE
  11. while read NAME DEPT ID
  12. do
  13.         # echo all information into holdfile with redirection
  14.         echo $NAME $DEPT $ID >> $HOLD_FILE
  15.         # is it a match ???
  16.         if [ "$NAME" == "$NAME_MATCH" ]; then
  17.                 # yes then nice exit
  18.                 echo "all entries up to and including $NAME_MATCH are in $HOLD_F
  19. ILE"
  20.                 exit 0
  21.         fi      
  22. done < $INPUT_FILE
  23. # restore IFS
  24. IFS=$SAVEDIFS
复制代码
还可以采取进一步动作,列出多少个雇员属于同一部门。这里保持同样的读方式。假定
每个域都有一个变量名,然后在c a s e语句里用e x p r增加每行匹配脚本。任何发现的未知部门
知识反馈到标准错误中,如果一个无效部门出现,没有必要退出。
  1. #!/bin/sh
  2. # whileread_cond.sh
  3. # initialise variables
  4. ACC_LOOP=0; CUS_LOOP=0; PAY_LOOP=0;
  5. SAVEDIFS=$IFS
  6. IFS=:
  7. while read NAME DEPT ID
  8. do
  9.         # increment counter for each matched dept.
  10.         case $DEPT in
  11.                 Accounts) ACC_LOOP=`expr $ACC_LOOP + 1`
  12.                 ACC="Accounts"
  13.                 ;;
  14.                 Customer) CUS_LOOP=`expr $CUS_LOOP + 1`
  15.                 CUS="Customer"
  16.                 ;;
  17.                 Payroll) PAY_LOOP=`expr $PAY_LOOP + 1`
  18.                 PAY="Payroll"
  19.                 ;;
  20.                 *) echo "`basename $0`: Unknown department $DEPT" >&2
  21.                 ;;
  22.         esac
  23. done < names.txt
  24. IFS=$SAVEDIFS
  25. echo "there are $ACC_LOOP employees assigned to $ACC dept"
  26. echo "there are $CUS_LOOP employees assigned to $CUS dept"
  27. echo "there are $PAY_LOOP employees assigned to $PAY dept"
复制代码
程序运行的结果如下所示:
  1. [root@localhost ~]# ./whileread_cond.sh   
  2. there are 2 employees assigned to Accounts dept
  3. there are 1 employees assigned to Customer dept
  4. there are 2 employees assigned to Payroll dept
  5. [root@localhost ~]# vim whileread_cond.sh
复制代码
扫描文件行来进行数目统计
一个常用的任务是读一个文件,统计包含某些数值列的数值总和。下面的文件包含有部
门S TAT和G I F T所卖的商品数量。
  1. [root@localhost ~]# cat total.txt
  2. STAT    3444
  3. GIFT     233
  4. GIFT     252
  5. GIFT     932
  6. STAT     212
  7. STAT     923
  8. GIFT     129
复制代码
现在的任务是要统计部门G I F T所卖的各种商品数量。使用e x p r保存统计和,看下面的
e x p r语句。变量L O O P和TO TA L首先在循环外初始化为0,循环开始后, I T E M S加入TO TA L,
第一次循环只包含第一种商品,但随着过程继续, I T E M S逐渐加入TO TA L。
下面的e x p r语句不断增加计数。
LOOP=0
TOTAL=0
...
while ...
do
TOTAL=`expr $TOTAL + $ITEMS`
ITEMS=`expr $ITEMS + 1`
done

使用e x p r语句时容易犯的一个错误是开始忘记初始化变量。
LOOP=0
TOTAL=0
如果真的忘了初始化,屏幕上将布满e x p r错误。
如果愿意,可以在循环内初始化循环变量。
TOTAL=`expr ${TOAL:=0} + ${ITEMS}`
上面一行如果变量TO TA L未赋值,将其初始化为0。这是在e x p r里初始化变量的第一个例
子。另外在循环外要打印出最后总数。
  1. #!/bin/sh
  2. # total.sh
  3. # init variables
  4. LOOP=0
  5. TOTAL=0
  6. COUNT=0
  7. echo "Items Dept"
  8. echo "____________"
  9. while read DEPT ITEMS
  10. do
  11.         # keep a count on total records read
  12.         COUNT=`expr $COUNT + 1`
  13.         if [ "$DEPT" == "GIFT" ]; then
  14.                 # keep a running total
  15.                 TOTAL=`expr $TOTAL + $ITEMS`
  16.                 ITEMS=`expr $ITEMS + 1`
  17.                 echo -e "$ITEMS\t$DEPT"
  18.         fi      
  19.         # echo $DEPT $ITEMS
  20. done < total.txt
  21. echo "============"
  22. echo $TOTAL
  23. echo "There were $COUNT entries altogether in the file"
复制代码
脚本的运行结果如下所示:
  1. Items Dept
  2. ____________
  3. 234     GIFT
  4. 253     GIFT
  5. 933     GIFT
  6. 130     GIFT
  7. ============
  8. 1546
  9. There were 7 entries altogether in the file
复制代码
每次读一对记录
有时可能希望每次处理两个记录,也许可从记录中进行不同域的比较。每次读两个记录
很容易,就是要在第一个w h i l e语句之后将第二个读语句放在其后。使用这项技术时,不要忘
了不断进行检查,因为它实际上读了大量的记录。
[root@localhost ~]# cat record.txt
record 1
record 2
record 3
record 4
record 5
record 6
每次读两个记录,下面的例子对记录并不做实际测试。
脚本如下:
  1. #!/bin/sh
  2. # readpair.sh
  3. # first record
  4. while read rec1
  5. do
  6.         # second record
  7.         read rec2
  8.         # further processing/testing goes here to test or compare both records
  9.         echo "This is record on of a pair :$rec1"
  10.         echo "This is record on of a pair :$rec2"
  11.         echo "----------------------------"
  12. done < record.txt
复制代码
首先来检查确实读了很多记录,可以使用w c命令:
[root@localhost ~]# cat record.txt | wc -l
6
共有6个记录,观察其输出:
This is record on of a pair :record 1
This is record on of a pair :record 2
----------------------------
This is record on of a pair :record 3
This is record on of a pair :record 4
----------------------------
This is record on of a pair :record 5
This is record on of a pair :record 6
----------------------------

忽略#字符
读文本文件时,可能要忽略或丢弃遇到的注释行,下面是一个典型的例子。
假定要使用一般的w h i l e循环读一个配置文件,可拣选每一行,大部分都是实际操作语句。
有时必须忽略以一定字符开头的行,这时需要用c a s e语句,因为#是一个特殊字符,最好首先
用反斜线屏蔽其特殊意义,在#符号后放一个星号*,指定*后可包含任意字符。
配置文件如下:
  1. [root@localhost ~]# cat config
  2. # THIS IS THE SUB SYSTEM AUDIT CONFIG FILE
  3. # DO NOT EDIT!!!!.IT WORKS
  4. #
  5. # type of admin access
  6. AUDITSCH=full
  7. # lanuch place of sub-systems
  8. AUDITSUB=/usr/opt/audit/sub
  9. # serial hash number of product
  10. HASHSER=12890AB3
  11. # END OF CONFIG FILE!!!
复制代码
忽略#符号的实现脚本如下:
  1. #!/bin/sh
  2. # ignore_hash.sh
  3. INPUT_FILE=config
  4. if [ -s $INPUT_FILE ]; then
  5.         while read LINE
  6.         do      
  7.                 case $LINE in
  8.                         \#*);;
  9.                         # ignore any hash signs
  10.                         *) echo $LINE
  11.                         ;;
  12.                 esac   
  13.         done < $INPUT_FILE
  14. else   
  15.         echo "`basename $0` : Sorry $INPUT_FILE does not exist or is empty"
  16.         exit 1
  17. fi      
复制代码
程序的输出结果如下:
AUDITSCH=full
AUDITSUB=/usr/opt/audit/sub
HASHSER=12890AB3

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2010-11-20 21:59 |显示全部楼层
处理格式化报表
读报表文件时,一个常用任务是将不想要的行剔除。以下是库存商品水平列表,我们感
兴趣的是那些包含商品记录当前水平的列
  1. [root@localhost ~]# cat order
  2. ################ RE-ORDER REPORT  ##############
  3. ITEM           ORDERLEVEL              LEVEL
  4. ####################################################
  5. Pens            14                              12
  6. Pencils         15                              15
  7. Pads             7                               3
  8. Disks            3                               2
  9. Sharpeners       5                               1
  10. ####################################################
复制代码
我们的任务是读取其中取值,决定哪些商品应重排。如果重排,重排水平应为现在商品
的两倍。输出应打印需要重排的每种商品数量及重排总数。
我们已经知道可以忽略以某些字符开始的行,因此这里没有问题。首先读文件,忽略所
有注释行和以‘ I T E M’开始的标注行。读取文件至一临时工作文件中,为确保不存在空行,
用s e d删除空行,需要真正做的是过滤文本文件。脚本如下:
  1. #!/bin/sh
  2. # whileorder.sh
  3. INPUT_FILE=order
  4. HOLD=order.tmp
  5. if [ -s $INPUT_FILE ]; then
  6.         # zero the output file, we do not want to append!
  7.         >$HOLD
  8.         while read LINE
  9.         do      
  10.                 case $LINE in
  11.                         \#*|ITEM*);; # ignore any # or the line with ITEM
  12.                         *)
  13.                         # redirect the output to a temp file
  14.                         echo $LINE >>$HOLD
  15.                         ;;
  16.                 esac   
  17.         done <$INPUT_FILE
  18.         # use to sed to delete any empty lines, if any
  19.         sed -e '/^$/d' order.tmp >order.$
  20.         mv order.$ order.tmp
  21. else   
  22.         echo "`basename $0` : Sorry $INPUT_FILE does not exist or empty"
  23. fi      
复制代码
执行脚本后,输出结果为。
[root@localhost ~]# cat order.tmp
Pens 14 12
Pencils 15 15
Pads 7 3
Disks 3 2
Sharpeners 5 1
现在要在另一个w h i l e循环中读取临时工作文件,使用e x p r对数字进行数值运算。
  1. #!/bin/sh
  2. # whileorder2
  3. # init the variables
  4. HOLD=order.tmp
  5. RE_ORDER=0
  6. ORDERS=0
  7. STATIONERY_TOT=0
  8. if [ -s $HOLD ]; then
  9.         echo "========= STOCK RE_ORDER REPORT ========="
  10.         while read ITEM RECORD LEVEL
  11.         do
  12.                 # are we below the reorder level for this item ??
  13.                 if [ "$LEVEL" -lt "$RECORD" ];then
  14.                         # yes, do the new order amount
  15.                         NEW_ORDER=`expr $RECORD + $RECORD`
  16.                         # running total of orders
  17.                         ORDERS=`expr $ORDERS + 1`
  18.                         # running total of stock levels
  19.                         STATIONERY_TOT=`expr $STATIONERY_TOT + $LEVEL`
  20.                         echo "$ITEM need reordering to the amount $NEW_ORDER"
  21.                 fi
  22.         done <$HOLD
  23.         echo "$ORDERS new items need to be ordered"
  24.         echo "Our reorder total is $STATIONERY_TOT"
  25. else
  26.         echo "`basename $0` : Sorry $HOLD does not exists or is empty"
  27. fi
复制代码
以下为依据报表文件运行所得输出结果。
  1. ========= STOCK RE_ORDER REPORT =========
  2. Pens need reordering to the amount 28
  3. Pads need reordering to the amount 14
  4. Disks need reordering to the amount 6
  5. Sharpeners need reordering to the amount 10
  6. 4 new items need to be ordered
  7. Our reorder total is 18
复制代码
将两段脚本结合在一起很容易。实际上这本来是一个脚本,为讲解方便,才将其分成两
个。

while循环和文件描述符
第5章查看文件描述符时,提到有必要用w h i l e循环将数据读入一个文件。使用文件描述符
3和4,下面的脚本进行文件m y f i l e . t x t到m y f i l e . b a k的备份。注意,脚本开始测试文件是否存在,
如果不存在或没有数据,脚本立即终止。还有w h i l e循环用到了空命令(:),这是一个死循环,
因为n u l l永远返回真。尝试读至文件结尾将返回错误,那时脚本也终止执行。
  1. #!/bin/sh
  2. # copyfile.sh
  3. FILENAME=myfile.txt
  4. FILENAME_BAK=myfile.bak
  5. if [ -s $FILENAME ]; then
  6.         # open FILENAME for writing
  7.         # open FILENAME for reading
  8.         exec 4>$FILENAME_BAK
  9.         exec 3<$FILENAME
  10.         # loop forever until no more data and thus an error so we
  11.         # are at end of file
  12.         while:
  13.         do      
  14.                 read LINE <&3
  15.                 if [ "$?" -ne 0 ]; then
  16.                         # errors then close up
  17.                         exec 3<&-
  18.                         exec 4<&-
  19.                 fi      
  20.                 # write to FILENAME_BAK
  21.                 echo $LINE>&4
  22.         done   
  23. else   
  24.         echo "`basename $0` : Sorry, $FILENAME is not present or is empty" >&2
  25. fi      
复制代码
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP