`

高级Bash脚本编程指南

阅读更多
Steps by the Bay 海湾的楼梯 点击放大

立刻加入博客人自己的广告网

译者序

毫无疑问,UNIX/Linux最重要的软件之一就是shell,目前最流行的shell被称为Bash(Bourne Again Shell),几乎所有的Linux和绝大部分的UNIX都可以使用Bash。作为系统与用户之间的交互接口,shell几乎是你在UNIX工作平台上最亲密的朋友,因此,学好shell,是学习Linux/UNIX的的开始,并且它会始终伴随你的工作学习。

shell是如此地重要,但令人惊奇的是,介绍shell的书没有真正令人满意的。所幸的是,我看到了这本被人称为abs的书,这本书介绍了bash大量的细节和广阔的范围,我遇到的绝大部分的技术问题--无论是我忘记的或是以前没有发现的--都可以在这本书里找到答案。这本使用大量的例子详细地介绍了Bash的语法,各种技巧,调试等等的技术,以循序渐进的学习方式,让你了解Bash的所有特性,在书中还有许多练习可以引导你思考,以得到更深入的知识。无论你是新手还是老手,或是使用其他语言的程序员,我能肯定你能在此书用受益。而本书除了介绍BASH的知识之外,也有许多有用的关于Linux/UNIX的知识和其他shell的介绍。

在看到本书的英文版后,我决定把它翻译出来,在Linuxsir论坛上结识了译者之一杨春敏共同翻译这本书,600多页的书是本大部头的书,我们花了6个月的业余时间才翻译完了。

关于版权的问题,英文版的作者Mendel Cooper对英文版的版权做了详细的约定,请参考:Appendix Q. Copyright。中文版版权由译者杨春敏和黄毅共同所有,在遵守英文版版权相应条款的条件下,欢迎在保留本书译者名字和版权说明以非盈利的方式自由发布此中文版,以盈利目的的所有行为必须联系英文作者和两位中文译者以获得许可。

本书得以成稿,我(黄毅)要多谢我的女朋友,本该给予她的时间我用来了翻译,多谢你的理解,你是一个很棒的女朋友!

译者 杨春敏 黄毅
2006.5.15

Advanced Bash-Scripting Guide
<<高级Bash脚本编程指南>>
一本深入学习shell脚本艺术的书籍

Version 3.7.2
2005/11/16

作者:Mendel Cooper

mail:thegrendel@theriver.com

这本书假定你没有任何脚本或一般程序的编程知识,但是如果你有相关的知识,那么你将很容易
达到中高级的水平...all the while sneaking in little snippets of UNIX? wisdom and
lore(这句不知道怎么译).你可以把本书作为教材,自学手册,或者你获得shell脚本技术的文档.
书中的练习和例子脚本中的注释将会与读者有更好的互动,但是最关键的前提是:
想真正学习脚本编程的唯一途径就是编写脚本.

这本书也可作为教材来讲解一般的编程概念.

下载本书最新版本,http://personal.riverusers...,
这是一个以tar和bzip2进行打包的,并且是以HTML来发行的.当然,你也可以获得本书的pdf版本
http://www.tldp.org/LDP/ab...可以在
http://personal.riverusers...中查看修订历史.

译者:杨春敏,黄毅
mail:chunmin.yang@gmail.com

一直想好好学习一下bash,可惜网上的资料都杂乱不堪,我还是喜欢通过一本书系统的学习.这本
书来得正是时候.本书的作者真是非常的严谨,从例子里的改进人名单就能看出来.可惜我水平真
的是非常有限,好多地方估计译得都有问题.希望阅读的朋友们多多提些修改建议.我会尽我的最
大努力去修正它.

目录
++++
第一部分. 热身

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

7.1. Test结构
7.2. 文件测试操作
7.3. 其他比较操作
7.4. 嵌套的if/then条件test
7.5. 检查你的test知识

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 Documents

17.1. Here Strings

18. 休息时间

Part 4. 高级

19. 正则表达式

19.1. 一个简要的正则表达式介绍
19.2. 通配

20. 子shell(Subshells)
21. 受限shell(Restricted Shells)
22. 进程替换
23. 函数

23.1. 复杂函数和函数复杂性
23.2. 局部变量
23.3. 不使用局部变量的递归

24. 别名(Aliases)
25. 列表结构
26. 数组
27. /dev 和 /proc

27.1. /dev
27.2. /proc

28. 关于Zeros和Nulls
29. 调试
30. 选项
31. Gotchas
32. 脚本编程风格

32.1. 非官方的Shell脚本风格

33. 杂项

33.1. 交互式和非交互式的shells和脚本
33.2. Shell 包装
33.3. 测试和比较: 另一种方法
33.4. 递归
33.5. 彩色脚本
33.6. 优化
33.7. 各种小技巧
33.8. 安全话题

33.8.1. 被感染的脚本
33.8.2. 隐藏Shell脚本源码

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.4.1. 硬件
35.4.2. 软件和排版软件

35.5. Credits

Bibliography
A. Contributed Scripts
B. Reference Cards
C. A Sed and Awk Micro-Primer

C.1. Sed
C.2. Awk

D. Exit Codes With Special Meanings
E. A Detailed Introduction to I/O and I/O Redirection
F. Standard Command-Line Options
G. Important Files
H. Important System Directories
I. Localization
J. History Commands
K. A Sample .bashrc File
L. Converting DOS Batch Files to Shell Scripts
M. Exercises

M.1. Analyzing Scripts
M.2. Writing Scripts

N. Revision History
O. Mirror Sites
P. To Do List
Q. Copyright

表格清单:

11-1. 作业标识符
30-1. Bash 选项
33-1. 转义序列中数值和彩色的对应
B-1. Special Shell Variables
B-2. TEST Operators: Binary Comparison
B-3. TEST Operators: Files
B-4. Parameter Substitution and Expansion
B-5. String Operations
B-6. Miscellaneous Constructs
C-1. Basic sed operators
C-2. Examples of sed operators
D-1. "Reserved" Exit Codes
L-1. Batch file keywords / variables / operators, and their shell equivalents
L-2. DOS commands and their UNIX equivalents
N-1. Revision History

例子清单:

2-1. 清除:清除/var/log下的log文件
2-2. 清除:一个改良的清除脚本
2-3. cleanup:一个增强的和广义的删除logfile的脚本
3-1. 代码块和I/O重定向
3-2. 将一个代码块的结果保存到文件
3-3. 在后台运行一个循环
3-4. 备份最后一天所有修改的文件.
4-1. 变量赋值和替换
4-2. 一般的变量赋值
4-3. 变量赋值,一般的和比较特殊的
4-4. 整型还是string?
4-5. 位置参数
4-6. wh,whois节点名字查询
4-7. 使用shift
5-1. echo一些诡异的变量
5-2. 转义符
6-1. exit/exit状态
6-2. 否定一个条件使用!
7-1. 什么情况下为真?
7-2. 几个等效命令test,/usr/bin/test,[],和/usr/bin/[
7-3. 算数测试使用(( ))
7-4. test死的链接文件
7-5. 数字和字符串比较
7-6. 测试字符串是否为null
7-7. zmore
8-1. 最大公约数
8-2. 使用算术操作符
8-3. 使用&&和||进行混合状态的test
8-4. 数字常量的处理
9-1. $IFS和空白
9-2. 时间输入
9-3. 再来一个时间输入
9-4. Timed read
9-5. 我是root?
9-6. arglist:通过$*和$@列出所有的参数
9-7. 不一致的$*和$@行为
9-8. 当$IFS为空时的$*和$@
9-9. 下划线变量
9-10. 在一个文本文件的段间插入空行
9-11. 利用修改文件名,来转换图片格式
9-12. 模仿getopt命令
9-13. 提取字符串的一种可选的方法
9-14. 使用参数替换和error messages
9-15. 参数替换和"usage"messages
9-16. 变量长度
9-17. 参数替换中的模式匹配
9-18. 重命名文件扩展名
9-19. 使用模式匹配来分析比较特殊的字符串
9-20. 对字符串的前缀或后缀使用匹配模式
9-21. 使用declare来指定变量的类型
9-22. 间接引用
9-23. 传递一个间接引用给awk
9-24. 产生随机数
9-25. 从一副扑克牌中取出一张随机的牌
9-26. 两个指定值之间的随机数
9-27. 使用随机数来摇一个骰子
9-28. 重新分配随机数种子
9-29. 使用awk产生伪随机数
9-30. C风格的变量处理
10-1. 循环的一个简单例子
10-2. 每个[list]元素带两个参数的for循环
10-3. 文件信息:对包含在变量中的文件列表进行操作
10-4. 在for循环中操作文件
10-5. 在for循环中省略[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. Unset一个变量
11-18. 使用export命令传递一个变量到一个内嵌awk的脚本中
11-19. 使用getopts命令来读取传递给脚本的选项/参数.
11-20. "Including"一个数据文件
11-21. 一个没什么用的,source自身的脚本
11-22. exec的效果
11-23. 一个exec自身的脚本
11-24. 在继续处理之前,等待一个进程的结束
11-25. 一个结束自身的脚本.
12-1. 使用ls命令来创建一个烧录CDR的内容列表
12-2. Hello or 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. Generating "Crypto-Quote" Puzzles
12-23. 格式化文件列表.
12-24. 使用 column 来格式化目录列表
12-25. nl: 一个自己计算行号的脚本.
12-26. manview: 查看格式化的man页
12-27. 使用 cpio 来拷贝一个目录树
12-28. 解包一个 rpm 归档文件
12-29. 从 C 文件中去掉注释
12-30. Exploring /usr/X11R6/bin
12-31. 一个"改进过"的 strings 命令
12-32. 在一个脚本中使用 cmp 来比较2个文件.
12-33. basename 和 dirname
12-34. 检查文件完整性
12-35. Uudecod 编码后的文件
12-36. 查找滥用的连接来报告垃圾邮件发送者
12-37. 分析一个垃圾邮件域
12-38. 获得一份股票报价
12-39. 更新 Fedora Core 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. Checking a remote server for identd<rojy bug=""></rojy>
13-6. pidof 帮助杀掉一个进程
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重定向标准输入
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循环重定向 loop (将标准输入和标准输出都重定向了)
16-10. 重定向if/then测试结构
16-11. 用于上面例子的"names.data"数据文件
16-12. 记录日志事件
17-1. 广播: 发送消息给每个登录上的用户
17-2. 仿造文件: 创建一个两行的仿造文件
17-3. 使用cat的多行消息
17-4. 带有抑制tab功能的多行消息
17-5. 使用参数替换的here document
17-6. 上传一个文件对到"Sunsite"的incoming目录
17-7. 关闭参数替换
17-8. 一个产生另外一个脚本的脚本
17-9. Here documents与函数
17-10. "匿名" here Document
17-11. 注释掉一段代码块
17-12. 一个自文档化(self-documenting)的脚本
17-13. 在一个文件的开头添加文本
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. 用户名的真实名
23-12. 局部变量的可见范围
23-13. 用局部变量来递归
23-14. 汉诺塔
24-1. 脚本中的别名
24-2. unalias: 设置和删除别名
25-1. 使用"与列表(and list)"来测试命令行参数
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. 丢失关键字(keyword)
29-3. 另一个错误脚本
29-4. 用"assert"测试条件
29-5. 捕捉 exit
29-6. 在Control-C后清除垃圾
29-7. 跟踪变量
29-8. 运行多进程 (在多处理器的机器里)
31-1. 数字和字符串比较是不相等同的
31-2. 子SHELL缺陷
31-3. 把echo的输出用管道输送给read命令
33-1. shell 包装
33-2. 稍微复杂一些的shell包装
33-3. 写到日志文件的shell包装
33-4. 包装awk的脚本
33-5. 另一个包装awk的脚本
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. 整型还是string?
33-17. 传递和返回数组
33-18. anagrams游戏
33-19. 在shell脚本中调用的窗口部件
34-1. 字符串扩展
34-2. 间接变量引用 - 新方法
34-3. 使用间接变量引用的简单数据库应用
34-4. 用数组和其他的小技巧来处理四人随机打牌

A-1. mailformat: Formatting an e-mail message
A-2. rn: A simple-minded file rename utility
A-3. blank-rename: renames filenames containing blanks
A-4. encryptedpw: Uploading to an ftp site, using a locally encrypted password
A-5. copy-cd: Copying a data CD
A-6. Collatz series
A-7. days-between: Calculate number of days between two dates
A-8. Make a "dictionary"
A-9. Soundex conversion
A-10. "Game of Life"
A-11. Data file for "Game of Life"
A-12. behead: Removing mail and news message headers
A-13. ftpget: Downloading files via ftp
A-14. password: Generating random 8-character passwords
A-15. fifo: Making daily backups, using named pipes
A-16. Generating prime numbers using the modulo operator
A-17. tree: Displaying a directory tree
A-18. string functions: C-like string functions
A-19. Directory information
A-20. Object-oriented database
A-21. Library of hash functions
A-22. Colorizing text using hash functions
A-23. Mounting USB keychain storage devices
A-24. Preserving weblogs
A-25. Protecting literal strings
A-26. Unprotecting literal strings
A-27. Spammer Identification
A-28. Spammer Hunt
A-29. Making wget easier to use
A-30. A "podcasting" script
A-31. Basics Reviewed
A-32. An expanded cd command
C-1. Counting Letter Occurrences
K-1. Sample .bashrc file
L-1. VIEWDATA.BAT: DOS Batch File
L-2. viewdata.sh: Shell Script Conversion of VIEWDATA.BAT
P-1. Print the server environment

第一部分 热身
++++++++++++++++
shell是一个命令解释器.是介于操作系统kernel与用户之间的一个绝缘层.准确地说,它也是一
一种强力的计算机语言.一个shell程序,被称为一个脚本,是一种很容易使用的工具,它可以通过
将系统调用,公共程序,工具,和编译过的二进制程序粘合在一起来建立应用.事实上,所有的UNIX
命令和工具再加上公共程序,对于shell脚本来说,都是可调用的.如果这些你还觉得不够,那么
shell内建命令,比如test与循环结构,也会给脚本添加强力的支持和增加灵活性.Shell脚本对于
管理系统任务和其它的重复工作的例程来说,表现的非常好,根本不需要那些华而不实的成熟
紧凑的程序语言.

第1章 为什么使用shell编程
===========================
没有程序语言是完美的.甚至没有一个唯一最好的语言,只有对于特定目的,比较适合和不适合
的程序语言.
Herbert Mayer

对于任何想适当精通一些系统管理知识的人来说,掌握shell脚本知识都是最基本的,即使这些
人可能并不打算真正的编写一些脚本.想一下Linux机器的启动过程,在这个过程中,必将运行
/etc/rc.d目录下的脚本来存储系统配置和建立服务.详细的理解这些启动脚本对于分析系统的
行为是非常重要的,并且有时候可能必须修改它.

学习如何编写shell脚本并不是一件很困难的事,因为脚本可以分为很小的块,并且相对于shell
特性的操作和选项[1]部分,只需要学习很小的一部分就可以了.语法是简单并且直观的,编写脚
本很像是在命令行上把一些相关命令和工具连接起来,并且只有很少的一部分规则需要学习.
绝大部分脚本第一次就可以正常的工作,而且即使调试一个长一些的脚本也是很直观的.

一个shell脚本是一个类似于小吃店的(quick and dirty)方法,在你使用原型设计一个复杂的
应用的时候.在工程开发的第一阶段,即使从功能中取得很有限的一个子集放到shell脚本中来
完成往往都是非常有用的.使用这种方法,程序的结果可以被测试和尝试运行,并且在处理使用
诸如C/C++,Java或者Perl语言编写的最终代码前,主要的缺陷和陷阱往往就被发现了.

Shell脚本遵循典型的UNIX哲学,就是把大的复杂的工程分成小规模的子任务,并且把这些部件
和工具组合起来.许多人认为这种办法更好一些,至少这种办法比使用那种高\大\全的语言更
美,更愉悦,更适合解决问题.比如Perl就是这种能干任何事能适合任何人的语言,但是代价就是
你需要强迫自己使用这种语言来思考解决问题的办法.

什么时候不使用Shell脚本

资源密集型的任务,尤其在需要考虑效率时(比如,排序,hash等等)

需要处理大任务的数学操作,尤其是浮点运算,精确运算,或者复杂的算术运算
(这种情况一般使用C++或FORTRAN来处理)

有跨平台移植需求(一般使用C或Java)

复杂的应用,在必须使用结构化编程的时候(需要变量的类型检查,函数原型,等等)

对于影响系统全局性的关键任务应用。

对于安全有很高要求的任务,比如你需要一个健壮的系统来防止入侵,破解,恶意破坏等等.

项目由连串的依赖的各个部分组成。

需要大规模的文件操作

需要多维数组的支持

需要数据结构的支持,比如链表或数等数据结构

需要产生或操作图形化界面GUI

需要直接操作系统硬件

需要I/O或socket接口

需要使用库或者遗留下来的老代码的接口

私人的,闭源的应用(shell脚本把代码就放在文本文件中,全世界都能看到)

如果你的应用符合上边的任意一条,那么就考虑一下更强大的语言吧--或许是Perl,Tcl,Python,
Ruby -- 或者是更高层次的编译语言比如C/C++,或者是Java.即使如此,你会发现,使用shell
来原型开发你的应用,在开发步骤中也是非常有用的.

我们将开始使用Bash,Bash是"Bourne-Again shell"首字母的缩写,也是Stephen Bourne的经典
的Bourne shell的一个双关语,(译者:说实话,我一直搞不清这个双关语是什么意思,为什么叫
"Bourn-Again shell",这其中应该有个什么典故吧,哪位好心,告诉我一下^^).Bash已经成为了
所有UNIX中shell脚本的事实上的标准了.同时这本书也覆盖了绝大部分的其他一些shell的原
则,比如Korn Shell,Bash从ksh中继承了一部分特性,[2]C Shell和它的变种.(注意:C Shell
编程是不被推荐的,因为一些特定的内在问题,Tom Christiansen在1993年10月指出了这个问题
请在http://www.etext.org/Quart...中查看具体内容.)

接下来是脚本的一些说明.在展示shell不同的特征之前,它可以减轻一些阅读书中例子
的负担.本书中的例子脚本,都在尽可能的范围内进行了测试,并且其中的一些将使用在真
实的生活中.读者可以运行这些例子脚本(使用scriptname.sh或者scriptname.bash的形式),
[3]并给这些脚本执行权限(chmod u+rx scriptname),然后执行它们,看看发生了什么.如果存
档的脚本不可用,那么就从本书的HTML,pdf或者text的发行版本中把它们拷贝粘贴出来.考虑到
这些脚本中的内容在我们还没解释它之前就被列在这里,可能会影响读者的理解,这就需要读者
暂时忽略这些内容.

除非特别注明,本书作者编写了本书中的绝大部分例子脚本.

注意事项:
[1] 这些在builtins章节被引用,这些是shell的内部特征.
[2] ksh88的许多特性,甚至是一些ksh93的特性都被合并到Bash中了.
[3] 根据惯例,用户编写的Bourne shell脚本应该在脚本的名字后边加上.sh扩展名.
一些系统脚本,比如那些在/etc/rc.d中的脚本,则不遵循这种命名习惯.



第2章 带着一个Sha-Bang出发(Sha-Bang指的是#!)
==============================================
在一个最简单的例子中,一个shell脚本其实就是将一堆系统命令列在一个文件中.它的最基本的
用处就是,在你每次输入这些特定顺序的命令时可以少敲一些字.

Example 2-1 清除:清除/var/log下的log文件
################################Start Script#######################################
1 # Cleanup
2 # 当然要使用root身份来运行这个脚本
3
4 cd /var/log
5 cat /dev/null > messages
6 cat /dev/null > wtmp
7 echo "Logs cleaned up."
################################End Script#########################################
这根本就没什么稀奇的, 只不过是命令的堆积, 来让从console或者xterm中一个一个的输入命
令更方便一些.好处就是把所有命令都放在一个脚本中,不用每次都敲它们.这样的话,对于特定
的应用来说,这个脚本就很容易被修改或定制.

Example 2-2 清除:一个改良的清除脚本
################################Start Script#######################################
1 #!/bin/bash
2 # 一个Bash脚本的正确的开头部分.
3
4 # Cleanup, 版本 2
5
6 # 当然要使用root身份来运行.
7 # 在此处插入代码,来打印错误消息,并且在不是root身份的时候退出.
8
9 LOG_DIR=/var/log
10 # 如果使用变量,当然比把代码写死的好.
11 cd $LOG_DIR
12
13 cat /dev/null > messages
14 cat /dev/null > wtmp
15
16
17 echo "Logs cleaned up."
18
19 exit # 这个命令是一种正确并且合适的退出脚本的方法.
################################End Script#########################################

现在,让我们看一下一个真正意义的脚本.而且我们可以走得更远...
Example 2-3. cleanup:一个增强的和广义的删除logfile的脚本
################################Start Script#######################################
1 #!/bin/bash
2 # 清除, 版本 3
3
4 # Warning:
5 # -------
6 # 这个脚本有好多特征,这些特征是在后边章节进行解释的,大概是进行到本书的一半的
7 # 时候,
8 # 你就会觉得它没有什么神秘的了.
9 #
10
11
12
13 LOG_DIR=/var/log
14 ROOT_UID=0 # $UID为0的时候,用户才具有根用户的权限
15 LINES=50 # 默认的保存行数
16 E_XCD=66 # 不能修改目录?
17 E_NOTROOT=67 # 非根用户将以error退出
18
19
20 # 当然要使用根用户来运行
21 if [ "$UID" -ne "$ROOT_UID" ]
22 then
23 echo "Must be root to run this script."
24 exit $E_NOTROOT
25 fi
26
27 if [ -n "$1" ]
28 # 测试是否有命令行参数(非空).
29 then
30 lines=$1
31 else
32 lines=$LINES # 默认,如果不在命令行中指定
33 fi
34
35
36 # Stephane Chazelas 建议使用下边
37 #+ 的更好方法来检测命令行参数.
38 #+ 但对于这章来说还是有点超前.
39 #
40 # E_WRONGARGS=65 # 非数值参数(错误的参数格式)
41 #
42 # case "$1" in
43 # "" ) lines=50;;
44 # *[!0-9]*) echo "Usage: `basename $0` file-to-cleanup"; exit $E_WRONGARGS;;
45 # * ) lines=$1;;
46 # esac
47 #
48 #* 直到"Loops"的章节才会对上边的内容进行详细的描述.
49
50
51 cd $LOG_DIR
52
53 if [ `pwd` != "$LOG_DIR" ] # 或者 if[ "$PWD" != "$LOG_DIR" ]
54 # 不在 /var/log中?
55 then
56 echo "Can't change to $LOG_DIR."
57 exit $E_XCD
58 fi # 在处理log file之前,再确认一遍当前目录是否正确.
59
60 # 更有效率的做法是
61 #
62 # cd /var/log || {
63 # echo "Cannot change to necessary directory." >&2
64 # exit $E_XCD;
65 # }
66
67
68
69
70 tail -$lines messages > mesg.temp # 保存log file消息的最后部分.
71 mv mesg.temp messages # 变为新的log目录.
72
73
74 # cat /dev/null > messages
75 #* 不再需要了,使用上边的方法更安全.
76
77 cat /dev/null > wtmp # ': > wtmp' 和 '> wtmp'具有相同的作用
78 echo "Logs cleaned up."
79
80 exit 0
81 # 退出之前返回0,返回0表示成功.
82 #
################################End Script#########################################

因为你可能希望将系统log全部消灭,这个版本留下了log消息最后的部分.你将不断地找到新
的方法来完善这个脚本,并提高效率.

要注意,在每个脚本的开头都使用"#!",这意味着告诉你的系统这个文件的执行需要指定一个解
释器.#!实际上是一个2字节[1]的魔法数字,这是指定一个文件类型的特殊标记, 换句话说, 在
这种情况下,指的就是一个可执行的脚本(键入man magic来获得关于这个迷人话题的更多详细
信息).在#!之后接着是一个路径名.这个路径名指定了一个解释脚本中命令的程序,这个程序可
以是shell,程序语言或者是任意一个通用程序.这个指定的程序从头开始解释并且执行脚本中
的命令(从#!行下边的一行开始),忽略注释.[2]
如:
1 #!/bin/sh
2 #!/bin/bash
3 #!/usr/bin/perl
4 #!/usr/bin/tcl
5 #!/bin/sed -f
6 #!/usr/awk -f

上边每一个脚本头的行都指定了一个不同的命令解释器,如果是/bin/sh,那么就是默认shell
(在Linux系统中默认是Bash).[3]使用#!/bin/sh,在大多数商业发行的UNIX上,默认是Bourne
shell,这将让你的脚本可以正常的运行在非Linux机器上,虽然这将会牺牲Bash一些独特的特征.
脚本将与POSIX[4] 的sh标准相一致.

注意: #! 后边给出的路径名必须是正确的,否则将会出现一个错误消息,通常是
"Command not found",这将是你运行这个脚本时所得到的唯一结果.

当然"#!"也可以被忽略,不过这样你的脚本文件就只能是一些命令的集合,不能够使用shell内建
的指令了,如果不能使用变量的话,当然这也就失去了脚本编程的意义了.

注意:这个例子鼓励你使用模块化的方式来编写脚本,平时也要注意收集一些零碎的代码,
这些零碎的代码可能用在你将来编写的脚本中.这样你就可以通过这些代码片段来构
造一个较大的工程用例. 以下边脚本作为序,来测试脚本被调用的参数是否正确.
################################Start Script#######################################
1 E_WRONG_ARGS=65
2 script_parameters="-a -h -m -z"
3 # -a = all, -h = help, 等等.
4
5 if [ $# -ne $Number_of_expected_args ]
6 then
7 echo "Usage: `basename $0` $script_parameters"
8 # `basename $0`是这个脚本的文件名
9 exit $E_WRONG_ARGS
10 fi
################################End Script#########################################
大多数情况下,你需要编写一个脚本来执行一个特定的任务,在本章中第一个脚本就是一个这样
的例子, 然后你会修改它来完成一个不同的,但比较相似的任务.用变量来代替写死的常量,就是
一个好方法,将重复的代码放到一个函数中,也是一种好习惯.


2.1 调用一个脚本
----------------
编写完脚本之后,你可以使用sh scriptname,[5]或者bash scriptname来调用它.
(不推荐使用sh 更方便的方法是让脚本本身就具有可执行权限,通过chmod命令可以修改.

比如:
chmod 555 scriptname (允许任何人都具有 可读和执行权限) [6]
或:
chmod +rx scriptname (允许任何人都具有 可读和执行权限)
chmod u+rx scriptname (只给脚本的所有者 可读和执行权限)

既然脚本已经具有了可执行权限,现在你可以使用./scriptname.[7]来测试它了.如果这个脚本
以一个"#!"行开头,那么脚本将会调用合适的命令解释器来运行.

最后一步,在脚本被测试和debug之后,你可能想把它移动到/usr/local/bin(当然是以root身份)
,来让你的脚本对所有用户都有用.这样用户就可以直接敲脚本名字来运行了.

注意事项:
[1] 那些具有UNIX味道的脚本(基于4.2BSD)需要一个4字节的魔法数字,在#!后边需要一个
空格#! /bin/sh.
[2] 脚本中的#!行的最重要的任务就是命令解释器(sh或者bash).因为这行是以#开始的,
当命令解释器执行这个脚本的时候,会把它作为一个注释行.当然,在这之前,这行语句
已经完成了它的任务,就是调用命令解释器.

如果在脚本的里边还有一个#!行,那么bash将把它认为是一个一般的注释行.
1 #!/bin/bash
2
3 echo "Part 1 of script."
4 a=1
5
6 #!/bin/bash
7 # 这将不会开始一个新脚本.
8
9 echo "Part 2 of script."
10 echo $a # Value of $a stays at 1.
[3] 这里可以玩一些小技巧.
1 #!/bin/rm
2 # 自删除脚本.
3
4 # 当你运行这个脚本时,基本上什么都不会发生...除非这个文件消失不见.
5
6 WHATEVER=65
7
8 echo "This line will never print (betcha!)."
9
10 exit $WHATEVER # 没关系,脚本是不会在这退出的.
当然,你还可以试试在一个README文件的开头加上#!/bin/more,并让它具有执行权限.
结果将是文档自动列出自己的内容.(一个使用cat命令的here document可能是一个
更好的选则,--见Example 17-3).
[4] 可移植的操作系统接口,标准化类UNIX操作系统的一种尝试.POSIX规范可以在
http://www.opengroup.org/o...中查阅.
[5] 小心:使用sh scriptname来调用脚本的时候将会关闭一些Bash特定的扩展,脚本可能
因此而调用失败.
[6] 脚本需要读和执行权限,因为shell需要读这个脚本.
[7] 为什么不直接使用scriptname来调用脚本?如果你当前的目录下($PWD)正好有你想要
执行的脚本,为什么它运行不了呢?失败的原因是,出于安全考虑,当前目录并没有被
加在用户的$PATH变量中.因此,在当前目录下调用脚本必须使用./scriptname这种
形式.


2.2 初步的练习
--------------
1. 系统管理员经常会为了自动化一些常用的任务而编写脚本.举出几个这种有用的脚本的实例.
2. 编写一个脚本,显示时间和日期,列出所有的登录用户,显示系统的更新时间.然后这个脚本
将会把这些内容保存到一个log file中.


第二部分 基本
++++++++++++++++

第3章 特殊字符
================

# 注释,行首以#开头为注释(#!是个例外).

1 # This line is a comment.

注释也可以存在于本行命令的后边.

1 echo "A comment will follow." # 注释在这里
2 # ^ 注意#前边的空白

注释也可以在本行空白的后边.

1 # A tab precedes this comment.

注意:命令是不能跟在同一行上注释的后边的,没有办法,在同一行上,注释的后边想
要再使用命令,只能另起一行.
当然,在echo命令中被转义的#是不能作为注释的.
同样的,#也可以出现在特定的参数替换结构中或者是数字常量表达式中.

1 echo "The # here does not begin a comment."
2 echo 'The # here does not begin a comment.'
3 echo The \# here does not begin a comment.
4 echo The # 这里开始一个注释
5
6 echo ${PATH#*:} # 参数替换,不是一个注释
7 echo $(( 2#101011 )) # 数制转换,不是一个注释
8
9 # Thanks, S.C.

标准的引用和转义字符("'\)可以用来转义#

; 命令分隔符,可以用来在一行中来写多个命令.

1 echo hello; echo there
2
3
4 if [ -x "$filename" ]; then # 注意:"if"和"then"需要分隔
5 # 为啥?
6 echo "File $filename exists."; cp $filename $filename.bak
7 else
8 echo "File $filename not found."; touch $filename
9 fi; echo "File test complete."

有时候需要转义

;; 终止"case"选项.

1 case "$variable" in
2 abc) echo "\$variable = abc" ;;
3 xyz) echo "\$variable = xyz" ;;
4 esac

. .命令等价于source命令(见Example 11-20).这是一个bash的内建命令.

. .作为文件名的一部分.如果作为文件名的前缀的话,那么这个文件将成为隐藏文件.
将不被ls命令列出.

bash$ touch .hidden-file
bash$ ls -l
total 10
-rw-r--r-- 1 bozo 4034 Jul 18 22:04 data1.addressbook
-rw-r--r-- 1 bozo 4602 May 25 13:58 data1.addressbook.bak
-rw-r--r-- 1 bozo 877 Dec 17 2000 employment.addressbook


bash$ ls -al
total 14
drwxrwxr-x 2 bozo bozo 1024 Aug 29 20:54 ./
drwx------ 52 bozo bozo 3072 Aug 29 20:51 ../
-rw-r--r-- 1 bozo bozo 4034 Jul 18 22:04 data1.addressbook
-rw-r--r-- 1 bozo bozo 4602 May 25 13:58 data1.addressbook.bak
-rw-r--r-- 1 bozo bozo 877 Dec 17 2000 employment.addressbook
-rw-rw-r-- 1 bozo bozo 0 Aug 29 20:54 .hidden-file

.命令如果作为目录名的一部分的话,那么.表达的是当前目录.".."表示上一级目录.

bash$ pwd
/home/bozo/projects

bash$ cd .
bash$ pwd
/home/bozo/projects

bash$ cd ..
bash$ pwd
/home/bozo/

.命令经常作为一个文件移动命令的目的地.

bash$ cp /home/bozo/current_work/junk/* .

. .字符匹配,这是作为正则表达是的一部分,用来匹配任何的单个字符.

" 部分引用."STRING"阻止了一部分特殊字符,具体见第5章.

' 全引用. 'STRING' 阻止了全部特殊字符,具体见第5章.

, 逗号链接了一系列的算术操作,虽然里边所有的内容都被运行了,但只有最后一项被
返回.

如:
1 let "t2 = ((a = 9, 15 / 3))" # Set "a = 9" and "t2 = 15 / 3"

\ 转义字符,如\X等价于"X"或'X',具体见第5章.

/ 文件名路径分隔符.或用来做除法操作.

` 后置引用,命令替换,具体见第14章

