数据加载中……
shell编程基础

可用任何一种文字编辑器,如:emacs、vi 等来编写shell脚本,必须以
      #!/bin/sh开始   ---符号#!用来告诉系统执行该脚本的程序。
      要执行脚本须先使其可执行: chmod +x filename
      此后在该脚本所在目录下,输入./filename即可执行该脚本。
------------------------------------
一、变量赋值和引用
shell编程中,使用变量无需事先声明,同时变量名的命名遵循如下规则:
         1. 首个字符必须为字母(a-z,A-Z)
         2. 中间不能有空格,可以使用下划线( _ )
         3. 不能使用标点符合
         4. 不能使用bash里的关键字(可用help命令查看保留关键字)
给变量赋值: 变量名=值 # (Attention: Donot keep blank between the variable with the equal operator '=')
要取用一个变量值,$变量名 
                  #!/bin/sh
                  #对变量赋值;
                  a="hello world"     #等号两边均不能有空格存在
                  #打印变量a的值
                  echo "A is :" $a
有时候变量名可能会和其它文字混淆,比如: 
                   num =2       
                  echo  "this is the $numnd"       # 这是shell会去搜索变量numnd,实际上numnd变量并不存在
                  输出结果:this  is the
                  echo  "this is the ${num}nd"   # 这是shell会去搜索变量num
                  输出结果:this is the 2nd

shell脚本中有许多变量是系统自动设定的,我们将在用到这些变量时再做说明。除了只在脚本内有效的普通shell变量外,还有环境变量,即那些有export关键字处理过的变量。环境变量一般只在登录脚本中用到。

Shell里的流程控制
if 语句
                     if ......; then
                        .........
                  elif .......; then
                        ........
                   else
                        ........
                    fi
                     如:
                              #!/bin/sh
                              if  [ "$SHELL" = "/bin/bash" ]; then

                                    echo "your login shell is the bash (bourne again shell)"
                              else
                                    echo "your login shell is not bash but $SHELL"
                              fi
                     变量$SHELL包含有登录shell的名称,我们拿它和/bin/bash进行比较以判断当前使用的shell是否为bash。
                        
                  大多数情况下,可以使用测试命令来对条件进行测试,比如可以比较字符串、判断文件是否存在及是否可读等等...通常用"[]" 来表示条件测试,注意这里的空格很重要,要确保方括号前后的空格。
                  [ -f"somefile" ] :判断是否是一个文件
                  [  -x"/bin/ls" ] : 判断/bin/ls是否存在并有可执行权限
                  [ -n "$var" ] : 判断$var变量是否有值
                  [ "$a"="$b" ] : 判断$a和$b是否相等

执行man test可以查看所有测试表达式可以比较和判断的类型。

 “与” && 和“或”|| 操作符
                  例一、 
                        [ -f "/etc/shadow" ] && echo "This computer uses shadow passwords"  # 表示如果/etc/shadow文件存在,则打印 "This computer uses shadow passwords" 
                  例二、
                        #!/bin/sh
                        mainfolder=/var/spool/mail/james
                        [ -r "$mainfolder" ] || {echo "Can not read $mailfolder" ; exit l; }
                        echo "$mailfolder has mail from :"
                        grep "^From " $mailfolder
                        #该脚本首先判断mailfolder是否可读,如果可读则打印该文件中的"From" 一行。如果不可读则或操作生效,打印错误信息后脚本退出。使用花括号以匿名函数的形式将两个命令放到一起作为一个命令使用。

case语句
可以用来匹配一个给定的字符串,而不是数字。
case ..... in
         ..... ) do something here ;;
esac
例如: smartzip 脚本,该脚本可以自动解压bzip,gzip 和zip 类型的压缩文件:
   #!/bin/sh
   
   ftype='file "$l"'
   case "$ftype" in                

         "$l : Zip archive"*)
            unzip "$l" ;;
         "$l : gzip compressed"*)
            gunzip "$l" ;;
         "$l : bzip2 compressed"*)
            bunzip2 "$l" ;;
         *) echo "File $l can not  be uncompressed with smartzip";;
         esac
# 该脚本使用了一个特殊变量$l ,该变量包含有传递给该脚本的第一个参数值。如运行: smartzip articles.zip ; $l 就是字符串 articles.zip

select语句
select 表达式是bash的一种扩展应用,擅长于交互式场合。用户可以从一组不同的值中进行选择:
select var in ....... ;do
      break;
done
      ...... now $var can be used ....
