33.2. Shell 包装

包装脚本是指嵌有一个系统命令和程序的脚本,也保存了一组传给该命令的参数. [1] 包装脚本使原本很复杂的命令行简单化. 这对 sedawk 特别有用.

sed awk 命令一般从命令行上以 sed -e 'commands'awk 'commands' 来调用. 把sed和awk的命令嵌入到Bash脚本里使调用变得更简单, 并且也可多次使用. 也可以综合地利用 sedawk 的功能, 例如管道(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兼有sedawk的能力, 并且具有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.