shell教程
OutOfMemory.CN技术专栏-> shell-> shell教程-> 33.2. Shell 包装

33.2. Shell 包装

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

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

sed awk 命令一般从命令行上以 sed -e 'commands'awk 'commands' 来调用. 把sed和awk的命令嵌入到Bash脚本里使调用变得更简单, 并且也可多次使用. 也可以综合地利用 sedawk 的功能, 例如管道(piping)连接sed 命令的输出到awk命令中. 保存为可执行的文件, 你可以用脚本编写的或修改的调用格式多次的调用它, 而不必在命令行上重复键入复杂的命令行.


例子 33-1. shell 包装

#!/bin/bash

# 这是一个把文件中的空行删除的简单脚本.
# 没有参数检查.
#
# 你可能想增加类似下面的代码:
#
# E_NOARGS=65
# if [ -z "$1" ]
# then
#  echo "Usage: `basename $0` target-file"
#  exit $E_NOARGS
# fi


# 就像从命令行调用下面的命令:
#    sed -e '/^$/d' filename
#

sed -e /^$/d "$1"
#  The '-e' 意味着后面跟的是编辑命令 (这是可选的).
#  '^' 匹配行的开头, '$' 则是行的结尾.
#  这个表达式匹配行首和行尾之间什么也没有的行,
#+ 即空白行.
#  'd'是删除命令.

#  引号引起命令行参数就允许在文件名中使用空白字符和特殊字符
#

#  注意这个脚本不能真正的修改目标文件.
#  如果你需要保存修改,就要重定向到某个输出文件里.

exit 0


例子 33-2. 稍微复杂一些的shell包装

#!/bin/bash

#  "subst", 把一个文件中的一个模式替换成一个模式的脚本
#
#  例如, "subst Smith Jones letter.txt".

ARGS=3         # 脚本要求三个参数.
E_BADARGS=65   # 传递了错误的参数个数给脚本.

if [ $# -ne "$ARGS" ]
# 测试脚本参数的个数 (这是好办法).
then
  echo "Usage: `basename $0` old-pattern new-pattern filename"
  exit $E_BADARGS
fi

old_pattern=$1
new_pattern=$2

if [ -f "$3" ]
then
    file_name=$3
else
    echo "File \"$3\" does not exist."
    exit $E_BADARGS
fi


#  这儿是实现功能的代码.

# -----------------------------------------------
sed -e "s/$old_pattern/$new_pattern/g" $file_name
# -----------------------------------------------

#  's' 在sed命令里表示替换,
#+ /pattern/表示匹配地址.
#  The "g"也叫全局标志使sed会在每一行有$old_pattern模式出现的所有地方替换,
#+ 而不只是匹配第一个出现的地方.
#  参考'sed'的有关书籍了解更深入的解释.

exit 0    # 脚本成功调用会返回 0.


例子 33-3. 写到日志文件的shell包装

#!/bin/bash
#  普通的shell包装,执行一个操作并记录在日志里
#

# 需要设置下面的两个变量.
OPERATION=
#         可以是一个复杂的命令链,
#+        例如awk脚本或是管道 . . .
LOGFILE=
#         不管怎么样,命令行参数还是要提供给操作的.


OPTIONS="$@"


# 记录操作.
echo "`date` + `whoami` + $OPERATION "$@"" >> $LOGFILE
# 现在, 执行操作.
exec $OPERATION "$@"

# 在操作之前记录日志是必须的.
# 为什么?


例子 33-4. 包装awk的脚本

#!/bin/bash
# pr-ascii.sh: 打印 ASCII 码的字符表.

START=33   # 可打印的 ASCII 字符的范围 (十进制).
END=125

echo " Decimal   Hex     Character"   # 表头.
echo " -------   ---     ---------"

for ((i=START; i<=END; i++))
do
  echo $i | awk '{printf("  %3d       %2x         %c\n", $1, $1, $1)}'
# 在这个上下文,不会运行Bash的内建printf命令:
#     printf "%c" "$i"
done

exit 0


#  Decimal   Hex     Character
#  -------   ---     ---------
#    33       21         !
#    34       22         "
#    35       23         #
#    36       24         $
#
#    . . .
#
#   122       7a         z
#   123       7b         {
#   124       7c         |
#   125       7d         }


#  把脚本的输出重定向到一个文件或是管道给more命令来查看:
#+   sh pr-asc.sh | more


例子 33-5. 另一个包装awk的脚本

#!/bin/bash

# 给目标文件增加一列由数字指定的列.

ARGS=2
E_WRONGARGS=65

if [ $# -ne "$ARGS" ] # 检查命令行参数个数是否正确.
then
   echo "Usage: `basename $0` filename column-number"
   exit $E_WRONGARGS
fi

filename=$1
column_number=$2

#  传递shell变量给脚本的awk部分需要一点技巧.
#  方法之一是在awk脚本中使用强引用来引起bash脚本的变量
#
#     $'$BASH_SCRIPT_VAR'
#      ^                ^
#  这个方法在下面的内嵌的awk脚本中出现.
#  参考awk文档了解更多的细节.

# 多行的awk脚本调用格式为:  awk ' ..... '


# 开始 awk 脚本.
# -----------------------------
awk '

{ total += $'"${column_number}"'
}
END {
     print total
}

' "$filename"
# -----------------------------
# awk脚本结束.


#   把shell变量传递给awk变量可能是不安全的,
#+  因此Stephane Chazelas提出了下面另外一种方法:
#   ---------------------------------------
#   awk -v column_number="$column_number" '
#   { total += $column_number
#   }
#   END {
#       print total
#   }' "$filename"
#   ---------------------------------------


exit 0

对于要实现这些功能而只用一种多合一的瑞士军刀应该用Perl. Perl兼有sedawk的能力, 并且具有C的一个很大的子集. 它是标准的并支持面向对象编程的方方面面,甚至是很琐碎的东西. 短的Perl脚本也可以嵌入到shell脚本中去,以至于有些人宣称Perl能够完全地代替shell编程(本文作者对此持怀疑态度).


例子 33-6. 把Perl嵌入Bash脚本

#!/bin/bash

# Shell命令可以包含 Perl 脚本.
echo "This precedes the embedded Perl script within \"$0\"."
echo "==============================================================="

perl -e 'print "This is an embedded Perl script.\n";'
# 像sed脚本, Perl 也使用"-e"选项.

echo "==============================================================="
echo "However, the script may also contain shell and system commands."

exit 0

把Bash脚本和Perl脚本放在同一个文件是可能的. 依赖于脚本如何被调用, 要么是Bash部分被执行,要么是Perl部分被执行.


例子 33-7. Bash 和 Perl 脚本联合使用

#!/bin/bash
# bashandperl.sh

echo "Greetings from the Bash part of the script."
# 下面可以有更多的Bash命令.

exit 0
# 脚本的Bash部分结束.

# =======================================================

#!/usr/bin/perl
# 脚本的这个部分必须用-x选项来调用.

print "Greetings from the Perl part of the script.\n";
# 下面可以有更多的Perl命令.

# 脚本的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.

© 内存溢出 OutOfMemory.CN