没有哪种编程语言是完美的。甚至没有一种“最佳”语言;只有适合某些特定用途的语言,或者说不太适合某些用途的语言。
——Herbert Mayer
对任何希望精通系统管理的人来说,掌握 shell 脚本的基本知识都是必不可少的,即使他们并不打算亲自编写脚本。要知道,当 Linux 机器启动时,它会执行 /etc/rc.d 目录下的 shell 脚本来恢复系统配置并设置服务。深入理解这些启动脚本对于分析系统行为,甚至修改系统都非常重要。
脚本编写的技巧并不难掌握,因为脚本可以分块逐步构建,而且只需要学习一小部分 shell 特有的操作符和选项。其语法简单——甚至有些简陋——类似于在命令行调用和串联各种工具,只有少数“规则”需要遵守。大多数短脚本第一次运行就能成功,即使是较长的脚本,调试起来也很直接。
在个人计算机的早期,BASIC 语言让任何有一定计算机基础的人都能在早期微型计算机上编写程序。几十年后,Bash 脚本语言让任何对 Linux 或 UNIX 有基本了解的人都能在现代计算机上做同样的事情。
现在我们有了功能强大的单板计算机,比如树莓派。Bash 脚本为探索这些有趣设备的能力提供了一种方式。
Shell 脚本是一种快速原型开发复杂应用的方法。即使只让部分功能在脚本中跑起来,通常也是项目开发的有用第一步。这样可以测试和调整应用结构,在最终用 C、C++、Java、Perl 或 Python 编码前,先发现主要的陷阱。
Shell 脚本呼应了经典 UNIX 哲学:将复杂项目拆分为更简单的子任务,串联各个组件和工具。许多人认为,这种方法比用新一代功能强大的“全能”语言(如 Perl)更好,或者至少更优雅。后者试图包打天下,但却迫使你改变思维方式来适应工具。
Herbert Mayer 认为,“有用的语言需要数组、指针和构建数据结构的通用机制。”按这个标准,shell 脚本似乎不算“有用”。或者,也许并非如此……
什么时候不该用 shell 脚本
- 资源密集型任务,尤其对速度有要求的(如排序、哈希、递归等)
- 涉及大量数学运算,特别是浮点运算、高精度计算或复数(建议用 C++ 或 FORTRAN)
- 需要跨平台移植性(建议用 C 或 Java)
- 复杂应用,需要结构化编程(如变量类型检查、函数原型等)
- 关键任务型应用,关系到公司未来
- 对安全性有高要求,需要保证系统完整性、防止入侵、破解和破坏
- 项目包含多个相互依赖的子组件
- 需要大量文件操作(Bash 仅支持串行文件访问,且效率低下)
- 需要原生支持多维数组
- 需要数据结构,如链表或树
- 需要生成/操作图形界面或 GUI
- 需要直接访问系统硬件或外部设备
- 需要端口或 socket I/O
- 需要使用库或与遗留代码接口
- 专有、闭源应用(Shell 脚本源码完全公开)
如果上述任何一条适用,请考虑使用更强大的脚本语言——比如 Perl、Tcl、Python、Ruby——或者 C、C++、Java 等编译型语言。即便如此,先用 shell 脚本做原型开发仍然是有用的开发步骤。
我们将使用 Bash,它是“Bourne-Again shell”的缩写,也是对 Stephen Bourne 经典 Bourne shell 的双关。Bash 已成为大多数 UNIX 系统 shell 脚本的事实标准。本书介绍的大多数原理同样适用于其他 shell,如 Korn Shell(Bash 吸收了它的一些特性)以及 C Shell 及其变种。(注意:不推荐用 C Shell 编程,因其存在一些固有问题,详见 Tom Christiansen 1993 年 10 月的 Usenet 贴文。)
接下来是 shell 脚本教程,主要通过示例来说明 shell 的各种特性。示例脚本都经过测试,有些在实际生活中也很有用。读者可以在源码包中找到这些脚本(scriptname.sh 或 scriptname.bash),赋予执行权限(chmod u+rx scriptname),然后运行看看效果。如果没有源码包,也可以从 HTML 或 PDF 版本中复制粘贴。需要注意,有些脚本会提前用到后面才讲解的特性,读者可能需要跳到后面查阅。
注释
- [1] 这些被称为 内建命令(builtins),即 shell 内部特性。
- [2] 虽然 shell 脚本可以实现递归,但速度慢,且实现通常很丑陋。
- [4] Bash 融合了 ksh88 的许多特性,甚至还有一些来自 ksh93。
- [5] 按惯例,用户自写的 Bourne shell 脚本通常以 .sh 结尾。系统脚本(如 /etc/rc.d 下的)不一定遵循此命名规则。