LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 85640|回复: 53

【Bourne shell编程入门及脚本测试[转帖]】

[复制链接]
发表于 2003-2-13 11:15:26 | 显示全部楼层 |阅读模式
发信人: Altmayer (alt>>追求>>堕落>>极限>>), 信区: GNULinux
标  题: Bourne Shell及shell编程(1)
发信站: 饮水思源 (2001年12月30日00:21:48 星期天), 站内信件
  
【 以下文字转载自 Altmayer 的信箱 】
【 原文由 Altmayer.bbs@altmayer.dhs.org 所发表 】
来  源: from altmayer.dhs.org ([211.80.41.106])
日  期: Sun Dec 30 00:20:35 2001
  
标  题: LINUX选修课讲义:Bourne Shell及shell编程(1)
发信站: 碧海青天 (Wed Apr 21 23:41:45 1999), 转信
  
版权声明:
    本文内容为大连理工大学LINUX选修课讲义,欢迎大家转载,但禁止使用本材料进行
    任何商业性或赢利性活动。转载时请保留本版权声明。
  
    作者:何斌武,hbwork@dlut.edu.cn,大连理工大学网络中心,April 1999.
  
    URL: ftp://ftp.dlut.edu.cn/pub/PEOPLE/albin/

/********抱歉,为了格式不乱,我就用代码模式粘贴了*********/

  1. ------------------------------------------------------------------------------
  2. Bourne Shell
  3.   
  4. 介绍:Bourne Shell 基础及其他很多有用的特性,shell编程及组织。
  5.   
  6. 主要内容:
  7. .shell基础      基本介绍,环境,选项,特殊字符
  8. .shell变量      用户定义变量,环境变量,位置变量(shell 参数)
  9. .shell script编程
  10.         条件测试,循环及重复控制
  11. .shell定制
  12.   
  13. 1.shell基础知识
  14.   作者:Stephen Bourne 在Bell实验室开发
  15.   建议:man sh  查看相关UNIX上的改进或特性
  16.   
  17. (1)shell提示符及其环境
  18.    /etc/passwd文件
  19.    提示符:$
  20.    /etc/profile $HOME/.profile
  21. (2)shell执行选项
  22.    -n   测试shell script语法结构,只读取shell script但不执行
  23.    -x   进入跟踪方式,显示所执行的每一条命令,用于调度
  24.    -a   Tag all variables for export
  25.    -c "string"  从strings中读取命令
  26.    -e   非交互方式
  27.    -f   关闭shell文件名产生功能
  28.    -h   locate and remember functions as defind
  29.    -i   交互方式
  30.    -k   从环境变量中读取命令的参数
  31.    -r   限制方式
  32.    -s   从标准输入读取命令
  33.    -t   执行命令后退出(shell exits)
  34.    -u   在替换中如使用未定义变量为错误
  35.    -v   verbose,显示shell输入行
  36.   
  37.    这些选项可以联合使用,但有些显然相互冲突,如-e和-i.
  38.   
  39. (3)受限制shell(Restircted Shell)
  40.     sh -r 或 /bin/rsh
  41.   
  42.     不能执行如下操作:cd, 更改PATH,指定全路径名,输出重定向,因此可以提供一个较
  43.     好的控制和安全机制。通常rsh用于应用型用户及拨号用户,这些用户通常是看不到提
  44.     示符的。通常受限制用户的主目录是不可写的。
  45.   
  46.     不足:如果用户可以调用sh,则rsh的限制将不在起作用,事实上如果用户在vi及more
  47.         程序中调用shell,而这时rsh的限制将不再起作用。
  48.   
  49. (4)用set改变 shell选项
  50.    用户可以在$提示符下用set命令来设置或取消shell的选项。使用-设置选项,+取消相应
  51.    选项,大多数UNIX系统允许a,e,f,h,k,n,u,v和x的开关设置/取消。
  52.   
  53.    set -xv
  54.         启动跟踪方式;显示所有的命令及替换,同样显示输入。
  55.    set -tu
  56.         关闭在替换时对未定义变量的检查。
  57.   
  58.    使用echo $-显示所有已设置的shell选项。
  59.   
  60.   
  61. (5)用户启动文件 .profile
  62.         PATH=$PATH:/usr/loacl/bin; export PATH
  63.   
  64. (6)shell环境变量
  65.         CDPATH  用于cd命令的查找路径
  66.         HOME    /etc/passwd文件中列出的用户主目录
  67.         IFS     Internal Field Separator,默认为空格,tab及换行符
  68.         MAIL    /var/mail/$USERNAME     mail等程序使用
  69.         PATH
  70.         PS1,PS2        默认提示符($)及换行提示符(>)
  71.         TERM    终端类型,常用的有vt100,ansi,vt200,xterm等
  72.   
  73.         示例:$PS1="test:";export PS1
  74.               test: PS1="\$";export PS1
  75.               $echo $MAIL
  76.               /var/mail/username
  77. (7)保留字符及其含义
  78. $      shell变量名的开始,如$var
  79.    |    管道,将标准输出转到下一个命令的标准输入
  80.    #    注释开始
  81.    &    在后台执行一个进程
  82.    ?   匹配一个字符
  83.    *    匹配0到多个字符(与DOS不同,可在文件名中间使用,并且含.)
  84.    $-   使用set及执行时传递给shell的标志位
  85.    $!   最后一个子进程的进程号
  86.    $#   传递给shell script的参数个数
  87.    $*   传递给shell script的参数
  88.    $@   所有参数,个别的用双引号括起来
  89.    $?   上一个命令的返回代码
  90.    $0   当前shell的名字
  91.    $n    (n:1-) 位置参数
  92.    $$   进程标识号(Process Identifier Number, PID)
  93.    >file        输出重定向
  94.    <file        输入重定向
  95.    `command`    命令替换,如    filename=`basename /usr/local/bin/tcsh`
  96.    >>fiile      输出重定向,append
  97.   
  98.    转义符及单引号:
  99.         $echo "$HOME $PATH"
  100.         /home/hbwork /opt/kde/bin:/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:
  101.         $echo '$HOME $PATH'
  102.         $HOME $PATH
  103.         $echo \$HOME $PATH
  104.         $HOME /opt/kde/bin:/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/home/hbw
  105. ork/bin
  106.   
  107.    其他:
  108.         $dir=ls
  109.         $$dir
  110.         $alias dir ls
  111.         $dir
  112.   
  113.         ls > filelist
  114.         ls >> filelist
  115.         wc -l < filelist
  116.         wc -l filelist
  117.         sleep 5; echo 5 seconds reaches; ls -l
  118.         ps ax |egrep inetd
  119.         find / -name core -exec rm {} \; &
  120.         filename=`date "+%Y%m%d"`.log
  121.   
  122. 2. shell变量
  123.   变量:代表某些值的符号,如$HOME,cd命令查找$HOME,在计算机语言中可以使用变量可以
  124.         进行多种运算和控制。
  125.   
  126.   Bourne Shell有如下四种变量:
  127.         .用户自定义变量
  128.         .位置变量即 shell script之参数
  129.         .预定义变量(特殊变量)
  130.         .环境变量(参考shell定制部分)
  131. (1)用户自定义变量(数据的存储)
  132.         $ COUNT=1
  133.         $ NAME="He Binwu"
  134.   
  135.      技巧:因为大部分UNIX命令使用小写字符,因此在shell编程中通常使用全大写变量,
  136.         当然这并不是强制性的,但使用大写字符可以在编程中方便地识别变量。
  137.   
  138.      变量的调用:在变量前加$
  139.         $ echo $HOME
  140.         /home/hbwork
  141.         $ WEEK=Satur
  142.         $ echo Today is $WEEKday
  143.         Today is
  144.         $echo Today is ${WEEK}day
  145.         Today is Saturday
  146.   
  147.      Shell变量赋值从右从左进行(Linux Shell/bash从左向右赋值!)
  148.      $ X=$Y Y=y
  149.      $ echo $X
  150.      y
  151.      $ Z=z Y=$Z
  152.      $ echo $Y
  153.   
  154.      $
  155.   
  156.      使用unset命令删除变量的赋值
  157.      $ Z=hello
  158.      $ echo $Z
  159.      hello
  160.      $ unset Z
  161.      $ echo $Z
  162.   
  163.      $
  164.   
  165.      有条件的命令替换
  166.         在Bourne Shell中可以使变量替换在特定条件下执行,即有条件的环境变量替换。
  167.         这种变量替换总是用大括号括起来的。
  168.   
  169.         .设置变量的默认值
  170.             在变量未赋值之前其值为空。Bourne Shell允许对变量设置默认值,其格式如
  171. 下:
  172.             ${variable:-defaultvalue}
  173.             例:
  174.                 $ echo Hello $UNAME
  175.                 Hello
  176.                 $ echo Hello ${UNAME:-there}
  177.                 Hello there
  178.                 $ echo $UNAME   #变量值并未发生变化
  179.   
  180.                 $ UNAME=hbwork
  181.                 $ echo Hello ${UNAME:-there}
  182.                 Hello hbwork
  183.                 $
  184.         .另一种情况:改变变量的值,格式如下:
  185.             ${variable:=value}
  186.   
  187.             例:
  188.                 $ echo Hello $UNAME
  189.                 Hello
  190.                 $ echo Hello ${UNAME:=there}
  191.                 Hello there
  192.                 $ echo $UNAME   #变量值并未发生变化
  193.                 there
  194.                 $
  195.         .变量替换中使用命令替换
  196.                 $USERDIR=${$MYDIR:-`pwd`}
  197.   
  198.         .在变量已赋值时进行替换  ${variable:+value}
  199.         .带有错误检查的有条件变量替换
  200.           ${variable:?value}
  201.           例:
  202.           $ UNAME=
  203.           $ echo ${UNAME:?"UNAME has not been set"}
  204.           UNAME: UNAME has not been set
  205.           $ echo ${UNAME:?}
  206.           UNAME: parameter null or not set
  207.   
  208.   (2)位置变量(Shell参数)
  209.   在shell script中位置参数可用$1..$9表示,$0表示内容通常为当前执行程序的文件名。
  210.   .防止变量值被替换 readonly variable
  211.   .使用export命令输出变量,使得变量对子shell可用,当shell执行一下程序时,shell
  212.    将为其设置一个新的环境让其执行,这称之了subshell.  在Bourne Shell中变量通常
  213.    被认为是本地变量,也就是说在对其赋值之外的shell环境之外是不认识此变量的。使
  214.    用export对subshell可用。
  215.   
  216.    例:对变量PS1的export操作,shell的提示符将发生变化。
  217.    $ PS1=`hostname`$
  218.         peony$sh
  219.         $ echo $PS1
  220.         $  <-输出结果
  221.         $ exit
  222.         peony$export PS1
  223.         peony$sh
  224.         peony$ echo $PS1
  225.         peony$  <-输出结果
  226.         peony$
  227.   
  228.   
  229. 3.Shell Script编程
  230. 目的:使用UNIX所提供的最常用工具来完成所需复杂任务的强大功能。
  231.   
  232. (1)最简单的Shell 编程
  233.    $ls -R / |grep myname |more
  234.   
  235.    每天数据的备份:
  236.    $ cd /usr/yourname; ls * |cpio -o > /dev/rmt/0h
  237.   
  238.    书写程序的目的是一次编程,多次使用(执行)!
  239.   
  240.    $ cat > backup.sh
  241.    cd /home/hbwork
  242.    ls * | cpio -o > /dev/rmt/0h
  243.    ^D
  244.   
  245.    执行:
  246.    $ sh backup.sh
  247.   
  248.    或:
  249.    $ chmod +x backup.sh
  250.    $ ./backup.sh
  251.   
  252.    技巧:在shell script中加入必要的注释,以便以后阅读及维护。
  253.   
  254. (2)shell是一个(编程)语言
  255.   同传统的编程语言一样,shell提供了很多特性,这些特性可以使你的shell script
  256.   编程更为有用,如:数据变量、参数传递、判断、流程控制、数据输入和输出,子
  257.   程序及以中断处理等。
  258.   
  259.   . 在shell编程中使用数据变量可以将程序变量更为通用,如在上面backup.sh中:
  260.     cd $WORKDIR
  261.     ls * | cpio -o > /dev/rmt/0h
  262.   
  263.   . Shell编程中的注释以#开头
  264.   . 对shell变量进行数字运算,使用expr命令
  265.         expr integer operator integer
  266.         其中operator为+ - * / %, 但对*的使用要用转义符\,如:
  267.         $expr 4 \* 5
  268.         20
  269.         $int=`expr 5 + 7`
  270.         $echo $int
  271.         12
  272.   
  273. (3)Shell编程的参数传递, 可通过命令行参数以及交互式输入变量(read)
  274.   
  275.      restoreall.sh 对backup.sh程序的备份磁带进行恢复
  276.         $cat > restoreall.sh
  277.         cd $WORKDIR
  278.         cpio -i < /dev/rmt/0h
  279.         ^D
  280.      restore1.sh:只能恢复一个文件
  281.         #restore1 --program to restore a single file
  282.         cd $WORKDIR
  283.         cpio -i $i < /dev/rmt/0h
  284.   
  285.         $restore1 file1
  286.   
  287.         恢复多个文件restoreany :
  288.         #restoreany --program to restore a single file
  289.         cd $WORKDIR
  290.         cpio -i $* < /dev/rmt/0h
  291.   
  292.         $ restoreany file1 file2 file3
  293.   
  294.   
  295. (4)条件判断
  296.    . if-then语句,格式如下:
  297.         if command_1
  298.         then
  299.                 command_2
  300.                 command_3
  301.         fi
  302.         command_4
  303.   
  304.    在if-then语句中使用了命令返回码$?,即当command_1执行成功时才执行command_2和
  305.    command_3,而command_4总是执行.
  306.   
  307.    示例程序unload: 在备份成功时删除原始文件,带有错误检查
  308.   
  309.         cd $1
  310.         #备份时未考虑不成功的情况!
  311.         ls -a | cpio -o > /dev/rmt/0h
  312.         rm -rf *
  313.   
  314.         改进如下:
  315.   
  316.         #当使用了管道命令时,管理命令的返回代码为最后一个命令的返回代码
  317.         if ls -a | cpio -o > /dev/rmt/0h
  318.         then
  319.                 rm -rf *
  320.         fi
  321.   
  322.    . if-then-else语句
  323.         if command_1
  324.         then
  325.                 command_2
  326.         else
  327.                 command_3
  328.         fi
  329.   
  330.         技巧: 由于shell对命令中的多余的空格不作任何处理,一个好的程序员会用这一特

  331.               对自己的程序采用统一的缩进格式,以增强自己程序的可读性.
  332.   
  333.     . 使用test命令进行进行条件测试
  334.       格式: test conditions
  335.   
  336.       test在以下四种情况下使用: a. 字符比较 b.两个整数值的比较
  337.                 c. 文件操作,如文件是否存在及文件的状态等
  338.                 d. 逻辑操作,可以进行and/or,与其他条件联合使用
  339.   
  340.       a. 测试字符数据: shell变量通常民政部下均作为字符变量
  341.         str1 = str2     二者相长,相同
  342.         str1 != str2    不同
  343.         -n string       string不为空(长度不为零)
  344.         -z string       string为空
  345.         string          string不为空
  346.   
  347.         例:
  348.                 $ str1=abcd     #在含有空格时必须用引号括起来
  349.                 $ test $str1=abcd
  350.                 $ echo $?
  351.                 0
  352.                 $ str1="abcd "
  353.                 $ test $str1=abcd
  354.                 $ echo $?
  355.                 1
  356.         Note: 在test处理含有空格的变量时最好用引号将变量括起来,否则会出现错误的
  357. 结果,
  358.               因为shell在处理命令行时将会去掉多余的空格,而用引号括起来则可以防止
  359.               shell去掉这些空格.
  360.               例:
  361.                 $ str1="    "
  362.                 $ test $str1
  363.                 $ echo $?
  364.                 1
  365.                 $ test "$str1"
  366.                 $ echo $?
  367.                 0
  368.                 $ test -n $str1
  369.                 test: argument expected
  370.                 $ test -n "$str1"
  371.                 $ echo $?
  372.                 0
  373.                 $
  374.   
  375.       b. 整数测试: test与expr相同,可以将字符型变量转换为整数进行操作,expr进行
  376.          整数的算术运算,而test则进行逻辑运算.
  377.   
  378.          表达式                 说明
  379.          ---------------------------------------
  380.          int1 -eq int2          相等?
  381.          int1 -ne int2          不等?
  382.          int1 -gt int2          int1 > int2 ?
  383.          int1 -ge int2          int1 >= int2 ?
  384.          int1 -lt int2          int1 < int2 ?
  385.          int1 -le int2          int1 <= int2 ?
  386.   
  387.          例:
  388.                 $ int1=1234
  389.                 $ int2=01234
  390.                 $ test $int1 -eq $int2
  391.                 $ echo $?
  392.                 0
  393.   
  394.       c. 文件测试:检查文件状态如存在及读写权限等
  395.   
  396.          -r filename     用户对文件filename有读权限?
  397.          -w filename     用户对文件filename有写权限?
  398.          -x filename     用户对文件filename有可执行权限?
  399.          -f filename     文件filename为普通文件?
  400.          -d filename     文件filename为目录?
  401.          -c filename     文件filename为字符设备文件?
  402.          -b filename     文件filename为块设备文件?
  403.          -s filename     文件filename大小不为零?
  404.          -t fnumb        与文件描述符fnumb(默认值为1)相关的设备是一个终端设备?
  405.   
  406.       d. 测试条件之否定,使用!
  407.         例:
  408.                 $ cat /dev/null > empty
  409.                 $ test -r empty
  410.                 $ echo $?
  411.                 0
  412.                 $ test -s empty
  413.                 1
  414.                 $ test ! -s empty
  415.                 $ echo $?
  416.                 0
  417.       e. 测试条件之逻辑运算
  418.         -a      And
  419.         -o      Or
  420.   
  421.         例: $ test -r empty -a -s empty
  422.             $ echo $?
  423.             1
  424.       f. 进行test测试的标准方法
  425.          因为test命令在 shell编程中占有很重要的地位,为了使shell能同其他编程语言
  426. 一样
  427.          便于阅读和组织, Bourne Shell在使用test测试时使用了另一种方法:用方括号将
  428. 整个
  429.          test测试括起来:
  430.   
  431.          $ int1=4
  432.          $ [ $int1 -gt 2 ]
  433.          $ echo $?
  434.          0
  435.   
  436.          例: 重写unload程序,使用test测试
  437.          #!/bin/sh
  438.          #unload - program to backup and remove files
  439.          #syntax: unload directory
  440.   
  441.          #check arguments
  442.          if [ $# -ne 1 ]
  443.          then
  444.                 echo "usage: $0 directory"
  445.                 exit 1
  446.          fi
  447.   
  448.          #check for valid directory name
  449.          if [ ! -d "$1" ]
  450.          then
  451.                 echo "$1 is not a directory"
  452.                 exit 2
  453.          fi
  454.   
  455.          cd $1
  456.   
  457.          ls -a | cpio -o > /dev/rmt/0h
  458.   
  459.          if [ $? -eq 0 ]
  460.          then
  461.                 rm -rf *
  462.          else
  463.                 echo "A problem has occured in creating backup"
  464.                 echo "The directory will not be ereased"
  465.                 echo "Please check the backup device"
  466.                 exit 3
  467.          fi
  468.          # end of unload
  469.   
  470.          在如上示例中出现了exit, exit有两个作用:一是停止程序中其他命令的执行,二

  471.          设置程序的退出状态
  472.   
  473.       g. if嵌套及elif结构
  474.         if command
  475.         then
  476.                 command
  477.         else
  478.                 if command
  479.                 then
  480.                         command
  481.                 else
  482.                         if command
  483.                         then
  484.                                 command
  485.                         fi

  486.                 fi
  487.         fi
  488.   
  489.         改进:使用elif结构
  490.         if command
  491.         then
  492.                 command
  493.         elif    command
  494.         then
  495.                 command
  496.         elif    command
  497.         then
  498.                 command
  499.         fi
  500.   
  501.         elif结构同if结构类似,但结构更清淅,其执行结果完全相同.
复制代码

--
※ 转寄:·极乐世界 altmayer.dhs.org·[FROM: NIRVANA]
 楼主| 发表于 2003-2-13 11:18:06 | 显示全部楼层

[ZZ]Bourne Shell及shell编程(2)

发信人: Altmayer (alt>>追求>>堕落>>极限>>), 信区: GNULinux
标  题: Bourne Shell及shell编程(2)
发信站: 饮水思源 (2001年12月30日00:21:51 星期天), 站内信件
  
【 以下文字转载自 Altmayer 的信箱 】
【 原文由 Altmayer.bbs@altmayer.dhs.org 所发表 】
来  源: from altmayer.dhs.org ([211.80.41.106])
日  期: Sun Dec 30 00:20:37 2001
  
标  题: LINUX选修课讲课义:Bourne Shell及shell编程(2)
发信站: 碧海青天 (Thu Apr 22 23:33:45 1999), 转信
  
版权声明:
    本文内容为大连理工大学LINUX选修课讲义,欢迎大家转载,但禁止使用本材料进行
    任何商业性或赢利性活动。转载时请保留本版权声明。
  
    作者:何斌武,hbwork@dlut.edu.cn,大连理工大学网络中心,April 1999.
  
    URL: ftp://ftp.dlut.edu.cn/pub/PEOPLE/albin/

/********抱歉,为了格式不乱,我用代码模式粘贴全文*********/
------------------------------------------------------------------------------

  1. Bourne Shell及Shell编程(2)
  2.       h.交互式从键盘读入数据
  3.         使用read语句,其格式如下:
  4.   
  5.         read var1 var2 ... varn
  6.   
  7.         read将不作变量替换,但会删除多余的空格,直到遇到第一个换行符(回车),
  8.         并将输入值依次赋值给相应的变量。
  9.   
  10.         例:
  11.         $ read var1 var2 var3
  12.         Hello   my  friends
  13.         $ echo $var1 $var2 $var3
  14.         Hello my friends
  15.         $ echo $var1
  16.         Hello
  17.         $ read var1 var2 var3
  18.         Hello my dear friends
  19.         $ echo $var3
  20.         dear friends   <-输入多余变量时,输入值余下的内容赋给最后一个变量
  21.         $ read var1 var2 var3
  22.         Hello friends
  23.         $ echo $var3
  24.                         <- var3为空
  25.         $
  26.   
  27.         在shell script中可使用read语句进行交互操作:
  28.   
  29.         ...
  30.         #echo -n message 输出结果后不换行
  31.         echo -n "Do you want to continue: Y or N"
  32.         read ANSWER
  33.   
  34.         if [ $ANSWER=N -o $ANSWER=n ]
  35.         then
  36.                 exit
  37.         fi
  38.   
  39.       i. case结构:结构较elif-then结构更清楚
  40.   
  41.         比较if-then语句:
  42.   
  43.         if [ variable1 = value1 ]
  44.         then
  45.             command
  46.             command
  47.         elif [ variable1 = value2 ]
  48.         then
  49.             command
  50.             command
  51.         elif [ variable1 = value3 ]
  52.         then
  53.             command
  54.             command
  55.         fi
  56.   
  57.         相应的case结构:
  58.   
  59.         case value in
  60.             pattern1)
  61.                command
  62.                command;;
  63.             pattern2)
  64.                command
  65.                command;;
  66.             ...
  67.             patternn)
  68.                command;
  69.          esac
  70.   
  71.        * case语句只执行第一个匹配模式
  72.   
  73.        例:使用case语句建立一个菜单选择shell script
  74.   
  75.        #Display a menu
  76.        echo _
  77.        echo "1 Restore"
  78.        echo "2 Backup"
  79.        echo "3 Unload"
  80.        echo
  81.   
  82.        #Read and excute the user's selection
  83.        echo -n "Enter Choice:"
  84.        read CHOICE
  85.   
  86.        case "$CHOICE" in
  87.         1)      echo "Restore";;
  88.         2)      echo "Backup";;
  89.         3)      echo "Unload";;
  90.         *)      echo "Sorry $CHOICE is not a valid choice
  91.                 exit 1
  92.         esac
  93.   
  94.         在上例中,*指默认匹配动作。此外,case模式中也可以使用逻辑操作,如下所示

  95.   
  96.         pattern1 | pattern2 )   command
  97.                                 command ;;
  98.   
  99.         这样可以将上面示例程序中允许用户输入数字或每一个大写字母。
  100.   
  101.         case "$CHOICE" in
  102.                 1|R)    echo "Restore";;
  103.                 2|B)    echo "Backup";;
  104.                 3|U)    echo "Unload";;
  105.                 *)      echo "Sorry $CHOICE is not a valid choice
  106.                         exit 1
  107.         esac
  108.   
  109. (5)循环控制
  110.     <1> while循环:
  111.         格式:
  112.                 while   command
  113.                 do
  114.                         command
  115.                         command
  116.                         command
  117.                         ...
  118.                 done
  119.   
  120.         例: 计算1到5的平方
  121.         #!/bin/sh
  122.         #
  123.         #Filename: square.sh
  124.         int=1
  125.   
  126.         while [ $int -le 5 ]
  127.         do
  128.                 sq=`expr $int \* $int`
  129.                 echo $sq
  130.                 int=`expr $int + 1`
  131.         done
  132.         echo "Job completed"
  133.   
  134.         $ sh square.sh
  135.         1
  136.         4
  137.         9
  138.         16
  139.         25
  140.         Job completed
  141.   
  142.       <2> until循环结构:
  143.         格式:
  144.                 until command
  145.                 do
  146.                         command
  147.                         command
  148.                         ....
  149.                         command
  150.                 done
  151.   
  152.         示例:使用until结构计算1-5的平方
  153.         #!/bin/sh
  154.   
  155.         int=1
  156.   
  157.         until [ $int -gt 5 ]
  158.         do
  159.                 sq=`expr $int \* $int`
  160.                 echo $sq
  161.                 int=`expr $int + 1`
  162.         done
  163.         echo "Job completed"
  164.   
  165.      <3> 使用shift对不定长的参数进行处理
  166.         在以上的示例中我们总是假设命令行参数唯一或其个数固定,或者使用$*将整个命

  167.         行参数传递给shell script进行处理。对于参数个数不固定并且希望对每个命令参

  168.         进行单独处理时则需要shift命令。使用shift可以将命令行位置参数依次移动位置

  169.         即$2->$1, $3->$2. 在移位之前的第一个位置参数$1在移位后将不在存在。
  170.   
  171.         示例如下:
  172.   
  173.         #!/bin/sh
  174.         #
  175.         #Filename: shifter
  176.   
  177.          until [ $# -eq 0 ]
  178.          do
  179.             echo "Argument is $1 and `expr $# - 1` argument(s) remain"
  180.             shift
  181.          done
  182.   
  183.   
  184.          $ shifter 1 2 3 4
  185.          Argument is 1 and 3 argument(s) remain
  186.          Argument is 2 and 2 argument(s) remain
  187.          Argument is 3 and 1 argument(s) remain
  188.          Argument is 4 and 0 argument(s) remain
  189.          $
  190.   
  191.          使用shift时,每进行一次移位,$#减1,使用这一特性可以用until循环对每个参
  192.          数进行处理,如下示例中是一个求整数和的shell script:
  193.   
  194.         #!/bin/sh
  195.         # sumints - a program to sum a series of integers
  196.         #
  197.   
  198.          if [ $# -eq 0 ]
  199.          then
  200.             echo "Usage: sumints integer list"
  201.             exit 1
  202.          fi
  203.   
  204.          sum=0
  205.   
  206.          until [ $# -eq 0 ]
  207.          do
  208.             sum=`expr $sum + $1`
  209.             shift
  210.          done
  211.          echo $sum
  212.   
  213.   
  214.         $ sh sumints 324 34 34 12 34
  215.         438
  216.         $
  217.   
  218.         使用shift的另一个原因是Bourne Shell的位置参数变量为$1~$9, 因此通过位置变

  219.         只能访问前9个参数。但这并不等于在命令行上最多只能输入9个参数。此时如果想
  220. 访问
  221.         前9个参数之后的参数,就必须使用shift.
  222.   
  223.         另外shift后可加整数进行一次多个移位,如:
  224.   
  225.                 shift 3
  226.   
  227.   
  228.     <4>. for循环
  229.        格式:
  230.                 for var in arg1 arg2 ... argn
  231.                 do
  232.                         command
  233.                         ....
  234.                         command
  235.                 done
  236.   
  237.         示例:
  238.         $ for letter in a b c d e; do echo $letter;done
  239.         a
  240.         b
  241.         c
  242.         d
  243.         e
  244.   
  245.         对当前目录下的所有文件操作:
  246.         $ for i in *
  247.           do
  248.                 if [ -f $i ]
  249.                 then
  250.                         echo "$i is a file"
  251.                 elif    [ -d $i ]
  252.                         echo "$i is a directory"
  253.                 fi
  254.           done
  255.   
  256.         求命令行上所有整数之和:
  257.         #!/bin/sh
  258.   
  259.         sum=0
  260.   
  261.         for INT in $*
  262.         do
  263.                 sum=`expr $sum + $INT`
  264.         done
  265.   
  266.         echo $sum
  267.   
  268.   
  269.       <6> 从循环中退出: break和continue命令
  270.         break           立即退出循环
  271.         continue        忽略本循环中的其他命令,继续下一下循环
  272.   
  273.         在shell编程中有时我们要用到进行无限循环的技巧,也就是说这种循环一直执行

  274.         到break或continue命令。这种无限循环通常是使用true或false命令开始的。UNIX
  275.         系统中的true总是返加0值,而false则返回非零值。如下所示:
  276.   
  277.         #一直执行到程序执行了break或用户强行中断时才结束循环
  278.         while true
  279.         do
  280.                 command
  281.                 ....
  282.                 command
  283.         done
  284.   
  285.         上面所示的循环也可以使用until false, 如下:
  286.   
  287.         until false
  288.         do
  289.                 command
  290.                 ....
  291.                 command
  292.         done
  293.   
  294.         在如下shell script中同时使用了continue,break以及case语句中的正规表达式用
  295. 法:
  296.   
  297.          #!/bin/sh
  298.          # Interactive program to restore, backup, or unload
  299.          # a directory
  300.   
  301.          echo "Welcome to the menu driven Archive program"
  302.   
  303.          while true
  304.          do
  305.          # Display a Menu
  306.             echo
  307.             echo "Make a Choice from the Menu below"
  308.             echo _
  309.             echo "1  Restore Archive"
  310.             echo "2  Backup directory"
  311.             echo "3  Unload directory"
  312.             echo "4  Quit"
  313.             echo
  314.   
  315.          # Read the user's selection
  316.             echo -n "Enter Choice: "
  317.   
  318.             read CHOICE
  319.   
  320.             case $CHOICE in
  321.                [1-3] ) echo
  322.                        # Read and validate the name of the directory
  323.   
  324.                        echo -n "What directory do you want? "
  325.                        read WORKDIR
  326.   
  327.                        if [ ! -d "$WORKDIR" ]
  328.                        then
  329.                           echo "Sorry, $WORKDIR is not a directory"
  330.                           continue
  331.                        fi
  332.   
  333.                        # Make the directory the current working directory
  334.                        cd $WORKDIR;;
  335.   
  336.                     4) :;;    # :为空语句,不执行任何动作
  337.                     *) echo "Sorry, $CHOICE is not a valid choice"
  338.                        continue
  339.             esac
  340.   
  341.             case "$CHOICE" in
  342.                1) echo "Restoring..."
  343.                   cpio -i </dev/rmt/0h;;
  344.   
  345.                2) echo "Archiving..."
  346.                   ls | cpio -o >/dev/rmt/0h;;
  347.   
  348.                3) echo "Unloading..."
  349.                   ls | cpio -o >/dev/rmt/0h;;
  350.   
  351.                4) echo "Quitting"
  352.                   break;;
  353.             esac
  354.   
  355.          #Check for cpio errors
  356.   
  357.             if [ $? -ne 0 ]
  358.             then
  359.                echo "A problem has occurred during the process"
  360.                if [ $CHOICE = 3 ]
  361.                then
  362.                   echo "The directory will not be erased"
  363.                fi
  364.   
  365.                echo "Please check the device and try again"
  366.                continue
  367.             else
  368.                if [ $CHOICE = 3 ]
  369.                then
  370.                   rm *
  371.                fi
  372.             fi
  373.          done
  374.   
  375.   
  376. (6)结构化编程:定义函数
  377.    同其他高级语言一样,shell也提供了函数功能。函数通常也称之为子过程(subroutine)
  378. ,
  379.    其定义格式如下:
  380.   
  381.    funcname()
  382.    {
  383.         command
  384.         ...
  385.         command;  #分号
  386.    }
  387.   
  388.    定义函数之后,可以在shell中对此函数进行调用,使用函数定义可以将一个复杂的程序

  389.    为多个可管理的程序段,如下所示:
  390.   
  391.         # start program
  392.   
  393.          setup ()
  394.          {  command list ; }
  395.   
  396.          do_data ()
  397.          {  command list ; }
  398.   
  399.          cleanup ()
  400.          {  command list ; }
  401.   
  402.          errors ()
  403.          {  command list ; }
  404.   
  405.          setup
  406.          do_data
  407.          cleanup
  408.          # end program
  409.   
  410.      技巧:
  411.         . 在对函数命名时最好能使用有含义的名字,即函数名能够比较准确的描述函数所
  412. 完成
  413.           的任务。
  414.         . 为了程序的维护方便,请尽可能使用注释
  415.   
  416.   
  417.      使用函数的另一个好处就是可以在一个程序中的不同地方执行相同的命令序列(函数),
  418.      如下所示:
  419.   
  420.      iscontinue()
  421.      {
  422.         while true
  423.         do
  424.                 echo -n "Continue?(Y/N)"
  425.                 read ANSWER
  426.   
  427.                 case $ANSWER in
  428.                         [Yy])   return 0;;
  429.                         [Nn])   return 1;;
  430.                         *) echo "Answer Y or N";;
  431.                 esac
  432.         done
  433.      }
  434.   
  435.      这样可以在shell编程中调用iscontinue确定是否继续执行:
  436.   
  437.      if iscontinue
  438.      then
  439.         continue
  440.      else
  441.         break
  442.      fi
  443.   
  444.   
  445.   ** shell函数与shell程序非常相似,但二者有一个非常重要的差别:shell程序是由子sh
  446. ell
  447.      执行的,而shell函数则是作为当前shell的一部分被执行的,因此在当前shell中可以

  448.      变函数的定义。此外在任意shell(包括交互式的shell)中均可定义函数,如:
  449.   
  450.      $ dir
  451.      dir: not found
  452.      $ dir () { ls -l ;}
  453.      $ dir
  454.      total 5875
  455.      -rw-r--r--   1 hbwork        100 Nov 10 23:16 doc
  456.      -rw-r--r--   1 hbwork    2973806 Nov 10 23:47 ns40docs.zip
  457.      -rw-r--r--   1 hbwork    1715011 Nov 10 23:30 ns840usr.pdf
  458.      -rw-r--r--   1 hbwork    1273409 Sep 23  1998 radsol21b6.tar.Z
  459.      -rw-r--r--   1 hbwork       7526 Nov 10 23:47 wget-log
  460.      -rw-r--r--   1 hbwork       1748 Nov 13 21:51 wget-log.1
  461.      $ unset dir
  462.      $ dir () {
  463.      > echo "Permission  Link Owner Group  File_SZ   LastAccess FileName"
  464.      > echo "-----------------------------------------------------------"
  465.      > ls -l $*;
  466.      > }
  467.   
  468.         $ dir
  469.         Permission  Link Owner Group  File_SZ   LastAccess FileName
  470.         -----------------------------------------------------------
  471.         total 5875
  472.         -rw-r--r--   1 hbwork        100 Nov 10 23:16 doc
  473.         -rw-r--r--   1 hbwork    2973806 Nov 10 23:47 ns40docs.zip
  474.         -rw-r--r--   1 hbwork    1715011 Nov 10 23:30 ns840usr.pdf
  475.         -rw-r--r--   1 hbwork    1273409 Sep 23  1998 radsol21b6.tar.Z
  476.         -rw-r--r--   1 hbwork       7526 Nov 10 23:47 wget-log
  477.         -rw-r--r--   1 hbwork       1748 Nov 13 21:51 wget-log.1
  478.   
  479.      通常情况下,shell script是在子shell中执行的,困此在此子shell中对变量所作的
  480.      修改对父shell不起作用。点(.) 命令使用shell在不创建子shell而由当前shell读取
  481.      并执行一个shell script, 可以通过这种方式来定义函数及变量。此外点(.)命令最
  482.      常用的功能就是通过读取.profile来重新配置初始化login变量。如下所示:
  483.   
  484.      $ . .profile
  485.      (csh相对于.命令的是source命令).
  486.   
  487. (7)使用And/Or结构进行有条件的命令执行
  488. <1> And , 仅当第一个命令成功时才有执行后一个命令,如同在逻辑与表达式中如果前面的
  489.      结果为真时才有必要继续运算,否则结果肯定为假。
  490.   
  491.      格式如下:
  492.   
  493.      command1 && command2
  494.   
  495.      例:rm $TEMPDIR/* && echo "File successfully removed"
  496.   
  497.          等价于
  498.   
  499.          if rm $TEMPDIR/*
  500.          then
  501.                 echo "File successfully removed"
  502.          fi
  503.   
  504. <2>Or, 与AND相反,仅当前一个命令执行出错时才执行后一条命令
  505.   
  506.     例: rm $TEMPDIR/* || echo "File not removed"
  507.   
  508.          等价与:
  509.   
  510.          if rm $TEMPDIR/*
  511.          then
  512.                 command
  513.          else
  514.                 echo "File not removed"
  515.          fi
  516.   
  517. <3> 混合命令条件执行
  518.      command1 && command2 && command3
  519.         当command1, command2成功时才执行command3
  520.   
  521.      command1 && command2 || comamnd3
  522.          仅当command1成功,command2失败时才执行command3
  523.   
  524.      当然可以根据自己的需要进行多种条件命令的组合,在此不多讲述。
  525.   
  526.   
  527. (8) 使用getopts命令读取unix格式选项
  528.     UNIX格式选项指如下格式的命令行参数:
  529.     command -options parameters
  530.   
  531.     使用格式:
  532.     getopts option_string variable
  533.   
  534.     具体使用方法请参考getopts的在线文档(man getopts).
  535.   
  536.     示例如下:
  537.   
  538.          #newdate
  539.          if [ $# -lt 1 ]
  540.          then
  541.              date
  542.          else
  543.             while getopts mdyDHMSTjJwahr OPTION
  544.             do
  545.                case $OPTION
  546.                in
  547.                   m) date '+%m ';;  # Month of Year
  548.                   d) date '+%d ';;  # Day of Month
  549.                   y) date '+%y ';;  # Year
  550.                   D) date '+%D ';;  # MM/DD/YY
  551.                   H) date '+%H ';;  # Hour
  552.                   M) date '+%M ';;  # Minute
  553.                   S) date '+%S ';;  # Second
  554.                   T) date '+%T ';;  # HH:MM:SS
  555.                   j) date '+%j ';;  # day of year
  556.                   J) date '+%y%j ';;# 5 digit Julian date
  557.                   w) date '+%w ';;  # Day of the Week
  558.                   a) date '+%a ';;  # Day abbreviation
  559.                   h) date '+%h ';;  # Month abbreviation
  560.                   r) date '+%r ';;  # AM-PM time
  561.                   \?) echo "Invalid option $OPTION";;
  562.                esac
  563.             done
  564.          fi
  565.   
  566.          $ newdate -J
  567.          94031
  568.          $ newdate -a -h -d
  569.          Mon
  570.          Jan
  571.          31
  572.          $ newdate -ahd
  573.          Mon
  574.          Jan
  575.          31
  576.          $
  577.   
  578.   
  579.          示例程序:复制程序
  580.   
  581.          # Syntax: duplicate [-c integer] [-v] filename
  582.          #    where integer is the number of duplicate copies
  583.          #    and -v is the verbose option
  584.   
  585.          COPIES=1
  586.          VERBOSE=N
  587.   
  588.   
  589.          while getopts vc: OPTION
  590.          do
  591.             case $OPTION
  592.             in
  593.                c) COPIES=$OPTARG;;
  594.                v) VERBOSE=Y;;
  595.                \?) echo "Illegal Option"
  596.                    exit 1;;
  597.             esac
  598.          done
  599.   
  600.          if [ $OPTIND -gt $# ]
  601.          then
  602.             echo "No file name specified"
  603.             exit 2
  604.          fi
  605.   
  606.   
  607.          shift `expr $OPTIND -1`
  608.   
  609.          FILE=$1
  610.          COPY=0
  611.   
  612.          while [ $COPIES -gt $COPY ]
  613.          do
  614.             COPY=`expr $COPY + 1`
  615.             cp $FILE ${FILE}${COPY}
  616.             if [ VERBOSE = Y ]
  617.             then
  618.                echo ${FILE}${COPY}
  619.             fi
  620.          done
  621.   
  622.   
  623.          $ duplicate -v fileA
  624.          fileA1
  625.          $ duplicate -c 3 -v fileB
  626.          fileB1
  627.          fileB2
  628.          fileB3
  629.   
  630.   
  631. 4. Shell的定制
  632.    通常使用shell的定制来控制用户自己的环境,比如改变shell的外观(提示符)以及增强
  633.    自己的命令。
  634.   
  635. (1)通常环境变量来定制shell
  636.    通常改变环境变量可以定制shell的工作环境。shell在处理信息时会参考这些环境变量
  637.    ,改变环境变量的值在一定程度上改变shell的操作方式,比如改变命令行提示符。
  638.   
  639.    .使用IFS增加命令行分隔符
  640.      默认状态下shell的分隔符为空格、制表符及换行符,但可以通过改变IFS的值加入自

  641.      的分隔符。如下所示:
  642.   
  643.   
  644.      $ IFS=":"
  645.      $ echo:Hello:my:Friend
  646.      Hello my Friend
  647.   
  648. (2)加入自己的命令及函数
  649.    如下程序:
  650.    #Directory and Prompt change program
  651.    #Syntax: chdir directory
  652.   
  653.    if [ ! -d "$1" ]
  654.    then
  655.         echo "$1 is not a directory"
  656.         exit 1
  657.    fi
  658.   
  659.    cd $1
  660.    PS1=`pwd`$
  661.    export PS1
  662.   
  663.    $ chdir /usr/home/teresa
  664.    $
  665.   
  666.    但此程序在执行时系统提示符并不会改变,因为此程序是在子shell中执行的。因此其变

  667. 对当前shell并无影响,要想对当前shell起作用,最好是将此作为函数写在自己的.profile

  668. 或建立自己的个人函数文件.persfuncs
  669.   
  670.    #Personal function file persfuncs
  671.   
  672.    chdir()
  673.    {
  674.    #Directory and Prompt change program
  675.    #Syntax: chdir directory
  676.    if [ ! -d "$1" ]
  677.    then
  678.         echo "$1 is not a directory"
  679.         exit 1
  680.    fi
  681.   
  682.    cd $1
  683.    PS1=`pwd`$
  684.    export PS1;
  685.    }
  686.   
  687.    再执行:
  688.    $ . .persfuncs
  689.    $ chdir temp
  690.    /home/hbbwork/temp$
  691.   
  692.    也可在自己的.profile文件中用 . .persfuncs调用.persfuncs.
  693.   
  694.    说明:在bash/tcsh中已经使用别名,相对而言别名比此方法更为方便。
  695.   
  696.   
  697. 5. 有关shell的专门讨论
  698. (1)shell程序的调试
  699.    切记:程序员(人)总是会犯错误的,而计算机是不会错的。
  700.    使用-x进行跟踪执行,执行并显示每一条指令。
  701.   
  702. (2)命令组
  703.    用小括号将一组命令括起来,则这些命令会由子shell来完成;而{command_list;}则在

  704.    前shell中执行。这两者的主要区别在于其对shell变量的影响,子shell执行的命令不会
  705.    影响当前shell中的变量。
  706.   
  707.    $ NUMBER=2
  708.    $ (A=2;B=2;NUMBER=`expr $A + $B`; echo $NUMBER)
  709.    4
  710.    $ echo $NUMBER
  711.    2
  712.    $ { A=2;B=2;NUMBER=`expr $A + $B`; echo $NUMBER; }
  713.    4
  714.    $ echo $NUMBER
  715.    4
  716.   
  717.   
  718. 总结:
  719.    在本章中讲述了Bourne Shell的基本知识,使用shell变量,shell script基础。这些概

  720.    对于理解学习Korn Shell, csh以及其他script编程都是非常有用的。
  721.   
  722.    很多OS都有不少语言及一些script功能,但很少有象UNIX SHELL这样灵活强大的script

  723.    本语言能力。
  724.   
  725.    对于系统管理员或程序员来说,熟练地使用shell script将对日常工作(系统维护及管理
  726. )
  727.    非常有用,如果你想作一个合格的系统管理员,强烈建议你进一步深入的了解和使用
  728.    shell.
  729.   
  730.    另外,对于系统管理员来说,PERL也是一个必不可少的script编程语言,尤其是对于处

  731.    文本格式的各种文件,PERL具有shell, awk, sed, grep等的功能,但使用起来更为灵活

  732.    功能也更强大。大家可以参考“Perl By Examples"来学习和使用PERL。
  733.   
  734. --

复制代码

※ 转寄:·极乐世界 altmayer.dhs.org·[FROM: NIRVANA]
--
发表于 2003-3-21 23:11:34 | 显示全部楼层

使用 Bash shell 脚本进行功能测试[转]

在使您的应用程序成型过程中节省时间和精力
Angel Rivera (rivera@us.ibm.com)
软件工程师,VisualAge TeamConnection, IBM
2001 年 3 月
-------------------------------------------------------
创建脚本
功能测试是软件开发的一个关键部分 -- 而已经装入 Linux 的 Bash 可以帮您轻而易举地完成功能测试。在本文中,Angel Rivera 将说明如何运用 Bash shell 脚本通过行命令来执行 Linux 应用程序的功能测试。由于此脚本依赖于命令行的返回码,因而您不能将这种方法运用于 GUI 应用程序。

功能测试是开发周期的一个阶段,在这个阶段中将测试软件应用程序以确保软件的函数如预期的那样,同时能正确处理代码中错误。此项工作通常在单个模块的单元测试结束之后,在负载/重压条件下整个产品的系统测试之前进行的。

市场上有许多测试工具提供了有助于功能测试的功能。然而,首先要获取它们,然后再安装、配置,这将占用您宝贵的时间和精力。Bash 可以帮您免去这些烦琐的事从而可以加快测试的进程。

使用 Bash shell 脚本进行功能测试的优点在于:
Bash shell 脚本已经在 Linux 系统中安装和配置好了。不必再花时间准备它。
可以使用由 Linux 提供的文本编辑器如 vi 创建和修改 Bash shell 脚本。不需要再为创建测试程序而获取专门的工具。
如果已经知道了如何开发 Bourne 或 Korn shell 脚本,那对于如何运用 Bash shell 脚本已经足够了。对您来说,学习曲线已不存在了。
Bash shell 提供了大量的编程构造用于开发从非常简单到中等复杂的脚本。

将脚本从 Korn 移植到 Bash 时的建议
如果已有现成的 Korn shell 脚本,而想要将它们移植到 Bash,就需要考虑下列情况:

Korn 的 "print" 命令在 Bash 中不能使用;而是改为使用 "echo" 命令。
需要将脚本的第一行:
#!/usr/bin/ksh
修改成:
#!/bin/bash

创建 Bash shell 脚本进行功能测试
这些基本的步骤和建议适用于许多在 Linux 上运行的客户机/服务器应用程序。

记录运行脚本的先决条件和主要步骤
将操作分成若干个逻辑组
基于一般方案制定执行步骤
在每个 shell 脚本中提供注释和说明
做一个初始备份以创建基准线
检查输入参数和环境变量
尝试提供 "usuage" 反馈
尝试提供一个"安静"的运行模式
当出现错误时,提供一个函数终止脚本
如可能,提供可以执行单个任务的函数
当显示正在生成的输出时,捕获每个脚本的输出
在每个脚本内,捕获每个行命令的返回码
计算失败事务的次数
在输出文件中,突出显示错误消息,以便于标识
如有可能,"实时"生成文件
在执行脚本的过程中提供反馈
提供脚本执行的摘要
提供一个容易解释的输出文件
如有可能,提供清除脚本及返回基准线的方法



下面详细讲述了每一条建议以及用于说明问题的脚本。若要下载此脚本,请参阅本文后面的参考资料部分。


1. 记录运行脚本的先决条件和主要步骤
记录,尤其是以有自述标题的单个文件(例如 "README-testing.txt")记录功能测试的主要想法是很重要的,包括,如先决条件、服务器和客户机的设置、脚本遵循的整个(或详细的)步骤、如何检查脚本的成功/失败、如何执行清除和重新启动测试。


2. 将操作分成若干个逻辑组
如果仅仅执行数量非常少的操作,可以将它们全部放在一个简单的 shell 脚本中。


但是,如果需要执行一些数量很多的操作,那最好是将它们分成若干个逻辑集合,例如将一些服务器操作放在一个文件而将客户机操作放在在另一个文件中。通过这种方法,划分适当的颗粒度来执行测试和维护测试。


3. 基于一般方案制定执行步骤
一旦决定对操作进行分组,需要根据一般方案考虑执行操作的步骤。此想法是模拟实际生活中最终用户的情形。作为一个总体原则,只需集中测试 80% 最常调用函数的 20% 用法即可。


例如,假设应用程序要求 3 个测试组以某个特定的顺序排列。每个测试组可以放在一个带有自我描述文件名(如果可能)的文件中,并用号码来帮助识别每个文件的顺序,例如:
  1. 1.  fvt-setup-1:    To perform initial setup.
  2. 2.  fvt-server-2:   To perform server commands.
  3. 3.  fvt-client-3:   To perform client commands.
  4. 4.  fvt-cleanup:    To cleanup the temporary files,
  5.                         in order to prepare for the repetition
  6.                         of the above test cases.
复制代码
4. 在每个 shell 脚本中提供注释和说明
在每个 shell 脚本的头文件中提供相关的注释和说明是一个良好的编码习惯。这样的话,当另一个测试者运行该脚本时,测试者就能清楚地了解每个脚本中测试的范围、所有先决条件和警告。


下面是一个 Bash 脚本 "test-bucket-1" 的示例 。

  1. #!/bin/bash
  2. #
  3. # Name: test-bucket-1
  4. #
  5. # Purpose:
  6. #    Performs the test-bucket number 1 for Product X.
  7. #    (Actually, this is a sample shell script,
  8. #     which invokes some system commands
  9. #     to illustrate how to construct a Bash script)
  10. #
  11. # Notes:
  12. # 1) The environment variable TEST_VAR must be set
  13. #    (as an example).
  14. # 2) To invoke this shell script and redirect standard
  15. #    output and standard error to a file (such as
  16. #    test-bucket-1.out) do the following (the -s flag
  17. #    is "silent mode" to avoid prompts to the user):
  18. #
  19. #    ./test-bucket-1  -s  2>&1  | tee test-bucket-1.out
  20. #
  21. # Return codes:
  22. #  0 = All commands were successful
  23. #  1 = At least one command failed, see the output file
  24. #      and search for the keyword "ERROR".
  25. #
  26. ########################################################
复制代码
5. 做一个初始备份以创建基准线
您可能需要多次执行功能测试。第一次运行它时,也许会找到脚本或进程中的一些错误。因而,为了避免因从头重新创建服务器环境而浪费大量时间 -- 特别是如果涉及到数据库 -- 您在测试之前或许想做个备份。


在运行完功能测试之后,就可以从备份中恢复服务器了,同时也为下一轮测试做好了准备。


6. 检查输入参数和环境变量
最好校验一下输入参数,并检查环境变量是否设置正确。如果有问题,显示问题的原因及其修复方法,然后终止脚本。


当测试者准备运行脚本,而此时如果没有正确设置脚本所调用的环境变量,但由于发现及时,终止了脚本,那测试者会相当感谢。没有人喜欢等待脚本执行了很久却发现没有正确设置变量。
  1. # --------------------------------------------
  2. # Main routine for performing the test bucket
  3. # --------------------------------------------
  4. CALLER=`basename $0`         # The Caller name
  5. SILENT="no"                  # User wants prompts
  6. let "errorCounter = 0"
  7. # ----------------------------------
  8. # Handle keyword parameters (flags).
  9. # ----------------------------------
  10. # For more sophisticated usage of getopt in Linux,
  11. # see the samples file: /usr/lib/getopt/parse.bash
  12. TEMP=`getopt hs $*`
  13. if [ $? != 0 ]
  14. then
  15. echo "$CALLER: Unknown flag(s)"
  16.   usage
  17. fi
  18. # Note quotes around `$TEMP': they are essential!
  19. eval set -- "$TEMP"
  20. while true                  
  21. do
  22.   case "$1" in
  23.    -h) usage "HELP";    shift;; # Help requested
  24.    -s) SILENT="yes";    shift;; # Prompt not needed
  25.    --) shift ; break ;;
  26.    *) echo "Internal error!" ; exit 1 ;;
  27.   esac
  28. done
  29. # ------------------------------------------------
  30. # The following environment variables must be set
  31. # ------------------------------------------------
  32. if [ -z "$TEST_VAR" ]
  33. then
  34.   echo "Environment variable TEST_VAR is not set."
  35.   usage
  36. fi
复制代码
关于此脚本的说明如下:

使用语句 CALLER=`basename $0` 可以得到正在运行的脚本名称。这样的话,无须在脚本中硬编码脚本名称。因此当复制脚本时,采用新派生的脚本可以减少工作量。
调用脚本时,语句 TEMP=`getopt hs $*` 用于得到输入变量(例如 -h 代表帮助,-s 代表安静模式)。
语句 [ -z "$X" ] 和 echo "The environment variable X is not set." 以及 usage 都是用于检测字符串是否为空 (-z),如果为空,随后就执行 echo 语句以显示未设置字符串并调用下面要讨论的 "usage" 函数。
若脚本未使用标志,可以使用变量 "$#",它可以返回正在传递到脚本的变量数量。

7. 尝试提供"usage"反馈
脚本中使用 "usage" 语句是个好主意,它用来说明如何使用脚本。
  1. # ----------------------------
  2. # Subroutine to echo the usage
  3. # ----------------------------
  4. usage()
  5. {
  6. echo "USAGE: $CALLER [-h] [-s]"
  7. echo "WHERE: -h = help       "
  8. echo "       -s = silent (no prompts)"
  9. echo "PREREQUISITES:"
  10. echo "* The environment variable TEST_VAR must be set,"
  11. echo "* such as: "
  12. echo "   export TEST_VAR=1"
  13. echo "$CALLER: exiting now with rc=1."
  14.   exit 1
  15. }
复制代码
调用脚本时,使用"-h"标志可以调用 "usage" 语句,如下所示:
./test-bucket-1 -h


8. 尝试使用"安静"的运行模式
您或许想让脚本有两种运行模式:

在 "verbose" 模式(您也许想将此作为缺省值)中提示用户输入值,或者只需按下 Enter 继续运行。
在 "silent" 模式中将不提示用户输入数据。

下列摘录说明了在安静模式下运用所调用标志 "-s" 来运行脚本:
  1. # -------------------------------------------------
  2. # Everything seems OK, prompt for confirmation
  3. # -------------------------------------------------
  4. if [ "$SILENT" = "yes" ]
  5. then
  6. RESPONSE="y"
  7. else
  8. echo "The $CALLER will be performed."
  9. echo "Do you wish to proceed [y or n]? "
  10. read RESPONSE                  # Wait for response
  11. [ -z "$RESPONSE" ] && RESPONSE="n"
  12. fi
  13. case "$RESPONSE" in
  14. [yY]|[yY][eE]|[yY][eE][sS])
  15. ;;
  16. *)
  17.   echo "$CALLER terminated with rc=1."
  18.   exit 1
  19. ;;
  20. esac
复制代码

9. 当出现错误时,提供一个函数终止脚本
遇到严重错误时,提供一个中心函数以终止运行的脚本不失为一个好主意。此函数还可提供附加的说明,用于指导在此情况下应做些什么:
  1. # ----------------------------------
  2. # Subroutine to terminate abnormally
  3. # ----------------------------------
  4. terminate()
  5. {
  6. echo "The execution of $CALLER was not successful."
  7. echo "$CALLER terminated, exiting now with rc=1."
  8. dateTest=`date`
  9. echo "End of testing at: $dateTest"
  10. echo ""
  11.   exit 1
  12. }
复制代码
10. 如有可能,提供可以执行简单任务的函数
例如,不使用许多很长的行命令,如:
  1. # --------------------------------------------------
  2. echo ""
  3. echo "Creating Access lists..."
  4. # --------------------------------------------------
  5. Access -create -component Development -login ted -authority plead -verbose
  6.   if [ $? -ne 0 ]
  7.   then
  8.   echo "ERROR found in Access -create -component Development -login ted
  9.     -authority plead"
  10.      let "errorCounter = errorCounter + 1"
  11. fi
  12. Access -create -component Development -login pat -authority general -verbose
  13.   if [ $? -ne 0 ]
  14.   then
  15.   echo "ERROR found in Access -create -component Development -login pat
  16.     -authority general"
  17.      let "errorCounter = errorCounter + 1"
  18. fi
  19. Access -create -component Development -login jim -authority general -verbose
  20.   if [ $? -ne 0 ]
  21.   then
  22.   echo "ERROR found in Access -create -component Development -login jim
  23.     -authority general"
  24.      let "errorCounter = errorCounter + 1"
  25. fi
复制代码
……而是创建一个如下所示的函数,此函数也可以处理返回码,如果有必要,还可以增加错误计数器:
  1. CreateAccess()
  2. {
  3. Access -create -component $1 -login $2 -authority $3 -verbose
  4.   if [ $? -ne 0 ]
  5.   then
  6.   echo "ERROR found in Access -create -component $1 -login $2 -authority $3"
  7.      let "errorCounter = errorCounter + 1"
  8. fi
  9. }
复制代码
……然后,以易读和易扩展的方式调用此函数:
  1. # -------------------------------------------
  2. echo ""
  3. echo "Creating Access lists..."
  4. # -------------------------------------------
  5. CreateAccess Development ted    projectlead
  6. CreateAccess Development pat    general
  7. CreateAccess Development jim    general
复制代码
11. 当显示正在生成的输出时,捕获每个脚本的输出
如果脚本不能自动地将输出发送到文件的话,可以利用 Bash shell 的一些函数来捕获所执行脚本的输出,如:

./test-bucket-1  -s  2>&1  | tee test-bucket-1.out

让我们来分析上面的命令:

"2>&1" 命令:

使用 "2>&1" 将标准错误重定向到标准输出。字符串 "2>&1" 表明任何错误都应送到标准输出,即 UNIX/Linux 下 2 的文件标识代表标准错误,而 1 的文件标识代表标准输出。如果不用此字符串,那么所捕捉到的仅仅是正确的信息,错误信息会被忽略。

管道 "|" 和 "tee" 命令:

UNIX/Linux 进程和简单的管道概念很相似。既然这样,可以做一个管道将期望脚本的输出作为管道的输入。下一个要决定的是如何处理管道所输出的内容。在这种情况下,我们会将它捕获到输出文件中,在此示例中将之称为 "test-bucket-1.out"。


但是,除了要捕获到输出结果外,我们还想监视脚本运行时产生的输出。为达到此目的,我们连接允许两件事同时进行的 "tee" (T- 形管道):将输出结果放在文件中同时将输出结果显示在屏幕上。 其管道类似于:
  1. process --> T ---> output file
  2.              |
  3.              V
  4.            screen
复制代码

如果只想捕获输出结果而不想在屏幕上看到输出结果,那可以忽略多余的管道: ./test-bucket-1 -s 2>&1 > test-bucket-1.out


假若这样,相类似的管道如下:

process --> output file

12. 在每个脚本内,捕获每个行命令所返回码
决定功能测试成功还是失败的一种方法是计算已失败行命令的数量,即返回码不是 0。变量 "$?" 提供最近所调用命令的返回码;在下面的示例中,它提供了执行 "ls" 命令的返回码。
  1. # -------------------------------------------
  2. # The commands are called in a subroutine
  3. # so that return code can be
  4. # checked for possible errors.
  5. # -------------------------------------------
  6. ListFile()
  7. {
  8. echo "ls -al $1"
  9. ls -al $1
  10.   if [ $? -ne 0 ]
  11.   then
  12.   echo "ERROR found in: ls -al $1"
  13.      let "errorCounter = errorCounter + 1"
  14. fi
  15. }
复制代码
13. 记录失败事务的次数
在功能测试中决定其成功或失败的一个方法是计算返回值不是 0 的行命令数量。但是,从我个人的经验而言,我习惯于在我的 Bash shell 脚本中仅使用字符串而不是整数。在我所参考的手册中没有清楚地说明如何使用整数,这就是我为什么想在此就关于如何使用整数和计算错误(行命令失败)数量的方面多展开讲的原因:
首先,需要按如下方式对计数器变量进行初始化:

let "errorCounter = 0"

然后,发出行命令并使用 $? 变量捕获返回码。如果返回码不是 0,那么计数器增加 1(见蓝色粗体语句):
  1. ListFile()
  2. {
  3. echo "ls -al $1"
  4. ls -al $1
  5.   if [ $? -ne 0 ]
  6.   then
  7.   echo "ERROR found in: ls -al $1"
  8.   let "errorCounter = errorCounter + 1"
  9. fi
  10. }
复制代码

顺便说一下,与其它变量一样,可以使用 "echo" 显示整数变量。


14. 在输出文件中,为了容易标识,突出显示错误消息
当遇到错误(或失败的事务)时,除了错误计数器的数量会增加外,最好标识出此处有错。较理想的做法是,字符串有一个如 ERROR 或与之相似的子串(见蓝色粗体的语句),这个子串允许测试者很快地在输出文件中查找到错误。此输出文件可能很大,而且它对于迅速找到错误非常重要。

  1. ListFile()
  2. {
  3. echo "ls -al $1"
  4. ls -al $1
  5.   if [ $? -ne 0 ]
  6.   then
  7.   echo "ERROR found in: ls -al $1"
  8.      let "errorCounter = errorCounter + 1"
  9. fi
  10. }
复制代码

15. 如有可能,"实时"生成文件
在某些情况下,有必要处理应用程序使用的文件。可以使用现有文件,也可以在脚本中添加语句来创建文件。如果要使用的文件很长,那最好将其作为独立的实体。如果文件很小而且内容简单或不相关(重要的一点是文本文件而不考虑它的内容),那就可以决定"实时"创建这些临时文件。


下面几行代码显示如何"实时"创建临时文件:
  1. cd $HOME/fvt
  2. echo "Creating file softtar.c"
  3. echo "Subject: This is softtar.c" >  softtar.c
  4. echo "This is line 2 of the file" >> softtar.c
复制代码
第一个 echo 语句使用单个的 > 强行创建新文件。第二个 echo 语句使用两个 >> 将数据附加到现有文件的后面。顺便说一下,如果该文件不存在,那么会创建一个文件。


16. 在执行脚本的过程中提供反馈
最好在脚本中包含 echo 语句以表明它执行的逻辑进展状况。可以添加一些能迅速表明输出目的的语句。


如果脚本要花费一些时间执行,那或许应在执行脚本的开始和结束的地方打印时间。这样可以计算出所花费的时间。


在脚本样本中,一些提供进展说明的 echo 语句如下所示:
  1. # --------------------------------------------
  2. echo "Subject: Product X, FVT testing"
  3. dateTest=`date`
  4. echo "Begin testing at: $dateTest"
  5. echo ""
  6. echo "Testcase: $CALLER"
  7. echo ""
  8. # --------------------------------------------
  9. # --------------------------------------------
  10. echo ""
  11. echo "Listing files..."
  12. # --------------------------------------------
  13. # The following file should be listed:
  14. ListFile   $HOME/.profile
  15. ...
  16. # --------------------------------------------
  17. echo ""
  18. echo "Creating file 1"
  19. # --------------------------------------------
复制代码

17. 提供脚本执行的摘要
如果正在计算错误或失败事务的次数,那最好表明是否有错误。此方法使得测试者在看到输出文件的最后能迅速地辨认出是否存在错误。


在下面的脚本示例中,代码语句提供了上述脚本的执行摘要:
  1. # --------------
  2. # Exit
  3. # --------------
  4. if [ $errorCounter -ne 0 ]
  5. then
  6. echo ""
  7. echo "*** $errorCounter ERRORS found during ***"
  8. echo "*** the execution of this test case.  ***"
  9. terminate
  10. else
  11. echo ""
  12. echo "*** Yeah! No errors were found during ***"
  13. echo "*** the execution of this test case. Yeah! ***"
  14. fi
  15. echo ""
  16. echo "$CALLER complete."
  17. echo ""
  18. dateTest=`date`
  19. echo "End of testing at: $dateTest"
  20. echo ""
  21. exit 0
  22. # end of file
复制代码
18. 提供一个容易解释的输出文件
在脚本生成的实际输出中提供一些关键信息是非常有用的。那样,测试者就可以很容易地确定正在查看的文件是否与自己所做的相关以及它是否是当前产生的。附加的时间戳记对于是否是当前状态是很重要的。摘要报告对于确定是否有错误也是很有帮助的;如果有错误,那么测试者就必须搜索指定的关键字,例如 ERROR,并确认出个别失败的事务。

以下是一段输出文件样本的片段:
  1. Subject: CMVC 2.3.1, FVT testing, Common, Part 1
  2. Begin testing at: Tue Apr 18 12:50:55 EDT 2000   
  3.                                                 
  4. Database: DB2                                    
  5. Family:   cmpc3db2                              
  6. Testcase: fvt-common-1                           
  7.                                                 
  8. Creating Users...                                
  9. User pat was created successfully.               
  10. ...
  11. Well done! No errors were found during the
  12. execution of this test case :)
  13.                                                                            
  14. fvt-common-1 complete.                                                      
  15.                                                                            
  16. End of testing at: Tue Apr 18 12:56:33 EDT 2000
复制代码
当遇到错误时输出文件最后部分的示例如下所示:
  1. ERROR found in Report -view DefectView
  2. *** 1 ERRORS found during the execution of this test case. ***           
  3. The populate action for the CMVC family was not successful.               
  4. Recreating the family may be necessary before
  5. running fvt-client-3 again, that is, you must use 'rmdb',
  6. 'rmfamily', 'mkfamily' and 'mkdb -d',      
  7. then issue: fvt-common-1 and optionally, fvt-server-2.                    
  8. fvt-client-3 terminated, exiting now with rc=1.                           
  9. End of testing at: Wed Jan 24 17:06:06 EST 2001
复制代码

19. 如有可能,提供清除脚本及返回基准线的方法
测试脚本可以生成临时文件;假若这样,最好能让脚本删除所有临时文件。这就会避免由于测试者也许没有删除所有临时文件而引起的错误,更糟糕的是将所需要的文件当作临时文件而删除了。

运行功能测试的 Bash shell 脚本
本节描述如何运用 Bash shell 脚本进行功能测试。假设您已经执行了在前面部分中所述步骤。


设置必要的环境变量
根据需要在 .profile 中或手工指定下列环境变量。该变量用于说明在脚本中如何处理,所需环境变量的验证必须在脚本执行前定义。

  export TEST_VAR=1

将 Bash shell 脚本复制到正确的目录下
Bash shell 脚本和相关文件需要复制到要进行功能测试的用户标识的目录结构下。

登录进某个帐户。您应该在主目录下。假设它是 /home/tester。
为测试案例创建目录:mkdir fvt
复制 Bash shell 脚本和相关文件。获取压缩文件(请参阅参考资料)并将其放在 $HOME 下。然后将其按下列方式解压:unzip trfvtbash.zip
为了执行这个文件,更改文件的许可权:chmod u+x *
更改名称以除去文件的后缀:mv test-bucket-1.bash test-bucket-1

运行脚本
执行下列步骤以运行脚本:

以测试者的用户标识登录
更改目录至所复制脚本的位置:cd $HOME/fvt
从 $HOME/fvt 运行脚本:./test-bucket-1 -s 2>&1 | tee test-bucket-1.out
看一下输出文件 "test-bucket-1.out" 的尾部并查看摘要报告的结论。
发表于 2003-4-22 14:25:08 | 显示全部楼层
顶,呵呵
发表于 2003-5-9 13:14:51 | 显示全部楼层
好东西

shell是linux的基础
不会shell就象 走路不穿鞋子一样
发表于 2003-5-28 23:27:12 | 显示全部楼层

re 最好能弄个pdf,方便打印

好东西呀



谢谢
发表于 2003-6-8 13:45:02 | 显示全部楼层
精!
发表于 2003-6-10 14:00:12 | 显示全部楼层
呵呵,挺有意思的,
有人初吗,给点见意啊:)
刚才弄了几下,感觉还可以:)
发表于 2003-6-15 14:46:22 | 显示全部楼层
此shel教程对于linux似乎老了一些!
有些语法特性似乎不能用了!l
发表于 2003-6-15 16:03:33 | 显示全部楼层
兄弟说得是!
不过基础就是最重要的!,如果我们掌握了最基础的shell知识,我想无论在什么linux平台上,都可以一展身手的!;)
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表