• 主页
  • Linux命令大全/Bash 参考
  • 高级Bash脚本编程指南
  • 普华Linux桌面应用教程
序
1. 原书作者致中国读者(英文)
2. 原书作者致中国读者(译文)
3. 黄毅
4. 杨春敏
5. 陈涛
第一部分. 热身
1. 为什么使用shell编程?
2. 带着一个Sha-Bang出发(Sha-Bang指的是#!) 2.1. 调用一个脚本 2.2. 初步的练习
第二部分. 基本
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. 数字常量
第三部分. 进阶
9. 变量重游 9.1. 内部变量 9.2. 操作字符串 9.3. 参数替换 9.4. 指定变量的类型: 使用declare或者typeset 9.5. 变量的间接引用 9.6. $RANDOM: 产生随机整数 9.7. 双圆括号结构
10. 循环与分支 10.1. 循环 10.2. 嵌套循环 10.3. 循环控制 10.4. 测试与分支(case与select结构)
11. 内部命令与内建命令 11.1. 作业控制命令
12. 外部过滤器, 程序和命令 12.1. 基本命令 12.2. 复杂命令 12.3. 时间/日期 命令 12.4. 文本处理命令 12.5. 文件与归档命令 12.6. 通讯命令 12.7. 终端控制命令 12.8. 数学计算命令 12.9. 混杂命令
13. 系统与管理命令 13.1. 分析一个系统脚本
14. 命令替换
15. 算术扩展
16. I/O重定向 16.1. 使用exec 16.2. 代码块重定向 16.3. 重定向的应用
17. Here Document 17.1. Here String
18. 休息片刻
第四部分. 高级主题
19. 正则表达式 19.1. 一份简要的正则表达式介绍 19.2. 通配(globbing)
20. 子shell
21. 受限shell
22. 进程替换
23. 函数 23.1. 复杂函数和函数复杂性 23.2. 局部变量 23.3. 不使用局部变量的递归
24. 别名
25. 列表结构
26. 数组
27. /dev和/proc 27.1. /dev 27.2. /proc
28. Zero与Null
29. 调试
30. 选项
31. 陷阱
32. 脚本编程风格 32.1. 非官方的Shell脚本编写风格
33. 杂项 33.1. 交互与非交互式的交互与非交互式的shell和脚本 33.2. Shell包装 33.3. 测试和比较: 一种可选的方法 33.4. 递归 33.5. 将脚本"彩色化" 33.6. 优化 33.7. 各种小技巧 33.8. 安全问题 33.9. 可移植性问题 33.10. Windows下的shell脚本
34. Bash, 版本2与版本3 34.1. Bash, 版本2 34.2. Bash, 版本3
35. 后记 35.1. 作者后记 35.2. 关于作者 35.3. 译者后记 35.4. 在哪里可以获得帮助 35.5. 用来制作这本书的工具 35.6. 致谢 35.7. 译者致谢
参考文献
A. 捐献的脚本
B. 参考卡片
C. 一个学习Sed和Awk的小手册 C.1. Sed C.2. Awk
D. 带有特殊含义的退出码
E. I/O和I/O重定向的详细介绍
F. 命令行选项 F.1. 标准命令行选项 F.2. Bash命令行选项
G. 重要的文件
H. 重要的系统目录
I. 本地化
J. 历史命令
K. 一个简单的.bashrc文件
L. 将DOS批处理文件转换为Shell脚本
M. 练习 M.1. 分析脚本 M.2. 编写脚本
N. 修订记录
O. 翻译版修订记录
P. 镜像站点
Q. To Do列表
R. 版权
表格清单
11-1. 作业标识符
30-1. Bash选项
33-1. 转义序列中颜色与数值的对应
B-1. 特殊的shell变量
B-2. 测试操作: 二元比较
B-3. 文件类型的测试操作
B-4. 参数替换和扩展
B-5. 字符串操作
B-6. 一些结构的汇总
C-1. 基本sed操作
C-2. sed操作符举例
D-1. "保留的"退出码
L-1. 批处理文件关键字 / 变量 / 操作符, 和等价的shell符号
L-2. DOS命令与UNIX的等价命令
N-1. 修订历史
O-1. 翻译版修订历史
例子清单
2-1. 清除: 清除/var/log下的log文件
2-2. 清除:一个改良的清除脚本
2-3. 清除: 一个增强的和广义的删除logfile的脚本
3-1. 代码块和I/O重定向
3-2. 将一个代码块的结果保存到文件
3-3. 在后台运行一个循环
3-4. 备份最后一天所有修改的文件
4-1. 变量赋值和替换
4-2. 简单的变量赋值
4-3. 简单和复杂, 两种类型的变量赋值
4-4. 整型还是字符串?
4-5. 位置参数
4-6. wh, whois节点名字查询
4-7. 使用shift命令
5-1. echo出一些诡异变量
5-2. 转义符
6-1. 退出/退出状态码
6-2. 反转一个条件的用法!
7-1. 什么是真?
7-2. test, /usr/bin/test, [ ], 和/usr/bin/[都是等价命令
7-3. 算术测试需要使用(( ))
7-4. 测试那些断掉的链接文件
7-5. 算术比较与字符串比较
7-6. 检查字符串是否为null
7-7. zmore
8-1. 最大公约数
8-2. 使用算术操作符
8-3. 使用&&和||进行混合条件测试
8-4. 数字常量表示法
9-1. $IFS与空白字符
9-2. 定时输入
9-3. 再来一个, 定时输入
9-4. 定时read
9-5. 我是root么?
9-6. arglist: 通过$*和$@列出所有的参数
9-7. $*和$@的不一致的行为
9-8. 当$IFS为空时的$*和$@
9-9. 下划线变量
9-10. 在一个文本文件的段落之间插入空行
9-11. 转换图片文件格式, 同时更改文件名
9-12. 将音频流文件转换为ogg各式的文件
9-13. 模拟getopt
9-14. 提取字符串的另一种方法
9-15. 使用参数替换和错误消息
9-16. 参数替换和"usage"消息(译者注: 通常就是帮助信息)
9-17. 变量长度
9-18. 参数替换中的模式匹配
9-19. 修改文件扩展名:
9-20. 使用模式匹配来解析任意字符串
9-21. 对字符串的前缀和后缀使用匹配模式
9-22. 使用declare来指定变量的类型
9-23. 间接引用
9-24. 传递一个间接引用给awk
9-25. 产生随机整数
9-26. 从一幅扑克牌中取出一张随机的牌
9-27. 两个指定值之间的随机数
9-28. 用随机数来摇单个骰子
9-29. 重新分配随机数种子
9-30. 使用awk来产生伪随机数
9-31. C语言风格的变量操作
10-1. 一个简单的for循环
10-2. 每个[list]元素中都带有两个参数的for循环
10-3. 文件信息: 对包含在变量中的文件列表进行操作
10-4. 在for循环中操作文件
10-5. 在for循环中省略in [list]部分
10-6. 使用命令替换来产生for循环的[list]
10-7. 对于二进制文件的grep替换
10-8. 列出系统上的所有用户
10-9. 在目录的所有文件中查找源字串
10-10. 列出目录中所有的符号链接
10-11. 将目录中所有符号链接文件的名字保存到一个文件中
10-12. 一个C风格的for循环
10-13. 在batch mode中使用efax
10-14. 简单的while循环
10-15. 另一个while循环
10-16. 多条件的while循环
10-17. C风格的while循环
10-18. until循环
10-19. 嵌套循环
10-20. break和continue命令在循环中的效果
10-21. 多层循环的退出
10-22. 多层循环的continue
10-23. 在实际的任务中使用"continue N"
10-24. 使用case
10-25. 使用case来创建菜单
10-26. 使用命令替换来产生case变量
10-27. 简单的字符串匹配
10-28. 检查输入字符是否为字母
10-29. 使用select来创建菜单
10-30. 使用函数中的select结构来创建菜单
11-1. 一个fork出多个自身实例的脚本
11-2. 使用printf的例子
11-3. 使用read来进行变量分配
11-4. 当使用一个不带变量参数的read命令时, 将会发生什么?
11-5. read命令的多行输入
11-6. 检测方向键
11-7. 通过文件重定向来使用read命令
11-8. 管道输出到read中的问题
11-9. 修改当前工作目录
11-10. 使用"let"命令来做算术运算.
11-11. 展示eval命令的效果
11-12. 强制登出(log-off)
11-13. 另一个"rot13"版本
11-14. 在Perl脚本中使用eval命令来强制变量替换
11-15. 使用set命令来改变脚本的位置参数
11-16. 反转位置参数
11-17. 重新分配位置参数
11-18. "Unsett"一个变量
11-19. 使用export命令来将一个变量传递到一个内嵌awk的脚本中
11-20. 使用getopts命令来来读取传递给脚本的选项/参数
11-21. "includ"一个数据文件
11-22. 一个(没什么用的)source自身的脚本
11-23. exec命令的效果
11-24. 一个exec自身的脚本
11-25. 在继续处理之前, 等待一个进程的结束
11-26. 一个结束自身的脚本程序
12-1. 使用ls命令来创建一个烧录CDR的内容列表
12-2. 到底是Hello还是Good-bye
12-3. 糟糕的文件名, 删除当前目录下文件名中包含一些糟糕字符(包括空白的文件.
12-4. 通过文件的inode号来删除文件
12-5. Logfile: 使用xargs来监控系统log
12-6. 把当前目录下的文件拷贝到另一个文件中
12-7. 通过名字kill进程
12-8. 使用xargs分析单词出现的频率
12-9. 使用expr
12-10. 使用date命令
12-11. 分析单词出现的频率
12-12. 哪个文件是脚本?
12-13. 产生10-进制随机数
12-14. 使用tail命令来监控系统log
12-15. 在脚本中模拟"grep"的行为
12-16. 在1913年的韦氏词典中查找定义
12-17. 检查列表中单词的正确性
12-18. 转换大写: 把一个文件的内容全部转换为大写.
12-19. 转换小写: 将当前目录下的所有文全部转换为小写.
12-20. Du: DOS到UNIX文本文件的转换.
12-21. rot13: rot13, 弱智加密.
12-22. 产生"Crypto-Quote"游戏(译者: 一种文字游戏)
12-23. 格式化文件列表.
12-24. 使用column来格式化目录列表
12-25. nl: 一个自己计算行号的脚本.
12-26. manview: 查看格式化的man页
12-27. 使用cpio来拷贝一个目录树
12-28. 解包一个rpm归档文件
12-29. 从C文件中去掉注释
12-30. 浏览/usr/X11R6/bin
12-31. 一个"改进过"的strings命令
12-32. 在一个脚本中使用cmp命令来比较两个文件.
12-33. basename和dirname
12-34. 检查文件完整性
12-35. Uudecode编码后的文件
12-36. 查找滥用的链接来报告垃圾邮件发送者
12-37. 分析一个垃圾邮件域
12-38. 获得一份股票报价
12-39. 更新FC4(Fedora 4)
12-40. 使用ssh
12-41. 一个mail自身的脚本
12-42. 按月偿还贷款
12-43. 数制转换
12-44. 使用"here document"来调用bc
12-45. 计算圆周率
12-46. 将10进制数字转换为16进制数字
12-47. 因子分解
12-48. 计算直角三角形的斜边
12-49. 使用seq命令来产生循环参数
12-50. 字母统计
12-51. 使用getopt来分析命令行选项
12-52. 一个拷贝自身的脚本
12-53. 练习dd
12-54. 记录按键
12-55. 安全的删除一个文件
12-56. 文件名产生器
12-57. 将长度单位-米, 转化为英里
12-58. 使用m4
13-1. 设置一个新密码
13-2. 设置一个擦除字符
13-3. 保密密码: 关闭终端对于密码的echo
13-4. 按键检测
13-5. 扫描远程机器上的identd服务进程
13-6. 使用pidof命令帮忙kill一个进程
13-7. 检查一个CD镜像
13-8. 在一个文件中创建文件系统
13-9. 添加一个新的硬盘驱动器
13-10. 用umask将输出文件隐藏起来
13-11. killall, 来自于/etc/rc.d/init.d
14-1. 愚蠢的脚本策略
14-2. 将一个循环输出的内容设置到变量中
14-3. 找anagram(回文构词法, 可以将一个有意义的单词, 变换为1个或多个有意义的单词, 但是还是原来的子母集合)
16-1. 使用exec重定向stdin
16-2. 使用exec来重定向stdout
16-3. 使用exec在同一个脚本中重定向stdin和stdout
16-4. 避免子shell
16-5. while循环的重定向
16-6. 重定向while循环的另一种形式
16-7. 重定向until循环
16-8. 重定向for循环
16-9. 重定向for循环(stdin和stdout都进行重定向)
16-10. 重定向if/then测试结构
16-11. 用于上面例子的"names.data"数据文件
16-12. 事件纪录
17-1. 广播: 将消息发送给每个登陆的用户
17-2. 虚拟文件: 创建一个2行的虚拟文件
17-3. 使用cat的多行消息
17-4. 带有抑制tab功能的多行消息
17-5. 使用参数替换的here document
17-6. 上传一个文件对到"Sunsite"的incoming目录
17-7. 关闭参数替换
17-8. 生成另外一个脚本的脚本
17-9. Here document与函数
17-10. "匿名"的here Document
17-11. 注释掉一段代码块
17-12. 一个自文档化(self-documenting)的脚本
17-13. 在一个文件的开头添加文本
17-14. 分析一个邮箱
20-1. 子shell中的变量作用域
20-2. 列出用户的配置文件
20-3. 在子shell中进行并行处理
21-1. 在受限模式下运行脚本
23-1. 简单函数
23-2. 带参数的函数
23-3. 函数与传递给脚本的命令行参数
23-4. 将一个间接引用传递给函数
23-5. 对一个传递给函数的参数进行解除引用的操作
23-6. 再来一次, 对一个传递给函数的参数进行解除引用的操作
23-7. 取两个数中的最大值
23-8. 将阿拉伯数字转化为罗马数字
23-9. 测试函数最大的返回值
23-10. 比较两个大整数
23-11. 从username中取得用户的真名
23-12. 局部变量的可见范围
23-13. 使用局部变量的递归
23-14. 汉诺塔
24-1. 用在脚本中的别名
24-2. unalias: 设置与删除别名
25-1. 使用"与列表"来测试命令行参数
25-2. 使用"与列表"来测试命令行参数的另一个例子
25-3. 将"或列表"和"与列表"结合使用
26-1. 简单的数组使用
26-2. 格式化一首诗
26-3. 多种数组操作
26-4. 用于数组的字符串操作
26-5. 将脚本的内容赋值给数组
26-6. 一些数组专用的小道具
26-7. 空数组与包含空元素的数组
26-8. 初始化数组
26-9. 拷贝和连接数组
26-10. 关于串联数组的更多信息
26-11. 一位老朋友: 冒泡排序
26-12. 嵌套数组与间接引用
26-13. 复杂的数组应用: 埃拉托色尼素数筛子
26-14. 模拟一个下推堆栈
26-15. 复杂的数组应用: 探索一个神秘的数学序列
26-16. 模拟一个二维数组, 并使他倾斜
27-1. 利用/dev/tcp来检修故障
27-2. 找出与给定PID相关联的进程
27-3. 网络连接状态
28-1. 隐藏令人厌恶的cookie
28-2. 使用/dev/zero来建立一个交换文件
28-3. 创建一个ramdisk
29-1. 一个错误脚本
29-2. 缺少关键字
29-3. test24, 另一个错误脚本
29-4. 使用"assert"来测试条件
29-5. 捕获exit
29-6. Control-C之后, 清除垃圾
29-7. 跟踪一个变量
29-8. 运行多进程(在对称多处理器(SMP box)的机器上)
31-1. 数字比较与字符串比较并不相同
31-2. 子shell缺陷
31-3. 将echo的输出通过管道传递给read命令
33-1. shell包装
33-2. 稍微复杂一些的shell包装
33-3. 一个通用的shell包装, 用来写日志文件
33-4. 包装awd脚本的shell包装
33-5. 另一个包装awd脚本的shell包装
33-6. 将Perl嵌入到Bash脚本中
33-7. 将Bash和Perl脚本写到同一个文件中
33-8. 递归调用自身的(没用的)脚本
33-9. 递归调用自身的(有用的)脚本
33-10. 另一个递归调用自身的(有用的)脚本
33-11. 一个"彩色的"地址数据库
33-12. 画一个盒子
33-13. 显示彩色文本
33-14. "赛马"游戏
33-15. 返回值小技巧
33-16. 返回多个值的技巧
33-17. 传递数组到函数, 从函数中返回数组
33-18. anagram游戏
33-19. 从shell脚本中调用窗口部件
34-1. 字符串扩展
34-2. 间接变量引用 - 新方法
34-3. 使用间接变量引用的简单数据库应用
34-4. 使用数组和其他的小技巧来处理4人随机打牌
A-1. mailformat: 格式化一个e-mail消息
A-2. rn: 一个非常简单的文件重命名工具
A-3. blank-rename: 重命名包含空白的文件名
A-4. encryptedpw: 使用一个本地加密口令, 上传到一个ftp服务器.
A-5. copy-cd: 拷贝一个数据CD
A-6. Collatz序列
A-7. days-between: 计算两个日期之间天数差
A-8. 构造一个"字典"
A-9. Soundex转换
A-10. "Game of Life"
A-11. "Game of Life"的数据文件
A-12. behead: 去掉信件与新消息的头
A-13. ftpget: 通过ftp下载文件
A-14. password: 产生随机的8个字符的密码
A-15. fifo: 使用命名管道来做每日的备份
A-16. 使用模操作符来产生素数
A-17. tree: 显示目录树
A-18. string functions: C风格的字符串函数
A-19. 目录信息
A-20. 面向对象数据库
A-21. hash函数库
A-22. 使用hash函数来给文本上色
A-23. 深入hash函数
A-24. 挂载USB keychain型的存储设备
A-25. 保存weblog
A-26. 保护字符串的字面含义
A-27. 不保护字符串的字面含义
A-28. 鉴定是否是垃圾邮件服务器
A-29. 垃圾邮件服务器猎手
A-30. 使得wget更易用
A-31. 一个"podcasting"(译者: 指的是在互联网上发布音视频文件, 并允许用户订阅并自动接收的方法)脚本
A-32. 基础回顾
A-33. 一个扩展的cd命令
C-1. 计算字符出现次数
K-1. .bashrc文件样本
L-1. VIEWDATA.BAT: DOS批处理文件
L-2. viewdata.sh: 转换自VIEWDATA.BAT的shell脚本
Q-1. 打印服务器环境
高级Bash脚本编程指南: 一本深入学习shell脚本艺术的书籍
前一页33. 杂项下一页

33.5. 将脚本"彩色化"

ANSI[1]定义了屏幕属性的转义序列集合, 比如说粗体文本, 前景与背景颜色. DOS批处理文件通常都使用ANSI转义码来控制 颜色输出, Bash脚本也是这么做的.


例子 33-11. 一个"彩色的"地址数据库

#!/bin/bash
# ex30a.sh: 脚本ex30.sh的"彩色"版本. 
#            没被加工处理过的地址数据库


clear                                   # 清屏. 

echo -n "          "
echo -e '\E[37;44m'"\033[1mContact List\033[0m"
                                        # 在蓝色背景下的白色. 
echo; echo
echo -e "\033[1mChoose one of the following persons:\033[0m"
                                        # 粗体
tput sgr0
echo "(Enter only the first letter of name.)"
echo
echo -en '\E[47;34m'"\033[1mE\033[0m"   # 蓝色
tput sgr0                               # 将颜色重置为"常规". 
echo "vans, Roland"                     # "[E]vans, Roland"
echo -en '\E[47;35m'"\033[1mJ\033[0m"   # 红紫色
tput sgr0
echo "ones, Mildred"
echo -en '\E[47;32m'"\033[1mS\033[0m"   # 绿色
tput sgr0
echo "mith, Julie"
echo -en '\E[47;31m'"\033[1mZ\033[0m"   # 红色
tput sgr0
echo "ane, Morris"
echo

read person

case "$person" in
# 注意, 变量被引用起来了. 

  "E" | "e" )
  # 大小写的输入都能接受. 
  echo
  echo "Roland Evans"
  echo "4321 Floppy Dr."
  echo "Hardscrabble, CO 80753"
  echo "(303) 734-9874"
  echo "(303) 734-9892 fax"
  echo "revans@zzy.net"
  echo "Business partner & old friend"
  ;;

  "J" | "j" )
  echo
  echo "Mildred Jones"
  echo "249 E. 7th St., Apt. 19"
  echo "New York, NY 10009"
  echo "(212) 533-2814"
  echo "(212) 533-9972 fax"
  echo "milliej@loisaida.com"
  echo "Girlfriend"
  echo "Birthday: Feb. 11"
  ;;

# 稍后为Smith & Zane添加信息. 

          * )
   # 默认选项. 	  
   # 空输入(直接按回车)也会在这被匹配. 
   echo
   echo "Not yet in database."
  ;;