: 空命令,等价于"NOP"(no op,一个什么也不干的命令).也可以被认为与shell的内建命令(true)作用相同.":"命令是一
个bash的内建命令,它的返回值为0,就是shell返回的true.

如:
1 :
2 echo $? # 0

死循环,如:

1 while :
2 do
3 operation-1
4 operation-2
5 ...
6 operation-n
7 done
8
9 # 与下边相同:
10 # while true
11 # do
12 # ...
13 # done

在if/then中的占位符,如:
1 if condition
2 then : # 什么都不做,引出分支.
3 else
4 take-some-action
5 fi

在一个2元命令中提供一个占位符,具体见Example 8-2,和"默认参数".如:
1 : ${username=`whoami`}
2 # ${username=`whoami`} 如果没有":"的话,将给出一个错误,除非"username"是
3 # 个命令
在here document中提供一个占位符,见Example 17-10.

使用"参数替换"来评估字符串变量(见Example 9-14).如:
1 : ${HOSTNAME?} ${USER?} ${MAIL?}
2 # 如果一个或多个必要的环境变量没被设置的话,
3 #+ 就打印错误信息.

"变量扩展/子串替换"
在和 > (重定向操作符)结合使用时,把一个文件截断到0长度,没有修改它的权限.
如果文件在之前并不存在,那么就创建它.如:
1 : > data.xxx #文件"data.xxx"现在被清空了.
2
3 #与 cat /dev/null >data.xxx 的作用相同
4 #然而,这不会产生一个新的进程,因为":"是一个内建命令.
具体参见Example 12-14.

