Bash脚本进阶指南
Repo
  • 正文
    • 第一部分 初见shell
      • 1. 为什么使用shell编程
      • 2. 和Sha-Bang(#!)一起出发
        • 2.1 调用一个脚本
        • 2.2 牛刀小试
    • 第二部分 shell基础
      • 3. 特殊字符
      • 4. 变量与参数
        • 4.1 变量替换
        • 4.2 变量赋值
        • 4.3 Bash弱类型变量
        • 4.4 特殊变量类型
      • 5. 引用
        • 5.1 引用变量
        • 5.2 转义
      • 6. 退出与退出状态
      • 7. 测试
        • 7.1 测试结构
        • 7.2 文件测试操作
        • 7.3 其他比较操作
        • 7.4 嵌套 if/then 条件测试
        • 7.5 牛刀小试
      • 8. 运算符相关话题
        • 8.1 运算符
        • 8.2 数字常量
        • 8.3 双圆括号结构
        • 8.4 运算符优先级
    • 第三部分 shell进阶
      • 9. 换个角度看变量
        • 9.1 内部变量
        • 9.2 变量类型标注:declare 与 typeset
          • 9.2.1 declare 的另类用法
        • 9.3 $RANDOM:生成随机数
      • 10. 变量处理
        • 10.1 字符串处理
          • 10.1.1 使用 awk 处理字符串
          • 10.1.2 参考资料
        • 10.2 参数替换
      • 11. 循环与分支
        • 11.1 循环
        • 11.2 嵌套循环
        • 11.3 循环控制
        • 11.4 测试与分支
      • 12. 命令替换
      • 13. 算术扩展
      • 14. 休息时间
    • 第四部分 命令
      • 15. 内建命令
        • 15.1 任务控制命令
      • 16. 外部筛选器,任务及命令
        • 16.1 基础命令
        • 16.2 复杂命令
        • 16.3 时间/日期命令
        • 16.4 文本处理命令
        • 16.5 文件与归档命令
        • 16.6 通信命令
        • 16.7 终端控制命令
        • 16.8 数学命令
        • 16.9 杂项命令
      • 17. 系统与管理命令
        • 17.1 分析一个系统脚本
    • 第五部分 高级话题
      • 18.正则表达式
        • 18.1正则表达式简介
        • 18.2文件名替换
      • 19. 嵌入文档
      • 20. I/O 重定向
        • 20.1 使用 exec
        • 20.2 重定向代码块
        • 20.3 应用程序
      • 21. 子shell
      • 22. 限制模式的Shell
      • 23. 进程替换
      • 24. 函数
        • 24.1 复杂函数和函数复杂性
        • 24.2 局部变量
        • 24.3 不适用局部变量的递归
      • 25. 别名
      • 26. 列表结构
      • 27. 数组
      • 28. 间接引用
      • 29. /dev 和 /proc
        • 29.1 /dev
        • 29.2 /proc
      • 30. 网络编程
      • 32. 调试
      • 33. 选项
      • 34. 陷阱
      • 36. 杂项
        • 36.1 交互和非交互shell以及脚本
        • 36.2 shell wrappers
        • 36.3 测试和比较的其他方法
        • 36.4 递归:调用自己的脚本
        • 36.5 “彩色”的脚本
        • 36.6 优化
        • 36.7 其他技巧
        • 36.8 安全问题
        • 36.9 可移植性问题
        • 36.10 Windows系统下的脚本
    • 38. 后记
      • 38.1 作者后记
      • 38.2 关于作者
      • 38.3 从哪里可以获得帮助
      • 38.4 用来制作这本书的工具
      • 38.5 致谢
      • 38.6 免责声明
  • 附录及索引
    • 参考文献
    • 附录
    • 索引
由 GitBook 提供支持
在本页
  • Example 36-1. shell wrapper
  • Example 36-2. 稍微复杂一点的 shell wrapper
  • Example 36-3. 一个通用的写日志文件的 shell wrapper
  • Example 36-4. 关于awk脚本的 shell wrapper
  • Example 36-5. 另一个关于awk的 shell wrapper
  • Example 36-6. Perl嵌入Bash脚本
  • Example 36-7. Bash和Perl脚本合并
  • Example 36-8. Python嵌入Bash脚本
  • Example 36-9. 会讲话的脚本

这有帮助吗?

  1. 正文
  2. 第五部分 高级话题
  3. 36. 杂项

36.2 shell wrappers

wrapper是一个包含系统命令和工具的脚本,脚本会把一些参数传递给这些(脚本内的)命令。将一个复杂的命令封装成一个wrapper是为了调用它时比较简单好记,特别在使用sed和awk命令时会这么做。

sed或awk脚本通常在命令行下调用时是sed -e '命令'或者awk '命令'。在Bash脚本中嵌入这些命令会让它们在调用时很简单,并且能够被重用。使用这种方法可以将sed和awk的优势统一起来,比如将sed命令处理的结果通过管道传递给awk继续处理。将这些保存成为一个可执行文件,你可以重复调用它的原始版本或者修改版本,而不用在命令行里反复敲冗长的命令。

Example 36-1. shell wrapper

#!/bin/bash

# 这个脚本功能是去除文件中的空白行
# 没有做参数检查
#
# 也许你想添加下面的内容:
#
# E_NOARGS=85
# if [ -z "$1" ]
# then
#  echo "Usage: `basename $0` target-file"
#  exit $E_NOARGS
# fi

sed -e /^$/d "$1"
# 就像这个命令
#    sed -e '/^$/d' filename
# 通过命令行调用

# '-e'意思是后面为编辑命令(这个选项可省略)。
# '^'代表行首,'$'代表行尾。
# 这个正则表达式表示要匹配出所有行首位没有内容的行,就是空白行。
# 是删除命令(译注:就是把刚才选出来的空白行删掉)

# 将文件名中的特殊字符和空白进行转译

# 这个脚本并不会真正的修改目标文件,如果想对目标文件真正的修改,请将输出重定向

exit

Example 36-2. 稍微复杂一点的 shell wrapper

#!/bin/bash

#  subst.sh: 在文件中进行替换字符串的脚本
#  例如 "sh subst.sh Smith Jones letter.txt"
#  letter.txt 中的所有 Jones 都被替换为 Smith。

ARGS=3         # 这个脚本需要三个参数
E_BADARGS=85   # 传给脚本的参数数量不正确

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/调用地址匹配
# 'g' 表示要对文件中的所有匹配项目都进行替换操作,而不是仅对第一个这样干。
# 如果需要深入了解,请阅读sed命令的相关文档。

exit $?  # 将这个脚本的输出重定向到一个文件即可记录真正的结果

Example 36-3. 一个通用的写日志文件的 shell wrapper

#!/bin/bash
#  logging-wrapper.sh
#  一个通用的shell wrapper,在进行操作的同时对操作进行日志记录

DEFAULT_LOGFILE=logfile.txt

# 设置下面两个变量的值
OPERATION=
# 可以是任意操作,比如一个awk脚本或者用管道连接的复杂命令

LOGFILE=
if [ -z "$LOGFILE" ]
    then     # 如果没有设置日志文件,则使用默认文件名
      LOGFILE="$DEFAULT_LOGFILE"
      fi

# 对于操作命令的参数(可选)
OPTIONS="$@"


# 日志记录
echo "`date` + `whoami` + $OPERATION "$@"" >> $LOGFILE
# 进行操作动作
exec $OPERATION "$@"

# 要在真正执行操作之前写日志
# 思考下为什么要先写日志,后操作。

Example 36-4. 关于awk脚本的 shell wrapper

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

START=33   # 可打印的ASCII码范围(十进制)
END=127    # 不会输出不可打印的ASCII码

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命令无法完成下面的操作: (译注:所以这使用awk脚本来实现输出)
#     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

Example 36-5. 另一个关于awk的 shell wrapper

#!/bin/bash

# 在目标文件中添加一个数字的特殊列
# 十进制浮点数也可以,因为awk可以处理这样的输出。

ARGS=2
E_WRONGARGS=85

if [ $# -ne "$ARGS" ] # Check for proper number of command-line args.
then
    echo "Usage: `basename $0` filename column-number"
    exit $E_WRONGARGS
fi

filename=$1
column_number=$2

# 将shell脚本的变量传递给awk有一点难办。
# 第一种方法是用引号将Bash脚本变量在awk脚本中包起来
#     $'$BASH_SCRIPT_VAR'
#      ^                ^
# 下面的awk脚本就是这么干的。
# 详细用法可以查阅awk文档。

# 多行的awk脚本可以写成这样
#   awk '
#   ...
#   ...
#   ...
#   '


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

{ total += $'"${column_number}"' # 译注:这就是那个bash脚本变量
}
END {
print total
}     

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


#   将shell变量传递给awk脚本也许是不安全的
#   所以Stephane Chazelas提出了下面的替代方案:
#   ---------------------------------------
#   awk -v column_number="$column_number" ' # 译注:将shell的值赋给一个awk变量
#   { total += $column_number
#   }
#   END {
#       print total
#   }' "$filename"
#   ---------------------------------------


exit 0

能满足那些需要瑞士军刀般全能工具的脚本语言,就只有Perl了。Perl集合了sed和awk的能力,并且比C更加精简。它是模块化的并且能支持包括厨房洗碗槽在内的所有面向对象编程所能涉及的事物。短小的Perl脚本可以嵌入shell脚本中,甚至Perl可以完全替代shell脚本。(本书作者对此仍然抱有怀疑)

Example 36-6. Perl嵌入Bash脚本

#!/bin/bash

# shell命令先于Perl脚本执行
echo "This precedes the embedded Perl script within \"$0\"."
echo "==============================================================="

perl -e 'print "This line prints from 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部分仍然要取决于调用脚本的方式。

Example 36-7. Bash和Perl脚本合并

#!/bin/bash
# bashandperl.sh

echo "Greetings from the Bash part of the script, $0."
# 这里可以写更多的Bash命令

exit
# Bash脚本部分结束

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

#!/usr/bin/perl
# 这部分脚本要像下面这样调用
#    perl -x bashandperl.sh

print "Greetings from the Perl part of the script, $0.\n";
# Perl 看起来并不像 “echo” ...
# 这里可以写更多的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.

当然还可以用shell wrapper嵌入更多的“外来户”,比如Python或者其他的...

Example 36-8. Python嵌入Bash脚本

#!/bin/bash
# ex56py.sh

# shell脚本先于Python脚本执行
echo "This precedes the embedded Python script within \"$0.\""
echo "==============================================================="

python -c 'print "This line prints from an embedded Python script.\n";'
# 并不像sed和Perl,Python使用'-c'选项
python -c 'k = raw_input( "Hit a key to exit to outer script. " )'

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

exit 0

使用脚本封装mplayer或者Google翻译服务器的一些功能,你能做出给你反馈一些信息的小东西。

Example 36-9. 会讲话的脚本

#!/bin/bash
#   参见:
#   http://elinux.org/RPi_Text_to_Speech_(Speech_Synthesis)

# 为了连接Google翻译服务器,这个脚本必须连接到互联网才能工作,
# 而且你的计算机上必须装有mplayer。

speak()
  {
  local IFS=+
  # 先调用mplayer,再连接Google翻译服务器。
  /usr/bin/mplayer -ao alsa -really-quiet -noconsolecontrols \
  "http://translate.google.com/translate_tts?tl=en&q="$*""
  # 可以说话的Google翻译
  }

LINES=4

spk=$(tail -$LINES $0) # 同样的结尾
speak "$spk"
exit
# BRowns 很高兴与你谈话。

有个有趣的shell wrapper例子是Martin Matusiak的undvd,为复杂的mencoder工具提供了一个简单易用的命令行接口。另一个例子是Itzchak Rehberg的Ext3Undel,它为在ext3文件系统上恢复删除的文件提供了一整套工具。

Notes [1] Linux工具事实上很多是shell wrapper,比如/usr/bin/pdf2ps,/usr/bin/batch和/usr/bin/xmkmf。

上一页36.1 交互和非交互shell以及脚本下一页36.3 测试和比较的其他方法

最后更新于5年前

这有帮助吗?