例如:
         #!/bin/sh                        #如果脚本运行出现 select :NOT Found 将#!/bin/sh 改为 #!/bin/bash
         echo "What is your favourite OS ?"
         select var in "Linux" "Gnu Hurd" "FreeBSD" "Other"; do
                  break;
         done
         echo "You have selected $var"
         该脚本运行结果如下:
               What is your favourite OS?
                1) Linux
                2) Gnu Hurd
                3) FreeBSD
                4)Other
               #? 1
               You have selected Linux
while/for循环
                  while .......; do
                           ........
                   done                  #关键字break用来跳出循环,而关键字continue则可以跳过一个循环的余下部分,直接跳到下一次循环中。
for循环会查看一个字符串表(字符串用空格分隔),并将其赋给一个变量:
            for var in .......; do
                  .............
            done
                                       Eg: 把A B C分别打印到屏幕上:
                                          #!/bin/sh
                                          for var in A B C ; do
                                                      echo "var is $var"
                                          done
                                       Eg: 实用的脚本showrpm,功能是打印一些RPM包的统计信息:
                                          #!/bin/sh
                                          # list a content summary of a number of RPM package
                                          # USAGE: showrpm  rpmfile1 rpmfile2 ......
                                          # EXAMPLE : showrpm  /cdrom/RedHat/RPMS/*.rpm
                                          for rpmpackage in $* ;  do            
                                          # 这里出现了第二个特殊变量 $* ,该变量包含有输入的所有命令行参数值。
                                                if [ -r "$rpmpackage" ];then

                                                      echo "======================== $rpmpackage ==========="
                                                      rpm -qi -p &rpmpackage
                                                else
                                                      echo "ERROR:cannot read file  $rpmpackage"
                                                fi
                                 done
Shell 里的一些特殊符号
引号
 在向程序传递任何参数之前,程序会扩展通配符和变量。这里所谓的扩展是指程序会把通配符(比如*)替换成适当的文件名,把变量替换成变量值。我们可以使用引号来防止这种扩展,先来看一个例子,假设在当前目录下有两个jpg文件:mail.jpg 和 tux.jpg

#!/bin/sh
echo *.jpg
运行结果为:
                        mail.jpg   tux.jpg
引号(单引号 和 双引号)可以防止通配符*的扩展:
#!/bin/sh
      echo  "*.jpg"
      echo  '*.jpg'
运行结果为:
               *.jpg
               *.jpg
其中单引号更严格一些,它可以防止任何变量扩展;而双引号可以防止通配符扩展但允许变量扩展;
            #!/bin/sh
            echo $SHELL
            echo "$SHELL "
            echo '$SHELL'
运行结果为:
            /bin/sh
            /bin/sh
           $SHELL
此外还有一种防止这种扩展的方法,即使用转义字符--反斜杠:\ 
            echo \*.jpg
            echo \$SHELL
运行结果:
            *.jpg
            $SHELL
