| 
 | 
 
 
 楼主 |
发表于 2009-12-9 09:31
|
显示全部楼层
 
 
 
6. 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 < 
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命令进行重命名。 
 
4)函数 
如果您写了一些稍微复杂一些的程序,您就会发现在程序中可能在几个地方使用了相同的代码,并且您也会发现,如果我们使用了函数,会方便很多。一个函数是这个样子的: 
functionname() 
{ 
# inside the body $1 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 < 
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 "$1" ] && help 
[ "$1" = "-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 < 
This is a generic command line parser demo. 
USAGE EXAMPLE: cmdparser -l hello -f -- -somefile1 somefile2 
HELP 
 exit 0 
} 
while [ -n "$1" ]; do 
case $1 in 
  -h) help;shift 1;; # function help is called 
  -f) opt_f=1;shift 1;; # variable opt_f is set 
  -l) opt_l=$2;shift 2;; # -l 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系统的惯例,首先输入的应该是包含减号的参数. 
第2部分 实例 
现在我们来讨论编写一个脚本的一般步骤。任何优秀的脚本都应该具有帮助和输入参数。并且写一个伪脚本(framework.sh),该脚本包含了大多数脚本都需要的框架结构,是一个非常不错的主意。这时候,在写一个新的脚本时我们只需要执行一下copy命令: 
cp framework.sh myscript 
 然后再插入自己的函数。 
  让我们再看两个例子: 
  二进制到十进制的转换 
  脚本 b2d 将二进制数 (比如 1101) 转换为相应的十进制数。这也是一个用expr命令进行数学运算的例子: 
#!/bin/sh 
# vim: set sw=4 ts=4 et: 
help() 
{ 
 cat < 
b2h -- convert binary to decimal 
USAGE: b2h [-h] binarynum 
OPTIONS: -h help text 
EXAMPLE: b2h 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 
  numofcharminus1=`expr $numofchar "-" 1` 
  # now cut all but the last char: 
  rval=`echo -n "$1" | cut -b 0-${numofcharminus1}` 
} 
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" 
binnumorig="$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 < 
rotatefile -- 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 
  exit 0 
} 
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; do 
  if [ -f "$filen.$n" ]; 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 
touch $filen 
  这个脚本是如何工作的呢?在检测用户提供了一个文件名以后,我们进行一个9到1的循环。文件9被命名为10,文件8重命名为9等等。循环完成之后,我们将原始文件命名为文件1同时建立一个与原始文件同名的空文件。 
调试 
  最简单的调试命令当然是使用echo命令。您可以使用echo在任何怀疑出错的地方打印任何变量值。这也是绝大多数的shell程序员要花费80%的时间来调试程序的原因。Shell程序的好处在于不需要重新编译,插入一个echo命令也不需要多少时间。 
  shell也有一个真实的调试模式。如果在脚本"strangescript" 中有错误,您可以这样来进行调试: 
sh -x strangescript 
  这将执行该脚本并显示所有变量的值。 
  shell还有一个不需要执行脚本只是检查语法的模式。可以这样使用: 
sh -n your_script 
  这将返回所有语法错误 |   
 
 
 
 |