esac

tput sgr0                               # 将颜色重置为"常规". 

echo

exit 0


例子 33-12. 画一个盒子

#!/bin/bash
# Draw-box.sh: 使用ASCII字符画一个盒子. 

# 由Stefano Palmeri编写, 本书作者做了少量修改. 
# 经过授权, 可以在本书中使用. 


######################################################################
###  draw_box函数注释  ###

#  "draw_box"函数可以让用户
#+ 在终端上画一个盒子. 
#
#  用法: draw_box ROW COLUMN HEIGHT WIDTH [COLOR] 
#  ROW和COLUMN用来定位你想要
#+ 画的盒子的左上角. 
#  ROW和COLUMN必须大于0, 
#+ 并且要小于当前终端的尺寸. 
#  HEIGHT是盒子的行数, 并且必须 >0 . 
#  HEIGHT + ROW 必须 <= 终端的高度. 
#  WIDTH是盒子的列数, 必须 >0 .
#  WIDTH + COLUMN 必须 <= 终端的宽度. 
#
# 例如: 如果你的终端尺寸为20x80, 
#  draw_box 2 3 10 45 是合法的
#  draw_box 2 3 19 45 的HEIGHT是错的 (19+2 > 20)
#  draw_box 2 3 18 78 的WIDTH是错的 (78+3 > 80)
#
#  COLOR是盒子边框的颜色. 
#  这是第5个参数, 并且是可选的. 
#  0=黑 1=红 2=绿 3=棕褐 4=蓝 5=紫 6=青 7=白.
#  如果你传递给函数的参数错误, 
#+ 它将会退出, 并返回65, 
#+ 不会有消息打印到stderr上. 
#
#  开始画盒子之前, 会清屏. 
#  函数内不包含清屏命令. 
#  这样就允许用户画多个盒子, 甚至可以叠加多个盒子. 