Here documents
当要将几行文字传递给一个命令时,用here documents 是一种不错的方法。对每个脚本写一段帮助性的文字是很有用的,此时如果使用here documents就不必用echo函数一行行输出。Here document 以 << 开头,后面接上一个字符串,这个字符串还必须出现在here document的末尾。下例子我们对多个文件进行重命名,并且使用here documents 打印帮助:
#!/bin/sh
# we have less than 3 arguments. Print the help text:
if [ $# -lt 3 ] ; then
cat << HELP
ren -- renames a number of files using sed regular expressions USAGE: ren 'regexp' 'replacement' files....
EXAMPLE: rename all *.HTM files in *.html:
 ren 'HTM$' 'html' *.HTM
HELP
 exit 0
fi
OLD="$1"
NEW="$2"
# The shift command removes one argument from the list of
#command line arguments.
shift
shift
# $* contains now all the files:
for file in $*; do
 if [ -f "$file" ] ; then
  newfile='echo "$file" | sed "s/${OLD}/${NEW}/g"'
  if [ -f "$newfile" ]; then
   echo "ERROR: $newfile exists already"
  else
   echo "renaming $file to $newfile ..."
   mv "$file" "$newfile"
  fi
 fi
done
说明:第一个if表达式判断输入命令行参数是否小于3个(特殊变量$#表示包含参数的个数)。如果输入参数小于3个,则将帮助文字传递给cat命令,然后由cat命令将其打印在屏幕上。打印帮助文字后程序退出。如果输入等于或大于3个,我们将第一个参数赋值给变量OLD,第二个参数赋值给变量NEW。下一步,我们使用shift命令将第一个和第二个参数从参数列表中删除,这样原来的第三个参数就成为参数列表$*的第一个参数。然后我们开始循环,命令行参数列表被一个接一个地被赋值给变量$file.接着判断该文件是否存在,如果存在则通过sed命令搜索和替换来产生新的文件名。然后将反短斜线内命令结果赋值给newfile。这样我们就达到了目的:得到了旧文件名和新文件名。然后使用mv命令进行重命名。
Shell里的函数
如:
functionname()
{
 # inside the body $l is the first argument given to the function
 # $2 the second ...
 body
}
需要在每个脚本的开始对函数进行声明
下面是一个名为xtitlebar的脚本,它可以改变终端窗口的名称。这里使用一个名为help的函数,该函数在脚本中使用了两次:
#!/bin/sh
# vim: set sw=4 ts=4 et:
help()
{
cat << HELP
xtitlebar -- change the name of an xterm, gnome-terminal or kde konsole
USAGE: xtitlebar [ -h ] "string_for_titelbar"
OPTIONS: -h help text
EXAMPLE: xtitlebar "CVS"
HELP
exit 0
}
# in case of error or if -h is given we call the function help:
[ -z "$l" ] && help
[ "$l"="-h" ] && help
# send the escape sequence to change the xterm titelbar:
echo -e "33]0;$107"
#
在脚本中提供帮助是一种很好的编程习惯,可以方便使用和理解脚本。
命令行参数
我们已经见过$*和$1,$2,.......$9 等特殊变量,这些特殊变量包含了用户从命令行输入的参数。迄今为止,我们仅仅了解了一些简单的命令行语法(比如一些强制性的参数和查看帮助的-h选项)。但是在编写更复杂的程序时,你可能发现需要更多的自定义的选项。通常的惯例是在所有可选的参数之前加一个减号,后面再加上参数值(比如文件名)。
有好多方法可以实现对输入参数的分析,但是下面的使用case表达式的例子无疑是一个不错的方法。
#!/bin/sh
help()
   {
      cat << HELP
      This is a generic command line parser demo.
      USAGE EXAMPLE : cmdparser -l hello -f -- -somefile1 somefile2
      HELP
      exit0
   }
   while [ -n "%l" ] ; do
   case $1 in
               -h) help;shift l;;                      # function help is called
               -f) opt_f=l;shift l;;                # variable opt_f is set
               -l) opt_l=$2; shift 2;;          # -1 takes an argument -> shift by 2
              --) shift;break;;                   # end of options
             -*) echo "error: no such option $1. -h for help";exit 1;;
               *) break;;
      esac
      done
      echo "opt_f is $opt_f"
      echo "opt_l is $opt_l"
      echo "first arg is $1"
      echo "2nd arg is $2"
可以这样运行这个脚本:
      cmdparser -l hello -f -- -somefile1 somefile2
返回结果如下:
      opt_f is 1
      opt_l is hello
      first arg is -somefile1
      2nd arg is somefile2
这个脚本是如何工作的呢?脚本首先在所有输入命令行参数中进行循环,将输入参数与case表达式进行比较,如果匹配则设置一个变量并且移除该参数。根据unix系统的惯例,首先输入的应该是包含减号的参数。
Shell脚本示例
一般编程步骤
  任何优秀的脚本都应该具有帮助和输入参数。写一个框架脚本(framework.sh),该脚本包含了大多数脚本需要的框架结构,是一个非常不错的主意。这样一来,当我们开始编写新脚本时,可以先执行如下命令:
         cp framework.sh myscript
