bash(以及通常意义上的shell脚本编程)出现的日子可是不短了,每天都有新手通过bash见识到shell脚本编程和系统自动化的威力。随着微软公司在Windows 10中发布了交互式的bash shell以及Unix子系统,现在已是更适合了解shell脚本所能实现的简洁和高效的时候了。
从计算机出现的早期开始,shell脚本就一直在帮助系统管理员和程序员完成费时费力的枯燥工作。那么,什么是shell脚本?为什么你要关心它?
什么是shell脚本shell脚本就是包含一组可运行的特定shell命令(在本文中是bash shell)的文本文件,命令的执行顺序与其出现在脚本中的顺序一致。shell以命令行的形式提供了可用的操作系统命令库的接口。
shell脚本其实就是为使用shell环境中的命令所编写的小型程序,可用于自动化那些通常没人愿意手动完成的任务,例如Web爬取、磁盘用量跟踪、天气数据下载、文件更名,等等。你甚至能够用shell脚本制作一些初级的游戏!脚本中可以加入简单的逻辑,例如在其他语言中出现的if语句。
OS X、BSD以及Linux操作系统中可用的命令行shell有很多种,包括tcsh、zsh和广受欢迎的bash。本文关注的是Unix环境中的主流:bash。
每种shell都有自己的特性和功能,但是多数人在Unix中最先熟悉的就是bash。在OS X中,Terminal(终端)应用会打开一个bash shell窗口(如图0-1)。在Linux中,有各种各样的shell控制台程序,其中最常见的是gnome-terminal(GNOME)或konsole(KDE)。这些应用可以修改自身的配置来使用其他类型的shell,不过默认选用的都是bash。实际上,不管你用的是哪种类Unix操作系统,打开Terminal应用,得到的都是bash。
图0-1 OS X中的Terminal应用,其中显示了bash的版本号
注意 2016年8月,微软发布了针对Windows 10周年版(Windows 10 Anniversary)的bash,所以如果你用的是Windows,照样可以使用bash shell。bash的美妙之处就在于它的可移植性,所以多数脚本基本上应该没问题。
使用终端与系统交互可能看起来是件艰巨的任务。但随着时间的推移,打开终端,快速对系统做出更改,会变得比在一个又一个的菜单中移动鼠标,找到要更改的选项更加自然。
执行命令bash的核心功能是执行系统命令。来看一个简单的“Hello World”的例子。在bash shell中,echo命令可以在屏幕上显示文本,例如:
$ echo "Hello World"
在命令行中输入上面的内容,你就会看到Hello World出现在屏幕上。这行代码执行了bash标准命令echo。bash用来搜索标准命令的目录被保存在环境变量PATH中。你可以使用echo查看PATH变量的内容,如代码清单0-1所示。
代码清单0-1 输出当前环境变量PATH
$ echo $PATH /Users/bperry/.rvm/gems/Ruby-2.1.5/bin:/Users/bperry/.rvm/gems/ruby-2.1.5@ global/bin:/Users/bperry/.rvm/rubies/ruby-2.1.5/bin:/usr/local/bin:/usr/bin:/ bin:/usr/sbin:/sbin:/opt/X11/bin:/usr/local/MacGPG2/bin:/Users/bperry/.rvm/bin
注意 如果代码清单中既显示了输入命令,也显示了输出内容,输入命令会以粗体显示并以$作为起始,以此同输出内容区分开来。
输出中的各个目录之间以冒号分隔。当需要运行程序或命令时,bash会检查所有这些目录。如果命令没有在其中,bash就无法执行。另外要注意的是,bash是以这些目录在PATH中出现的顺序依次检查的。顺序在这里很重要,如果有两个同名的命令分别位于PATH中两个不同的目录中,可能会由于目录出现的先后顺序产生不同的结果。如果在查找特定命令时碰到了麻烦,可以用which命令查看待查找命令在PATH中的位置,如代码清单0-2所示。
代码清单0-2 使用which在PATH中查找命令
$ which ruby /Users/bperry/.rvm/rubies/ruby-2.1.5/bin/ruby $ which echo /bin/echo
知道了这些信息,你可以把需要测试的文件移动或复制到echo $PATH所列出的某个目录中(如代码清单0-1所示),然后就能执行命令了。本文中使用了which来确定命令的完整路径。在调试有问题的PATH环境变量时,which是一个有用的工具。
配置登录脚本我们会编写一些随后用于其他脚本中的脚本,因此能够轻松调用新脚本就显得很重要了。你可以配置PATH环境变量,使得在启动新的命令shell时,可以像其他命令那样自动调用定制脚本。新命令shell启动后的第一件事是读取用户主目录(在OS X和Linux中分别是/Users/<username>和/home/<username>)中的登录脚本并执行其中的定制命令。根据你所用的系统,登录脚本可以是.login、.profile、.bashrc或.bash_profile。要想知道具体是哪个,像下面这样在这些文件中各加入一行:
echo this is .profile
将最后一个单词调整为对应的文件名,然后登录。这行内容应该会出现在终端窗口顶端,指明登录时运行的是哪个脚本。如果你打开终端,看到的是this is .profile,那么说明你的shell环境载入的是.profile文件;如果看到的是this is .bashrc,则说明是.bashrc文件,以此类推。依赖于你所用的shell,这种行为也会有变化。
你可以修改登录脚本,让它帮助在PATH变量中加入其他目录。另外也可以在登录脚本中完成其他bash设置,例如修改bash提示符外观、定制PATH等。让我们用cat命令来看一个定制好的.bashrc登录脚本。cat命令接受文件名作为参数,然后将文件内容输出到控制台屏幕,如代码清单0-3所示。
代码清单0-3 定制过的.bashrc文件,其中将RVM加入了PATH
$ cat ~/.bashrc export PATH="$PATH:$HOME/.rvm/bin" # 将RVM添加到PATH中。
该代码显示出了.bashrc文件的内容,可以看到PATH获得了一个新的值,允许本地RVM (Ruby version manager,Ruby版本管理器)管理已安装的各种Ruby版本。因为.bashrc在每次启动新的命令shell时都会设置PATH,所以RVM在系统中总是默认可用。
你可以用类似的方法让自己的shell脚本也默认可用。首先,在主目录中创建一个开发目录,将编写的所有脚本都保存到这个目录。然后在登录脚本中将该目录添加到PATH变量中,这样就可以更方便地引用新写的脚本了。
命令echo $HOME可以在终端中打印出主目录的路径。进入主目录,然后创建开发目录(建议将其命名为scripts)。接着用文本编辑器打开登录脚本,在脚本顶端添加以下代码(把/path/to/scripts/替换成开发目录的具体路径),将开发目录加入PATH。
export PATH="/path/to/scripts/:$PATH"
在此之后,你保存到开发目录中的任何脚本都可以像其他shell命令那样调用了。
运行shell脚本我们到目前为止已经用到了几个命令,例如echo、which和cat,但是只是单独使用,并没有把它们放到shell脚本中。让我们来写一个可以连续执行这些命令的shell脚本,如代码清单0-4所示。这个脚本先打印出Hello World,然后输出shell脚本neqn的路径,neqn应该位于PATH默认的目录中。接着用该路径将neqn的内容打印到屏幕上。(neqn的内容是什么目前并不重要,这里只是作为一个例子而已。)这是利用shell脚本按顺序执行命令序列的一个很好的例子,在这里我们查看了文件的完整系统路径并快速检查了文件内容。
代码清单0-4 我们的第一个shell脚本
echo "Hello World" echo $(which neqn) cat $(which neqn)
打开你自己惯用的文本编辑器(Linux上的Vim或gedit、OS X上的TextEdit都是很流行的编辑器),输入代码清单0-4中的代码。然后将shell脚本保存到开发目录中并命名为intro。shell脚本对文件扩展名没有特别要求,用不用都行(如果喜欢的话,可以用.sh作为扩展名,但不是必须的)。第一行代码使用echo命令打印出文本Hello World。第二行代码稍微复杂点,使用which命令找出neqn的位置,然后将其用echo命令在屏幕上打印出来。为了像这样将一个命令作为另外一个命令的参数来运行两个命令,bash使用子shell(subshell)来执行第二个命令并将其输出作为第一个命令的参数。在上面的例子中,子shell执行的是which命令,该命令返回neqn脚本的完整路径。这个路径再被用作echo的参数,结果就是在屏幕上显示出neqn的路径。最后,还是用子shell将neqn的路径传给cat命令,在屏幕上打印出neqn脚本的内容。
保存好文件之后,就可以在终端运行脚本了。代码清单0-5显示了执行结果。
代码清单0-5 运行我们的第一个shell脚本
$ sh intro ❶ Hello World ❷ /usr/bin/neqn ❸ #!/bin/sh # Provision of this shell script should not be taken to imply that use of # GNU eqn with groff -Tascii|-Tlatin1|-Tutf8|-Tcp1047 is supported. GROFF_RUNTIME="${GROFF_BIN_PATH=/usr/bin}:" PATH="$GROFF_RUNTIME$PATH" export PATH exec eqn -Tascii ${1 "$@"} # eof $
运行这个脚本的时候,使用sh命令并将intro脚本作为参数。sh命令会依次执行脚本中的每行代码,就好像这些命令是在终端上敲入的一样。你可以看到先是打印出了Hello World❶,然后是neqn的路径❷,最后输出neqn的文件内容❸,也就是shell脚本neqn的源代码(至少在OS X上是这样的,Linux版本也许略有不同)。
让shell脚本用起来更自然不使用sh命令也可以执行脚本。如果在shell脚本intro中多加一行,然后修改脚本的权限,就可以像其他bash命令那样直接调用脚本了。在文本编辑器中更新intro脚本:
❶ #!/bin/bash echo "Hello World" echo $(which neqn) cat $(which neqn)
我们在脚本顶端加上了一行/bin/bash❶。这行叫作shebang。shebang允许你指定用哪个程序来解释脚本。这里选择将文件作为bash脚本。你可能还碰到过其他shebang,例如针对Perl(#!/usr/bin/perl)或Ruby(#!/usr/bin/env ruby)的。
shebang:这个词其实是两个字符名称sharp-bang 的简写。在Unix 的行话里,用sharp 或hash(有时候是mesh)来称呼字符“#”,用bang 来称呼惊叹号“!”,因而shebang 合起来就代表了这两个字符。详情请参考:http://en.wikipedia.org/wiki/ Shebang_(Unix)。
有了这行,还得设置文件权限才能像其他程序那样直接运行shell脚本。在终端中的操作方法如代码清单0-6所示。
代码清单0-6 将脚本intro的权限修改为可执行
❶ $ chmod x intro ❷ $ ./intro Hello World /usr/bin/neqn #!/bin/sh # Provision of this shell script should not be taken to imply that use of # GNU eqn with groff -Tascii|-Tlatin1|-Tutf8|-Tcp1047 is supported. GROFF_RUNTIME="${GROFF_BIN_PATH=/usr/bin}:" PATH="$GROFF_RUNTIME$PATH" export PATH exec eqn -Tascii ${1 "$@"} # eof $
我们用到了权限修改命令chmod❶并将 x作为命令参数,该参数可以将随后指定的文件设置为可执行权限。权限设置好之后,不用调用bash就可以直接运行shell脚本❷。这是一种很好的shell脚本编程实践,在你以后精进技艺的过程中就会发现它的作用了。
这只是一个简单的例子,告诉你如何运行shell脚本,如何使用shell脚本运行其他的shell脚本。在今后编写shell脚本的时候,你也会看到更多的shebang。
为什么要用shell脚本你也许疑惑为什么偏要选择bash shell脚本,而不去用那些漂亮的新语言,比如Ruby或Go。尽管这些语言都试图在多种系统上实现可移植性,但它们通常并没有被默认安装。原因很简单:所有Unix机器上都已经有了一个基本的shell,而且绝大多数用的都是bash shell。
文章开头也提到过,微软最近在Windows 10中也加入了多数Linux发行版和OS X中采用的bash shell。这意味着你的shell脚本几乎不需要做什么额外的工作,就拥有了比以往更好的可移植性。相较于其他语言,shell脚本能够更准确、更轻松地完成系统维护及其他任务。
代码清单0-7中展示了一个方便的微型shell脚本(没错,只有一行),完全可移植。该脚本可以统计出OpenOffice文档目录中的文档共有多少页,这对于作者特别有用。
代码清单0-7 统计OpenOffice文档目录中文档页面数量的bash脚本
#!/bin/bash echo "$(exiftool *.odt | grep Page-count | cut -d ":" -f2 | tr '\n' ' ')""0" | bc
我们不会深究这个脚本的工作细节,毕竟才刚上路嘛!不过概括地讲,脚本从各个文档中提取出页数信息,使用加号将页数拼接在一起,然后通过管道将算式传给命令行计算器,计算出最终的页面总数。所有这一切全在这一行代码中完成。还有更多像这样的酷炫脚本,做过一些练习之后,这个脚本的含义就一目了然了!
开始动手吧如果你之前没有接触过shell脚本编程,那么现在对此应该有了基本的了解。创建小型脚本完成特定的任务是Unix哲学的核心。搞清楚如何编写脚本并根据个人需要扩展Unix系统,这样才能成长为一名高级用户。本文只是道开胃菜,真正酷炫的shell脚本在这本书里面↓
shell脚本实战(第2版)
- 101个shell经典实例,拿来即用
- 一册搞定脚本编程技术
bash仍旧是类Unix服务器或工作站用户的主要工具,这些用户包括Web开发人员(很多人都是在OS X上做开发,然后部署到Linux服务器)、数据分析师、移动应用程序开发人员以及软件工程师,等等。除此之外,大量的业余爱好者也在自己的开源微型计算机(例如树莓派)上运行着Linux,实现智能家庭的自动化。shell脚本是这类用途的不二之选。
无论是对于在bash技艺上追求精益求精的老手,还是那些偶尔用一下终端或shell脚本的用户,书中所展现的脚本都大有裨益。后一阵营中的用户可能希望温习一些技巧或是学点bash的高级概念,给自己再充充电。
本书并非教程!其目标是通过(基本上)简短紧凑的脚本教会你bash脚本编程的实用技术以及常见工具的用法,但不会去逐行解释脚本。本书只讲解每个脚本的核心内容,有经验的shell脚本用户通过阅读代码就能明白其余的部分。你大可放开手脚,把脚本拆解开来,根据自己的需要修改,借此达到融会贯通的目的。书中的脚本旨在解决那些三天两头就会碰上的麻烦事,比如Web管理或是文件同步,不管用的是什么工具,每个技术专家都得应付这类问题。
本书重点关注编写可移植的自动化脚本(例如构建软件或提供业务流程)时经常面对的那些难题,为此我们的做法是先实现一些常见任务的自动化。但如果想从本书中获得最大收益,就要将已形成的解决方案推广应用到其他类似的问题上。
尽管很多系统管理员都能受益于这个脚本,但其中真正的价值在于,创建包装器脚本这种一般性的解决方案能够确保跨平台行为的一致性。书中随后的章节将深入研究bash脚本编程中一些酷炫的特性以及Unix系统常见的实用工具,为你传授各种绝招。
目录
第 1 章 遗失的代码库
第 2 章 改进用户命令
第 3 章 创建实用工具
第 4 章 Unix调校
第 5 章 系统管理:用户管理
第 6 章 系统管理:系统维护
第 7 章 Web与Internet用户
第 8 章 网站管理员绝招
第 9 章 Web与Internet管理
第 10 章 Internet服务器管理
第 11 章 OS X脚本
第 12 章 shell脚本趣用与游戏
第 13 章 与云共舞
第 14 章 ImageMagick及图像处理
第 15 章 天数与日期
附录 A 在Windows 10中安装bash
附录 B 免费福利
,