在和>>重定向操作符结合使用时,将不会对想要附加的文件产生任何影响.
如果文件不存在,将创建.
注意: 这只适用于正规文件,而不是管道,符号连接,和某些特殊文件.

也可能用来作为注释行,虽然我们不推荐这么做.使用#来注释的话,将关闭剩余行的
错误检查,所以可以在注释行中写任何东西.然而,使用:的话将不会这样.如:
1 : This is a comment thar generates an error,(if [ $x -eq 3] ).

":"还用来在/etc/passwd和$PATH变量中用来做分隔符.
bash$ echo $PATH
/usr/local/bin:/bin:/usr/X11R6/bin:/sbin:/usr/sbin:/usr/games
! 取反操作符,将反转"退出状态"结果,(见Example 6-2).也会反转test操作符的意义.比
如修改=为!=.!操作是Bash的一个关键字.

在一个不同的上下文中,!也会出现在"间接变量引用"见Example 9-22.

在另一种上下文中,!还能反转bash的"history mechanism"(见附录J 历史命令)
需要注意的是,在一个脚本中,"history mechanism"是被禁用的.

* 万能匹配字符,用于文件名匹配(这个东西有个专有名词叫file globbing),或者是正则
表达式中.注意:在正则表达式匹配中的作用和在文件名匹配中的作用是不同的.
bash$ echo *
abs-book.sgml add-drive.sh agram.sh alias.sh
* 数学乘法.
**是幂运算.
? 测试操作.在一个确定的表达式中,用?来测试结果.
(())结构可以用来做数学计算或者是写c代码,那?就是c语言的3元操作符的
一个.
在"参数替换"中,?测试一个变量是否被set了.
? 在file globbing中和在正则表达式中一样匹配任意的单个字符.