然后插入自己的函数
二进制到十进制的转换
脚本b2d将二进制数(比如 1101)转换为相应的十进制数。这是一个用expr命令进行数学运算的例子:
      $!/bin/sh
      # vim : set sw=4 ts=4 et:
      help()
      {
            cat << HELP
      b2b -- convert binary to decimal
      USAGE: b2d [ -h ] binarynum
      OPTIONS: -h help text
      EXAMPLE: b2d 111010
      will return 58
      HELP 
               exit 0
      }
      error()
      {
            # print an error and exit
            echo "$1"
            exit 1
      }
      lastchar()
      {
               # return the last character of a string in $rval
               if [ -z "$1" ]; then 
                     # empty string
                     rval=""
                     return
               fi
               # wc puts some space behind the output this is why we need sed:
               numofchar='echo -n "$1"' | wc -c | sed 's/  //g'
               # now cut out the last char
               rval='echo -n "$1" | cut -b $numofchar'
    }
   chop()
   {
            # remove the last character in string and return it in $rval
            if [ -z "$1" ]; then
                  # empty string
                  rval=""
                  return
            fi
           # wc puts some space behind the output this is why we need sed:

            numofchar='echo -n "$1" | wc -c | sed 's/   //g' '
            if  [  "$numofchar" = "1"  ]; then
                     # only one char in string
                     rval=""
                     return
               fi
               numofcharminusl='expr $numofchar "-" 1'
               # now cut all but the last char:
               rval='echo -n "$1" | cut -b -$numofcharminusl '
               #原来的 rval='echo -n "$1" | cut -b 0-${numofcharminusl} ' 运行时出错。
               #原因是cut从1开始技术,应该是cut -b 1-${numofcharminusl}
   }   
   while [  -n "$1"  ] ; do
   case $1 in
            -h) help;shift 1;;                      # function help is called
            --) shift;break;;                       # end of options
            -*) error "error: no such option $1. -h for help" ;;
             *) break;;
      esac
      done
      # The main program
      sum=0
      weight=1
      # one arg must be given:
      [ -z "$1" ]  && help
      binnum="$1"
      while [ -n "$binnum" ]; do
               lastchar "$binnum"
               if [ "$rval"="1" ]; then
                     sum='expr "$weight" "+"  "$sum" '
               fi
               # remove the last position in $binnum
               chop "$binnum"
               binnum="$rval"
               weight='expr "$weight" "*" 2'
          done
           echo "binary $binnumorig is decimal $sum"
            #
该脚本使用的算法是利用十进制和二进制数权值(1,2,4,8,16,......),比如二进制"10" 可以这样转换成十进制:
0*1+1*2=2
为了得到单个的二进制数我们是用了lastchar函数。该函数使用wc -c 计算字符个数,然后使用cut命令取出末尾一个字符。chop函数的功能是移除最后一个字符。
文件循环拷贝
你可能有这样的需求并一直都这么做:将所有发出邮件保持到一个文件中。但是过了几个月后,这个文件可能会变的很大以至于该文件的访问速度变慢;下面脚本rotatefile可以解决此问题。这个脚本可以重命名邮件保持文件(假设为outmail)为outmail.1,而原来的outmail.1 变成了outmail.2 等等。
#!/bin/sh
# vim: set sw=4 ts=4 et:
ver="0.1"
help()
{
      cat <<HELP
      rotatefiel -- rotate the file name
      USAGE: rotatefile [ -h ] filename
      OPTIONS: -h help text
      EXAMPLE: rotatefile out
      This will e.g rename out.2 to out.3, out.1 to out.2,out to out.1 and create an empty out-file
      The max number is 10
      version $ver
      HELP
      exit0
}
error()
      {
            echo "$1"
            exit 1
      }
      while [  -n "$1"  ]; do
            case $1 in
                  -h) help;shift 1;;
                  --) break;;
                  -*) echo "error: no such option $1. -h for help";exit 1;;
                   *) break;;
               esac
         done
         # input check:
         if [ -z "$1" ] ; then
                  error "ERROR: you must specify a file,use -h for help"
         fi
         filen="$1"
         # rename any.1,.2 etc file:
         for n in 9 8 7 6 5 4 3 2 1; then
               p='expr $n +1'
               echo "mv $filen.$n  $filen.$p"
               mv $filen.$n  $filen.$p
         fi
      done
      # rename the original file:
      if [  -f "$filen" ]; then
            echo "mv $filen $filen.1"
            mv $filen $filen.1
         fi
         echo touch $filen
这个脚本是如何工作的:在检验到用户提供了一个文件名之后,首先进行一个9到1的循环;文件名.9重命名为文件名.10,文件名.8重命名为文件名.9 等等。循环结束后,把原始文件命名为文件名.1,同时创建一个和原始文件同名的空文件(touch $filen)。
脚本调试
最简单的调试方法是使用echo。可以在任何怀疑出错的地方用echo打印变量值,这也是大部分shell程序员花费80%的时间用于调试的原因。shell脚本的好处在于无需重新编译,而插入一个echo也不需要多少时间。
shell也有一个真正的调试模式,如脚本"strangescript"出错,可以使用如下命令进行调试:
sh -x strangescript
上述命令会执行该脚本同时显示所有变量的值。
shell还有一个不执行脚本只检查语法的模式,命令如下:
sh -n your_script
这个命令会返回所有语法错误。
}

posted on 2010-09-25 17:26 doublezxh 阅读(209) 评论(0)  编辑 收藏 引用

只有注册用户登录后才能发表评论。