| Advanced Bash-Scripting Guide: An in-depth exploration of the art of shell scripting | ||
|---|---|---|
| Prev | Chapter 33. Miscellany | Next | 
包装脚本是指嵌有一个系统命令和程序的脚本,也保存了一组传给该命令的参数. [1] 包装脚本使原本很复杂的命令行简单化. 这对 sed 和 awk 特别有用.
sed 和 awk 命令一般从命令行上以 sed -e 'commands' 和 awk 'commands' 来调用. 把sed和awk的命令嵌入到Bash脚本里使调用变得更简单, 并且也可多次使用. 也可以综合地利用 sed 和 awk 的功能, 例如管道(piping)连接sed 命令的输出到awk命令中. 保存为可执行的文件, 你可以用脚本编写的或修改的调用格式多次的调用它, 而不必在命令行上重复键入复杂的命令行.
例子 33-1. shell 包装
| 1 #!/bin/bash 2 3 # 这是一个把文件中的空行删除的简单脚本. 4 # 没有参数检查. 5 # 6 # 你可能想增加类似下面的代码: 7 # 8 # E_NOARGS=65 9 # if [ -z "$1" ] 10 # then 11 # echo "Usage: `basename $0` target-file" 12 # exit $E_NOARGS 13 # fi 14 15 16 # 就像从命令行调用下面的命令: 17 # sed -e '/^$/d' filename 18 # 19 20 sed -e /^$/d "$1" 21 # The '-e' 意味着后面跟的是编辑命令 (这是可选的). 22 # '^' 匹配行的开头, '$' 则是行的结尾. 23 # 这个表达式匹配行首和行尾之间什么也没有的行, 24 #+ 即空白行. 25 # 'd'是删除命令. 26 27 # 引号引起命令行参数就允许在文件名中使用空白字符和特殊字符 28 # 29 30 # 注意这个脚本不能真正的修改目标文件. 31 # 如果你需要保存修改,就要重定向到某个输出文件里. 32 33 exit 0 | 
例子 33-2. 稍微复杂一些的shell包装
| 1 #!/bin/bash 2 3 # "subst", 把一个文件中的一个模式替换成一个模式的脚本 4 # 5 # 例如, "subst Smith Jones letter.txt". 6 7 ARGS=3 # 脚本要求三个参数. 8 E_BADARGS=65 # 传递了错误的参数个数给脚本. 9 10 if [ $# -ne "$ARGS" ] 11 # 测试脚本参数的个数 (这是好办法). 12 then 13 echo "Usage: `basename $0` old-pattern new-pattern filename" 14 exit $E_BADARGS 15 fi 16 17 old_pattern=$1 18 new_pattern=$2 19 20 if [ -f "$3" ] 21 then 22 file_name=$3 23 else 24 echo "File \"$3\" does not exist." 25 exit $E_BADARGS 26 fi 27 28 29 # 这儿是实现功能的代码. 30 31 # ----------------------------------------------- 32 sed -e "s/$old_pattern/$new_pattern/g" $file_name 33 # ----------------------------------------------- 34 35 # 's' 在sed命令里表示替换, 36 #+ /pattern/表示匹配地址. 37 # The "g"也叫全局标志使sed会在每一行有$old_pattern模式出现的所有地方替换, 38 #+ 而不只是匹配第一个出现的地方. 39 # 参考'sed'的有关书籍了解更深入的解释. 40 41 exit 0 # 脚本成功调用会返回 0. | 
例子 33-3. 写到日志文件的shell包装
| 1 #!/bin/bash 2 # 普通的shell包装,执行一个操作并记录在日志里 3 # 4 5 # 需要设置下面的两个变量. 6 OPERATION= 7 # 可以是一个复杂的命令链, 8 #+ 例如awk脚本或是管道 . . . 9 LOGFILE= 10 # 不管怎么样,命令行参数还是要提供给操作的. 11 12 13 OPTIONS="$@" 14 15 16 # 记录操作. 17 echo "`date` + `whoami` + $OPERATION "$@"" >> $LOGFILE 18 # 现在, 执行操作. 19 exec $OPERATION "$@" 20 21 # 在操作之前记录日志是必须的. 22 # 为什么? | 
例子 33-4. 包装awk的脚本
|    1 #!/bin/bash
   2 # pr-ascii.sh: 打印 ASCII 码的字符表.
   3 
   4 START=33   # 可打印的 ASCII 字符的范围 (十进制).
   5 END=125
   6 
   7 echo " Decimal   Hex     Character"   # 表头.
   8 echo " -------   ---     ---------"
   9 
  10 for ((i=START; i<=END; i++))
  11 do
  12   echo $i | awk '{printf("  %3d       %2x         %c\n", $1, $1, $1)}'
  13 # 在这个上下文,不会运行Bash的内建printf命令:
  14 #     printf "%c" "$i"
  15 done
  16 
  17 exit 0
  18 
  19 
  20 #  Decimal   Hex     Character
  21 #  -------   ---     ---------
  22 #    33       21         !
  23 #    34       22         "
  24 #    35       23         #
  25 #    36       24         $
  26 #
  27 #    . . .
  28 #
  29 #   122       7a         z
  30 #   123       7b         {
  31 #   124       7c         |
  32 #   125       7d         }
  33 
  34 
  35 #  把脚本的输出重定向到一个文件或是管道给more命令来查看:
  36 #+   sh pr-asc.sh | more | 