###  draw_box函数注释结束  ### 
######################################################################

draw_box(){

#=============#
HORZ="-"
VERT="|"
CORNER_CHAR="+"

MINARGS=4
E_BADARGS=65
#=============#


if [ $# -lt "$MINARGS" ]; then                 # 如果参数小于4, 退出. 
    exit $E_BADARGS
fi

# 找出参数中非数字的字符. 
# 还有其他更好的方法么(留给读者作为练习?). 
if echo $@ | tr -d [:blank:] | tr -d [:digit:] | grep . &> /dev/null; then
   exit $E_BADARGS
fi

BOX_HEIGHT=`expr $3 - 1`   #  必须-1, 因为边角的"+"是
BOX_WIDTH=`expr $4 - 1`    #+ 高和宽共有的部分. 
T_ROWS=`tput lines`        #  定义当前终端的
T_COLS=`tput cols`         #+ 长和宽的尺寸. 
         
if [ $1 -lt 1 ] || [ $1 -gt $T_ROWS ]; then    #  开始检查参数
   exit $E_BADARGS                             #+ 是否正确. 
fi
if [ $2 -lt 1 ] || [ $2 -gt $T_COLS ]; then
   exit $E_BADARGS
fi
if [ `expr $1 + $BOX_HEIGHT + 1` -gt $T_ROWS ]; then
   exit $E_BADARGS
fi
if [ `expr $2 + $BOX_WIDTH + 1` -gt $T_COLS ]; then
   exit $E_BADARGS
fi
if [ $3 -lt 1 ] || [ $4 -lt 1 ]; then
   exit $E_BADARGS
fi                                 # 参数检查结束. 

plot_char(){                       # 函数内的函数. 
   echo -e "\E[${1};${2}H"$3
}

echo -ne "\E[3${5}m"               # 如果定义了, 就设置盒子边框的颜色. 

# 开始画盒子

count=1                                         #  使用plot_char函数
for (( r=$1; count<=$BOX_HEIGHT; r++)); do      #+ 画垂直线. 
  plot_char $r $2 $VERT
  let count=count+1
done 

count=1
c=`expr $2 + $BOX_WIDTH`
for (( r=$1; count<=$BOX_HEIGHT; r++)); do
  plot_char $r $c $VERT
  let count=count+1
done 

count=1                                        #  使用plot_char函数
for (( c=$2; count<=$BOX_WIDTH; c++)); do      #+ 画水平线. 
  plot_char $1 $c $HORZ
  let count=count+1
done 

count=1
r=`expr $1 + $BOX_HEIGHT`
for (( c=$2; count<=$BOX_WIDTH; c++)); do
  plot_char $r $c $HORZ
  let count=count+1
done 

plot_char $1 $2 $CORNER_CHAR                   # 画盒子的角. 
plot_char $1 `expr $2 + $BOX_WIDTH` +
plot_char `expr $1 + $BOX_HEIGHT` $2 +
plot_char `expr $1 + $BOX_HEIGHT` `expr $2 + $BOX_WIDTH` +

echo -ne "\E[0m"             #  恢复原来的颜色. 

P_ROWS=`expr $T_ROWS - 1`    #  在终端的底部打印提示符. 

echo -e "\E[${P_ROWS};1H"
}      


# 现在, 让我们开始画盒子吧. 
clear                       # 清屏. 
R=2      # 行
C=3      # 列
H=10     # 高
W=45     # 宽
col=1    # 颜色(红)
draw_box $R $C $H $W $col   # 画盒子. 

exit 0

# 练习:
# -----
# 添加一个选项, 用来支持可以在所画的盒子中打印文本. 

最简单的, 也可能是最有用的ANSI转义序列是加粗文本, \033[1m ... \033[0m. \033代表转义, "[1"打开加粗属性, 而"[0"关闭加粗属性. "m"表示转义序列结束.

bash$ echo -e "\033[1mThis is bold text.\033[0m"
	      

一种类似的转义序列用来切换下划线属性(在rxvt 和aterm上).

bash$ echo -e "\033[4mThis is underlined text.\033[0m"
	      

Note

echo命令的-e选项用来启用转义序列.

其他的转义序列可用于修改文本和背景色.

bash$ echo -e '\E[34;47mThis prints in blue.'; tput sgr0


bash$ echo -e '\E[33;44m'"yellow text on blue background"; tput sgr0


bash$ echo -e '\E[1;33;44m'"BOLD yellow text on blue background"; tput sgr0
	      

Note

通常情况下, 为浅色的前景文本设置粗体属性比较好.

tput sgr0把终端设置恢复为原样. 如果省略这一句, 那么这个终端所有后续的输出还会是蓝色.

Note

因为tput sgr0在某些环境下不能恢复终端设置, echo -ne \E[0m可能是更好的选择.

可以在有色的背景上, 使用下面的模板, 在上面写有色的文本.

echo -e '\E[COLOR1;COLOR2mSome text goes here.'

"\E["开始转义序列. 以分号分隔的数字"COLOR1"和"COLOR2"分别指定了前景色和背景色, 数值与色彩之间的对应, 请参考下面的表格. (数值的顺序其实没关系, 因为前景色和背景色的数值都落在互不重叠的范围中.) "m"用来终止转义序列, 文本紧跟在"m"的后面.

也要注意, 单引号将echo -e后面的命令序列都引用了起来.

下表的数值是在rxvt终端上运行的结果. 具体的结果可能和在其他终端上运行的结果不同.


表格 33-1. 转义序列中颜色与数值的对应

颜色前景背景
黑3040
红3141
绿3242
黄3343
蓝3444
洋红3545
青3646
白3747


例子 33-13. 显示彩色文本

#!/bin/bash
# color-echo.sh: 使用颜色来显示文本消息. 

# 可以按照你自己的目的来修改这个脚本. 
# 这比将颜色数值写死更容易. 

black='\E[30;47m'
red='\E[31;47m'
green='\E[32;47m'
yellow='\E[33;47m'
blue='\E[34;47m'
magenta='\E[35;47m'
cyan='\E[36;47m'
white='\E[37;47m'


alias Reset="tput sgr0"      #  不用清屏, 
                             #+ 将文本属性重置为正常情况. 


cecho ()                     # Color-echo.
                             # 参数$1 = 要显示的信息
                             # 参数$2 = 颜色
{
local default_msg="No message passed."
                             # 其实并不一定非的是局部变量. 

message=${1:-$default_msg}   # 默认为default_msg. 
color=${2:-$black}           # 如果没有指定, 默认为黑色. 

  echo -e "$color"
  echo "$message"
  Reset                      # 重置文本属性. 

  return
}  


# 现在, 让我们试一下. 
# ----------------------------------------------------
cecho "Feeling blue..." $blue
cecho "Magenta looks more like purple." $magenta
cecho "Green with envy." $green
cecho "Seeing red?" $red
cecho "Cyan, more familiarly known as aqua." $cyan
cecho "No color passed (defaults to black)."
       # 缺少$color参数. 
cecho "\"Empty\" color passed (defaults to black)." ""
       # 空的$color参数. 
cecho
       # 缺少$message和$color参数.
cecho "" ""
       # 空的$message和$color参数. 
# ----------------------------------------------------

echo

exit 0

# 练习:
# -----
# 1) 为'cecho ()'函数添加"粗体"属性. 
# 2) 为彩色背景添加选项. 


例子 33-14. "赛马"游戏

#!/bin/bash
# horserace.sh: 一个非常简单的模拟赛马的游戏. 
# 作者: Stefano Palmeri
# 经过授权可以在本书中使用. 

################################################################
#  脚本目的: 
#  使用转义序列和终端颜色进行游戏. 
#
#  练习: 
#  编辑脚本, 使它运行起来更具随机性, 
#+ 建立一个假的赌场 . . .     
#  嗯 . . . 嗯 . . . 这种开场让我联想起一部电影 . . .
#
#  脚本将会给每匹马分配一个随机障碍. 
#  按照马的障碍来计算几率, 
#+ 并且使用一种欧洲(?)的风格表达出来. 
#  比如: 几率(odds)=3.75的话, 那就意味着如果你押$1,
#+ 你就会赢得$3.75.
# 
#  此脚本已经在GNU/Linux操作系统上测试过, 
#+ 测试终端有xterm, rxvt, 和konsole. 
#  测试机器上安装的是AMD 900 MHz处理器, 
#+ 平均比赛时间为75秒.    
#  如果使用更快的机器, 那么比赛用时会更少. 
#  所以, 如果你想增加比赛的悬念, 可以重置变量USLEEP_ARG. 
#
#  本脚本由Stefano Palmeri编写. 
################################################################

E_RUNERR=65

# 检查一下md5sum和bc是否已经被安装. 
if ! which bc &> /dev/null; then
   echo bc is not installed.  
   echo "Can\'t run . . . "
   exit $E_RUNERR
fi
if ! which md5sum &> /dev/null; then
   echo md5sum is not installed.  
   echo "Can\'t run . . . "
   exit $E_RUNERR
fi

#  设置下面的变量将会降低脚本的运行速度. 
#  它会作为参数, 传递给usleep命令(man usleep),   
#+ 并且单位是微秒(500000微秒 = 半秒).
USLEEP_ARG=0  

#  如果脚本被Ctl-C中断, 那就清除临时目录, 
#+ 恢复终端光标和终端颜色. 
trap 'echo -en "\E[?25h"; echo -en "\E[0m"; stty echo;\
tput cup 20 0; rm -fr  $HORSE_RACE_TMP_DIR'  TERM EXIT
#  请参考与调试相关的章节, 可以获得'trap'命令的详细用法. 

# 为脚本设置一个唯一的(实际上不是绝对唯一)临时目录名. 
HORSE_RACE_TMP_DIR=$HOME/.horserace-`date +%s`-`head -c10 /dev/urandom | md5sum | head -c30`

# 创建临时目录, 并移动到该目录下. 
mkdir $HORSE_RACE_TMP_DIR
cd $HORSE_RACE_TMP_DIR


#  这个函数将会把光标移动到行为$1, 列为$2的位置上, 然后打印$3. 
#  例如: "move_and_echo 5 10 linux"等价与"tput cup 4 9; echo linux", 
#+ 但是使用一个命令代替了两个命令. 
#  注意: "tput cup"定义0 0位置, 为终端左上角, 
#+ 而echo定义1 1位置, 为终端左上角. 
move_and_echo() {
          echo -ne "\E[${1};${2}H""$3" 
}

# 此函数用来产生1-9之间的伪随机数. 
random_1_9 () {
                head -c10 /dev/urandom | md5sum | tr -d [a-z] | tr -d 0 | cut -c1 
}

#  在画马的时候, 这两个函数用来模拟"移动". 
draw_horse_one() {
               echo -n " "//$MOVE_HORSE//
}
draw_horse_two(){
              echo -n " "\\\\$MOVE_HORSE\\\\ 
}   


# 定义当前终端尺寸. 
N_COLS=`tput cols`
N_LINES=`tput lines`

# 至少需要一个20(行) X 80(列)的终端. 检查一下. 
if [ $N_COLS -lt 80 ] || [ $N_LINES -lt 20 ]; then
   echo "`basename $0` needs a 80-cols X 20-lines terminal."
   echo "Your terminal is ${N_COLS}-cols X ${N_LINES}-lines."
   exit $E_RUNERR
fi


# 开始画赛场. 

# 需要一个80字符的字符串. 见下面. 
BLANK80=`seq -s "" 100 | head -c80`

clear

# 将前景色与背景色设为白. 
echo -ne '\E[37;47m'

# 将光标移动到终端的左上角. 
tput cup 0 0 

# 画6条白线. 
for n in `seq 5`; do
      echo $BLANK80        # 用这个80字符的字符串将终端变为彩色的. 
done

# 将前景色设为黑色. 
echo -ne '\E[30m'

move_and_echo 3 1 "START  1"            
move_and_echo 3 75 FINISH
move_and_echo 1 5 "|"
move_and_echo 1 80 "|"
move_and_echo 2 5 "|"
move_and_echo 2 80 "|"
move_and_echo 4 5 "|  2"
move_and_echo 4 80 "|"
move_and_echo 5 5 "V  3"
move_and_echo 5 80 "V"

# 将前景色设置为红色. 
echo -ne '\E[31m'

# 一些ASCII的艺术效果. 
move_and_echo 1 8 "..@@@..@@@@@...@@@@@.@...@..@@@@..."
move_and_echo 2 8 ".@...@...@.......@...@...@.@......."
move_and_echo 3 8 ".@@@@@...@.......@...@@@@@.@@@@...."
move_and_echo 4 8 ".@...@...@.......@...@...@.@......."
move_and_echo 5 8 ".@...@...@.......@...@...@..@@@@..."
move_and_echo 1 43 "@@@@...@@@...@@@@..@@@@..@@@@."
move_and_echo 2 43 "@...@.@...@.@.....@.....@....."
move_and_echo 3 43 "@@@@..@@@@@.@.....@@@@...@@@.."
move_and_echo 4 43 "@..@..@...@.@.....@.........@."
move_and_echo 5 43 "@...@.@...@..@@@@..@@@@.@@@@.."


# 将前景色和背景色设为绿色. 
echo -ne '\E[32;42m'

# 画11行绿线. 
tput cup 5 0
for n in `seq 11`; do
      echo $BLANK80
done

# 将前景色设为黑色. 
echo -ne '\E[30m'
tput cup 5 0

# 画栅栏. 
echo "++++++++++++++++++++++++++++++++++++++\
++++++++++++++++++++++++++++++++++++++++++"

tput cup 15 0
echo "++++++++++++++++++++++++++++++++++++++\
++++++++++++++++++++++++++++++++++++++++++"

# 将前景色和背景色设回白色. 
echo -ne '\E[37;47m'

# 画3条白线. 
for n in `seq 3`; do
      echo $BLANK80
done

# 将前景色设为黑色. 
echo -ne '\E[30m'

# 创建9个文件, 用来保存障碍物. 
for n in `seq 10 7 68`; do
      touch $n
done  

# 将脚本所要画的"马"设置为第一种类型. 
HORSE_TYPE=2

#  为每匹"马"创建位置文件和几率文件. 
#+ 在这些文件中, 保存马的当前位置, 
#+ 类型和几率. 
for HN in `seq 9`; do
      touch horse_${HN}_position
      touch odds_${HN}
      echo \-1 > horse_${HN}_position
      echo $HORSE_TYPE >>  horse_${HN}_position
      # 给马定义随机障碍物. 
       HANDICAP=`random_1_9`
      # 检查函数random_1_9是否返回一个有效值. 
      while ! echo $HANDICAP | grep [1-9] &> /dev/null; do
                HANDICAP=`random_1_9`
      done
      # 给马定义最后一个障碍物的位置. 
      LHP=`expr $HANDICAP \* 7 + 3`
      for FILE in `seq 10 7 $LHP`; do
            echo $HN >> $FILE
      done   
     
      # 计算几率. 
      case $HANDICAP in 
              1) ODDS=`echo $HANDICAP \* 0.25 + 1.25 | bc`
                                 echo $ODDS > odds_${HN}
              ;;
              2 | 3) ODDS=`echo $HANDICAP \* 0.40 + 1.25 | bc`
                                       echo $ODDS > odds_${HN}
              ;;
              4 | 5 | 6) ODDS=`echo $HANDICAP \* 0.55 + 1.25 | bc`
                                             echo $ODDS > odds_${HN}
              ;; 
              7 | 8) ODDS=`echo $HANDICAP \* 0.75 + 1.25 | bc`
                                       echo $ODDS > odds_${HN}
              ;; 
              9) ODDS=`echo $HANDICAP \* 0.90 + 1.25 | bc`
                                  echo $ODDS > odds_${HN}
      esac