$ 变量替换
1 var1=5
2 var2=23skidoo
3
4 echo $var1 # 5
5 echo $var2 # 23skidoo
$ 在正则表达式中作为行结束符.
${} 参数替换,见9.3节.
$*,$@ 位置参数
$? 退出状态变量.$?保存一个命令/一个函数或者脚本本身的退出状态.
$$ 进程ID变量.这个$$变量保存运行脚本进程ID
() 命令组.如:
1 (a=hello;echo $a)
注意:在()中的命令列表,将作为一个子shell来运行.
在()中的变量,由于是在子shell中,所以对于脚本剩下的部分是不可用的.
如:
1 a=123
2 ( a=321; )
3
4 echo "a = $a" # a = 123
5 # 在圆括号中a变量,更像是一个局部变量.

用在数组初始化,如:
1 Array=(element1,element2,element3)

{xxx,yyy,zzz...}
大括号扩展,如:
1 cat {file1,file2,file3} > combined_file
2 # 把file1,file2,file3连接在一起,并且重定向到combined_file中.
3
4
5 cp file22.{txt,backup}
6 # 拷贝"file22.txt" 到"file22.backup"中

一个命令可能会对大括号中的以逗号分割的文件列表起作用[1]. file globbing将对
大括号中的文件名作扩展.
注意: 在大括号中,不允许有空白,除非这个空白是有意义的.
echo {file1,file2}\ :{\ A," B",' C'}
file1 : A file1 : B file1 : C file2 : A file2 : B file2 : C
{} 代码块.又被称为内部组.事实上,这个结构创建了一个匿名的函数.但是与函数不同的
是,在其中声明的变量,对于脚本其他部分的代码来说还是可见的.如:
bash$
{
local a;
a= 123;
}
bash中的local申请的变量只能够用在函数中.