例子 33-5. 另一个包装awk的脚本
|    1 #!/bin/bash
   2 
   3 # 给目标文件增加一列由数字指定的列.
   4 
   5 ARGS=2
   6 E_WRONGARGS=65
   7 
   8 if [ $# -ne "$ARGS" ] # 检查命令行参数个数是否正确.
   9 then
  10    echo "Usage: `basename $0` filename column-number"
  11    exit $E_WRONGARGS
  12 fi
  13 
  14 filename=$1
  15 column_number=$2
  16 
  17 #  传递shell变量给脚本的awk部分需要一点技巧.
  18 #  方法之一是在awk脚本中使用强引用来引起bash脚本的变量
  19 #
  20 #     $'$BASH_SCRIPT_VAR'
  21 #      ^                ^
  22 #  这个方法在下面的内嵌的awk脚本中出现.
  23 #  参考awk文档了解更多的细节.
  24 
  25 # 多行的awk脚本调用格式为:  awk ' ..... '
  26 
  27 
  28 # 开始 awk 脚本.
  29 # -----------------------------
  30 awk '
  31 
  32 { total += $'"${column_number}"'
  33 }
  34 END {
  35      print total
  36 }     
  37 
  38 ' "$filename"
  39 # -----------------------------
  40 # awk脚本结束.
  41 
  42 
  43 #   把shell变量传递给awk变量可能是不安全的,
  44 #+  因此Stephane Chazelas提出了下面另外一种方法:
  45 #   ---------------------------------------
  46 #   awk -v column_number="$column_number" '
  47 #   { total += $column_number
  48 #   }
  49 #   END {
  50 #       print total
  51 #   }' "$filename"
  52 #   ---------------------------------------
  53 
  54 
  55 exit 0 | 
对于要实现这些功能而只用一种多合一的瑞士军刀应该用Perl. Perl兼有sed和awk的能力, 并且具有C的一个很大的子集. 它是标准的并支持面向对象编程的方方面面,甚至是很琐碎的东西. 短的Perl脚本也可以嵌入到shell脚本中去,以至于有些人宣称Perl能够完全地代替shell编程(本文作者对此持怀疑态度).
例子 33-6. 把Perl嵌入Bash脚本
| 1 #!/bin/bash 2 3 # Shell命令可以包含 Perl 脚本. 4 echo "This precedes the embedded Perl script within \"$0\"." 5 echo "===============================================================" 6 7 perl -e 'print "This is an embedded Perl script.\n";' 8 # 像sed脚本, Perl 也使用"-e"选项. 9 10 echo "===============================================================" 11 echo "However, the script may also contain shell and system commands." 12 13 exit 0 | 
把Bash脚本和Perl脚本放在同一个文件是可能的. 依赖于脚本如何被调用, 要么是Bash部分被执行,要么是Perl部分被执行.
例子 33-7. Bash 和 Perl 脚本联合使用
| 1 #!/bin/bash 2 # bashandperl.sh 3 4 echo "Greetings from the Bash part of the script." 5 # 下面可以有更多的Bash命令. 6 7 exit 0 8 # 脚本的Bash部分结束. 9 10 # ======================================================= 11 12 #!/usr/bin/perl 13 # 脚本的这个部分必须用-x选项来调用. 14 15 print "Greetings from the Perl part of the script.\n"; 16 # 下面可以有更多的Perl命令. 17 18 # 脚本的Perl部分结束. | 
| bash$ bash bashandperl.sh Greetings from the Bash part of the script. bash$ perl -x bashandperl.sh Greetings from the Perl part of the script. | 
| [1] | 事实上,相当数量的Linux软件工具包是shell包装脚本. 例如/usr/bin/pdf2ps, /usr/bin/batch, 和 /usr/X11R6/bin/xmkmf. |