done


# 打印几率. 
print_odds() {
tput cup 6 0
echo -ne '\E[30;42m'
for HN in `seq 9`; do
      echo "#$HN odds->" `cat odds_${HN}`
done
}

# 在起跑线上把马画出来. 
draw_horses() {
tput cup 6 0
echo -ne '\E[30;42m'
for HN in `seq 9`; do
      echo /\\$HN/\\"                               "
done
}

print_odds

echo -ne '\E[47m'
# 等待按下回车键, 按下之后就开始比赛. 
# 转义序列'\E[?25l'禁用光标. 
tput cup 17 0
echo -e '\E[?25l'Press [enter] key to start the race...
read -s

#  禁用了终端的常规echo功能. 
#  这么做用来避免在比赛中, 
#+ 按键所导致的"花"屏. 
stty -echo

# --------------------------------------------------------
# 开始比赛. 

draw_horses
echo -ne '\E[37;47m'
move_and_echo 18 1 $BLANK80
echo -ne '\E[30m'
move_and_echo 18 1 Starting...
sleep 1

# 设置终点线的列号. 
WINNING_POS=74

# 定义比赛开始的时间. 
START_TIME=`date +%s`

# 下面的"while"结构需要使用COL变量. 
COL=0    

while [ $COL -lt $WINNING_POS ]; do
                   
          MOVE_HORSE=0     
          
          # 检查random_1_9函数是否返回了有效值. 
          while ! echo $MOVE_HORSE | grep [1-9] &> /dev/null; do
                MOVE_HORSE=`random_1_9`
          done
          
          # 定义"随机抽取的马"的原来类型和位置. 
          HORSE_TYPE=`cat  horse_${MOVE_HORSE}_position | tail -1`
          COL=$(expr `cat  horse_${MOVE_HORSE}_position | head -1`) 
          
          ADD_POS=1
          # 判断当前位置是否存在障碍物. 
          if seq 10 7 68 | grep -w $COL &> /dev/null; then
                if grep -w $MOVE_HORSE $COL &> /dev/null; then
                      ADD_POS=0
                      grep -v -w  $MOVE_HORSE $COL > ${COL}_new
                      rm -f $COL
                      mv -f ${COL}_new $COL
                      else ADD_POS=1
                fi 
          else ADD_POS=1
          fi
          COL=`expr $COL + $ADD_POS`
          echo $COL >  horse_${MOVE_HORSE}_position  # 保存新位置. 
                            
         # 选择要画出来的马的类型. 
          case $HORSE_TYPE in 
                1) HORSE_TYPE=2; DRAW_HORSE=draw_horse_two
                ;;
                2) HORSE_TYPE=1; DRAW_HORSE=draw_horse_one 
          esac       
          echo $HORSE_TYPE >>  horse_${MOVE_HORSE}_position # 保存当前类型. 
         
          # 将前景色设为黑, 背景色设为绿. 
          echo -ne '\E[30;42m'
          
          # 将光标移动到马的新位置. 
          tput cup `expr $MOVE_HORSE + 5`  `cat  horse_${MOVE_HORSE}_position | head -1` 
          
          # 画马. 
          $DRAW_HORSE
           usleep $USLEEP_ARG
          
           # 当所有的马都越过第15行之后, 再次打印几率. 
           touch fieldline15
           if [ $COL = 15 ]; then
             echo $MOVE_HORSE >> fieldline15  
           fi
           if [ `wc -l fieldline15 | cut -f1 -d " "` = 9 ]; then
               print_odds
               : > fieldline15
           fi           
          
          # 取得领头的马. 
          HIGHEST_POS=`cat *position | sort -n | tail -1`          
          
          # 将背景色设为白. 
          echo -ne '\E[47m'
          tput cup 17 0
          echo -n Current leader: `grep -w $HIGHEST_POS *position | cut -c7`"                              "           