1 a=123
2 { a=321; }
3 echo "a = $a" # a = 321 (说明在代码块中对变量a所作的修改,影响了外边的变量a)
4
5 # Thanks, S.C.

下边的代码展示了在{}结构中代码的I/O重定向.

Example 3-1. 代码块和I/O重定向
################################Start Script#######################################
1 #!/bin/bash
2 # 从 /etc/fstab中读行
3
4 File=/etc/fstab
5
6 {
7 read line1
8 read line2
9 } < $File
10
11 echo "First line in $File is:"
12 echo "$line1"
13 echo
14 echo "Second line in $File is:"
15 echo "$line2"
16
17 exit 0
18
19 # 现在,你怎么分析每行的分割域
20 # 暗示: 使用 awk.
################################End Script#########################################

Example 3-2. 将一个代码块的结果保存到文件
################################Start Script#######################################
1 #!/bin/bash
2 # rpm-check.sh
3
4 # 这个脚本的目的是为了描述,列表,和确定是否可以安装一个rpm包.
5 # 在一个文件中保存输出.
6 #
7 # 这个脚本使用一个代码块来展示
8
9 SUCCESS=0
10 E_NOARGS=65
11
12 if [ -z "$1" ]
13 then
14 echo "Usage: `basename $0` rpm-file"
15 exit $E_NOARGS
16 fi
17
18 {
19 echo
20 echo "Archive Description:"
21 rpm -qpi $1 # 查询说明
22 echo
23 echo "Archive Listing:"
24 rpm -qpl $1 # 查询列表
25 echo
26 rpm -i --test $1 # 查询rpm包是否可以被安装
27 if [ "$?" -eq $SUCCESS ]
28 then
29 echo "$1 can be installed."
30 else
31 echo "$1 cannot be installed."
32 fi
33 echo
34 } > "$1.test" # 把代码块中的所有输出都重定向到文件中
35
36 echo "Results of rpm test in file $1.test"
37
38 # 查看rpm的man页来查看rpm的选项
39
40 exit 0
################################End Script#########################################
注意: 与()中的命令不同的是,{}中的代码块将不能正常地开启一个新shell.[2]

