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. |