done  

# 定义比赛结束的时间. 
FINISH_TIME=`date +%s`

# 将背景色设置为绿色, 并且开启闪烁文本的功能. 
echo -ne '\E[30;42m'
echo -en '\E[5m'

# 让获胜的马闪烁. 
tput cup `expr $MOVE_HORSE + 5` `cat  horse_${MOVE_HORSE}_position | head -1`
$DRAW_HORSE

# 禁用闪烁文本. 
echo -en '\E[25m'

# 将前景色和背景色设置为白色. 
echo -ne '\E[37;47m'
move_and_echo 18 1 $BLANK80

# 将前景色设置为黑色. 
echo -ne '\E[30m'

# 让获胜的马闪烁. 
tput cup 17 0
echo -e "\E[5mWINNER: $MOVE_HORSE\E[25m""  Odds: `cat odds_${MOVE_HORSE}`"\
"  Race time: `expr $FINISH_TIME - $START_TIME` secs"

# 恢复光标, 恢复原来的颜色. 
echo -en "\E[?25h"
echo -en "\E[0m"

# 恢复打印功能. 
stty echo

# 删除掉和赛马有关的临时文件. 
rm -rf $HORSE_RACE_TMP_DIR

tput cup 19 0

exit 0

也请参考例子 A-22.

Caution

然而, 这里有一个严重的问题. ANSI转义序列是不可移植的. 在某些终端(或控制台)上运行的好好的代码, 可能在其他终端上根本没办法运行. "彩色"的脚本可能会在脚本作者的机器上运行的非常好, 但是在其他人的机器上就可能产生不可读的输出. 因为这个原因, 使得"彩色"脚本的用途大打折扣, 而且很有可能使得这项技术变成华而不实的小花招, 甚至成为一个"玩具".

Moshe Jacobson的彩色工具(http://runslinux.net/projects.html#color)能够非常容易的简化ANSI转义序列的使用. 这个工具使用清晰而且富有逻辑的语法代替了之前讨论的难用的结构.

Henry/teikedvl也开发了一个类似的工具(http://scriptechocolor.sourceforge.net/)用来简化彩色脚本的创建.

注意事项

[1]

当然, ANSI是美国国家标准组织(American National Standards Institute)的缩写. 这个令人敬畏的组织建立和维护着许多技术和工业的标准.


前一页首页下一页
递归上一级优化
曾经有很多次机会可以避免bug,将项目按时,保质保量交付给客户,但我没有珍惜,等到世界末日,我才意识到,程序员界最痛苦的事莫过于此。如果玛雅人能给我一次重新选择的机会,让22号的太阳依然升起,我会重新做程序员,用代码改变世界!