{} \; 路径名.一般都在find命令中使用.这不是一个shell内建命令.
注意: ";"用来结束find命令序列的-exec选项.

[] test.
test的表达式将在[]中.
值得注意的是[是shell内建test命令的一部分,并不是/usr/bin/test中的扩展命令
的一个连接.

[[]] test.
test表达式放在[[]]中.(shell关键字)
具体查看[[]]结构的讨论.

[] 数组元素
Array[1]=slot_1
echo ${Array[1]}

[] 字符范围
在正则表达式中使用,作为字符匹配的一个范围

(()) 数学计算的扩展
在(())结构中可以使用一些数字计算.
具体参阅((...))结构.

>&>>&>><
重定向.
scriptname >filename 重定向脚本的输出到文件中.覆盖文件原有内容.
command &>filename 重定向stdout和stderr到文件中
command >&2 重定向command的stdout到stderr
scriptname >>filename 重定向脚本的输出到文件中.添加到文件尾端,如果没有文件,
则创建这个文件.

进程替换,具体见"进程替换部分",跟命令替换极其类似.
(command)>
<(command)

<和> 可用来做字符串比较
<和> 可用在数学计算比较

<< 重定向,用在"here document"

<<< 重定向,用在"here string"

<,> ASCII比较
1 veg1=carrots
2 veg2=tomatoes
3
4 if [[ "$veg1" < "$veg2" ]]
5 then
6 echo "Although $veg1 precede $veg2 in the dictionary,"
7 echo "this implies nothing about my culinary preferences."
8 else
9 echo "What kind of dictionary are you using, anyhow?"
10 fi

\<,\> 正则表达式中的单词边界.如:
bash$grep '\' textfile

| 管道.分析前边命令的输出,并将输出作为后边命令的输入.这是一种产生命令链的
好方法.
1 echo ls -l | sh
2 # 传递"echo ls -l"的输出到shell中,
3 #+ 与一个简单的"ls -l"结果相同.
4
5
6 cat *.lst | sort | uniq
7 # 合并和排序所有的".lst"文件,然后删除所有重复的行.

管道是进程间通讯的一个典型办法,将一个进程的stdout放到另一个进程的stdin中.
标准的方法是将一个一般命令的输出,比如cat或echo,传递到一个过滤命令中(在这个
过滤命令中将处理输入),得到结果,如:
cat $filename1 | $filename2 | grep $search_word

当然输出的命令也可以传递到脚本中.如:
################################Start Script#######################################
1 #!/bin/bash
2 # uppercase.sh : 修改输出,全部转换为大写
3
4 tr 'a-z' 'A-Z'
5 # 字符范围必须被""引用起来
6 #+ 来阻止产生单字符的文件名.
7
8 exit 0
################################End Script#########################################

现在让我们输送ls -l的输出到一个脚本中.
bash$ ls -l | ./uppercase.sh
-RW-RW-R-- 1 BOZO BOZO 109 APR 7 19:49 1.TXT
-RW-RW-R-- 1 BOZO BOZO 109 APR 14 16:48 2.TXT
-RW-R--R-- 1 BOZO BOZO 725 APR 20 20:56 DATA-FILE

注意:管道中的一个进程的stdout必须被下一个进程作为stdin读入.否则,数据流会阻
塞,并且管道将产生非预期的行为.
如:
1 cat file1 file2 | ls -l | sort
2 #从"cat file1 file2"中的输出并没出现

作为子进程的运行的管道,不能够改变脚本的变量.
1 variable="initial_value"
2 echo "new_value" | read variable
3 echo "variable = $variable" #variable = initial_value
如果管道中的某个命令产生了一个异常,并中途失败,那么这个管道将过早的终止.
这种行为被叫做a broken pipe,并且这种状态下将发送一个SIGPIPE信号.

>| 强制重定向(即使设置了noclobber选项--就是-C选项).这将强制的覆盖一个现存文件.

|| 或-逻辑操作.

& 后台运行命令.一个命令后边跟一个&,将表示在后台运行.
bash$sleep 10 &
[1] 850
[1]+ Done sleep 10
在一个脚本中,命令和循环都可能运行在后台.

Example 3-3. 在后台运行一个循环
################################Start Script#######################################
1 #!/bin/bash
2 #background-loop.sh
3
4 for i in 1 2 3 4 5 6 7 8 9 10 #第一个循环
5 do
6 echo -n "$i"
7 done& #在后台运行这个循环
8 #在第2个循环之后,将在某些时候执行.
9
10 echo #这个'echo'某些时候将不会显示.
11
12 for i in 11 12 13 14 15 16 17 18 19 20 #第二个循环
13 do
14 echo -n "$i"
15 done
16
17 echo #这个'echo'某些时候将不会显示.
18
19 #--------------------------------------------------------
20
21 #期望的输出应该是
22 #1 2 3 4 5 6 7 8 9 10
23 #11 12 13 14 15 16 17 18 19 20
24
25 #然而实际的结果有可能是
26 #11 12 13 14 15 16 17 18 19 20
27 #1 2 3 4 5 6 7 8 9 10 bozo $
28 #(第2个'echo'没执行,为什么?)
29
30 #也可能是
31 #1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
32 #(第1个'echo'没执行,为什么?)
33
34 #非常少见的执行结果,也有可能是:
35 #11 12 13 1 2 3 4 5 6 7 8 9 10 14 15 16 17 18 19 20
36 #前台的循环先于后台的执行
37
38 exit 0
39
40 # Nasimuddin Ansari 建议加一句 sleep 1
41 #+ 在 6行和14行的 echo -n "$i"之后加
42 #+ 将看到一些乐趣
################################End Script#########################################
注意:在一个脚本内后台运行一个命令,有可能造成这个脚本的挂起,等待一个按键
响应.幸运的是,我们可以在Example 11-24附近,看到这个问题的解决办法.

&& 与-逻辑操作.

- 选项,前缀.在所有的命令内如果想使用选项参数的话,前边都要加上"-".

COMMAND -[Option1][Option2][...]
ls -al
sort -dfu $filename
set -- $variable

1 if [ $file1 -ot $file2 ]
2 then
3 echo "File $file1 is older than $file2."
4 fi
5
6 if [ "$a" -eq "$b" ]
7 then
8 echo "$a is equal to $b."
9 fi
10
11 if [ "$c" -eq 24 -a "$d" -eq 47 ]
12 then
13 echo "$c equals 24 and $d equals 47."
14 fi

- 用于重定向 stdin 或 stdout.

################################Start Script#######################################
1 (cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xpvf -)
2 # 从一个目录移动整个目录树到另一个目录
3 # [courtesy Alan Cox , with a minor change]
4
5 # 1) cd /source/directory 源目录
6 # 2) && 与操作,如果cd命令成功了,那么就执行下边的命令
7 # 3) tar cf - . 'c'创建一个新文档,'f'后边跟'-'指定目标文件作为stdout
8 # '-'后边的'f'(file)选项,指明作为stdout的目标文件.
9 # 并且在当前目录('.')执行.
10 # 4) | 管道...
11 # 5) ( ... ) 一个子shell
12 # 6) cd /dest/directory 改变当前目录到目标目录.
13 # 7) && 与操作,同上.
14 # 8) tar xpvf - 'x'解档,'p'保证所有权和文件属性,
15 # 'v'发完整消息到stdout
16 # 'f'后边跟'-',从stdin读取数据
17 #
18 # 注意:'x' 是一个命令, 'p', 'v', 'f' 是选项.
19 # Whew!
20
21
22
23 # 更优雅的写法应该是
24 # cd source/directory
25 # tar cf - . | (cd ../dest/directory; tar xpvf -)
26 #
27 # 当然也可以这么写:
28 # cp -a /source/directory/* /dest/directory
29 # 或者:
30 # cp -a /source/directory/* /source/directory/.[^.]* /dest/directory
31 # 如果在/source/directory中有隐藏文件的话.
################################End Script#########################################

################################Start Script#######################################
1 bunzip2 linux-2.6.13.tar.bz2 | tar xvf -
2 # --未解压的tar文件-- | --然后把它传递到"tar"中--
3 # 如果 "tar" 没能够正常的处理"bunzip2",
4 # 这就需要使用管道来执行2个单独的步骤来完成它.
5 # 这个练习的目的是解档"bzipped"的kernel源文件.
################################End Script#########################################
注意:在上边这个例子中'-'不太象是bash的操作符,而更像是tar的参数.
bash$echo "whatever" | cat -
whatever

在需要一个文件名的地方,-重定向输出到stdout(如在tar和cf命令中),或者从
stdin中接受输入,而不是从一个文件中接受输入.这是在管道中作为一个过滤
器,来使用文件定位工具的一种办法.
bash$file
用法: file [-bciknvzl] [-f namefile] [-m magicfiles] file...
上边这个例子file将会出错,提示你如何使用file命令.

添加一个"-"将得到一个更有用的结果.这将使得shell等待用户输入.
bash$file -
abc
standard input: ASCII text

bash$file -
#!/bin/bash
standard input: Bourn-Again shell script tesxt executable

现在命令从stdin中接受了输入,并分析它.

"-"常用于管道后边的命令,具体参看33.7节,来看使用技巧.
使用diff命令来和另一个文件的一部分进行比较.
grep Linux file1 | diff file2 -

最后,一个真实世界的使用tar命令的例子.

Example 3-4. 备份最后一天所有修改的文件.
################################Start Script#######################################
1 #!/bin/bash
2
3 # 在一个"tarball"中(经过tar和gzip处理过的文件)
4 #+ 备份最后24小时当前目录下d所有修改的文件.
5
6 BACKUPFILE=backup-$(date +%m-%d-%Y)
7 # 在备份文件中嵌入时间.
8 # Thanks, Joshua Tschida, for the idea.
9 archive=${1:-$BACKUPFILE}
10 # 如果在命令行中没有指定备份文件的文件名,
11 #+ 那么将默认使用"backup-MM-DD-YYYY.tar.gz".
12
13 tar cvf - `find . -mtime -1 -type f -print` > $archive.tar
14 gzip $archive.tar
15 echo "Directory $PWD backed up in archive file \"$archive.tar.gz\"."
16
17
18 # Stephane Chazelas指出上边代码,
19 #+ 如果在发现太多的文件的时候,或者是如果文件
20 #+ 名包括空格的时候,将执行失败.
21
22 # Stephane Chazelas建议使用下边的两种代码之一
23 # -------------------------------------------------------------------
24 # find . -mtime -1 -type f -print0 | xargs -0 tar rvf "$archive.tar"
25 # 使用gnu版本的find.
26
27
28 # find . -mtime -1 -type f -exec tar rvf "$archive.tar" '{}' \;
29 # 对于其他风格的UNIX便于移植,但是比较慢.
30 # -------------------------------------------------------------------
31
32
33 exit 0
################################End Script#########################################

注意:以"-"开头的文件名在使用"-"作为重定向操作符的时候,可能会产生问题.
应该写一个脚本来检查这个问题,并给这个文件加上合适的前缀.如:
./-FILENAME, $PWD/-FILENAME,或$PATHNAME/-FILENAME.

如果变量的值以"-"开头,可能也会引起问题.
1 var="-n"
2 echo $var
3 #具有"echo -n"的效果了,这样什么都不会输出的.

- 之前工作的目录."cd -"将回到之前的工作目录,具体请参考"$OLDPWD"环境变量.
注意:一定要和之前讨论的重定向功能分开,但是只能依赖上下文区分.

- 算术减号.

= 算术等号,有时也用来比较字符串.
1 a=28
2 echo $a # 28

+ 算术加号,也用在正则表达式中.
+ 选项,对于特定的命令来说使用"+"来打开特定的选项,用"-"来关闭特定的选项.

% 算术取模运算.也用在正则表达式中.

~ home目录.相当于$HOME变量.~bozo是bozo的home目录,并且ls ~bozo将列出其中的
内容. ~/就是当前用户的home目录,并且ls ~/将列出其中的内容,如:
bash$ echo ~bozo
/home/bozo

bash$ echo ~
/home/bozo

bash$ echo ~/
/home/bozo/

bash$ echo ~:
/home/bozo:

bash$ echo ~nonexistent-user
~nonexistent-user

~+ 当前工作目录,相当于$PWD变量.

~- 之前的工作目录,相当于$OLDPWD内部变量.

=~ 用于正则表达式,这个操作将在正则表达式匹配部分讲解,只有version3才支持.

^ 行首,正则表达式中表示行首."^"定位到行首.


控制字符
修改终端或文本显示的行为.控制字符以CONTROL + key组合.
控制字符在脚本中不能正常使用.
Ctl-B 光标后退,这应该依赖于bash输入的风格,默认是emacs风格的.
Ctl-C Break,终止前台工作.
Ctl-D 从当前shell登出(和exit很像)
"EOF"(文件结束符).这也能从stdin中终止输入.
在console或者在xterm window中输入的时候,Ctl-D将删除光标下字符.
当没有字符时,Ctrl-D将退出当前会话.在xterm window也有关闭窗口
的效果.
Ctl-G beep.在一些老的终端,将响铃.
Ctl-H backspace,删除光标前边的字符.如:
1 #!/bin/bash
2 # 在一个变量中插入Ctl-H
3
4 a="^H^H" # 两个 Ctl-H (backspaces).
5 echo "abcdef" # abcdef
6 echo -n "abcdef$a " # abcd f
7 # 注意结尾的空格 ^ ^ 两个 twice.
8 echo -n "abcdef$a" # abcdef
9 # 结尾没有空格 没有 backspace 的效果了(why?).
10 # 结果并不像期望的那样
11 echo; echo
Ctl-I 就是tab键.
Ctl-J 新行.
Ctl-K 垂直tab.(垂直tab?新颖,没听过)
作用就是删除光标到行尾的字符.
Ctl-L clear,清屏.
Ctl-M 回车
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics