第 0 章 shell脚本速成

第 0 章 shell脚本速成

bash(以及通常意义上的shell脚本编程)出现的日子可是不短了,每天都有新手通过bash见识到shell脚本编程和系统自动化的威力。随着微软公司在Windows 10中发布了交互式的bash shell以及Unix子系统,现在已是更适合了解shell脚本所能实现的简洁和高效的时候了。

0.1 什么是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。附录A给出了在Windows 10中安装bash的操作方法,不过本书假设你运行的是像OS X或Linux这样的类Unix操作系统。你完全可以在Windows 10下实验书中的脚本,但结果怎样就不保证了,因为我们自己没有在Windows中测试过。不过bash的美妙之处就在于它的可移植性,所以多数脚本基本上应该没问题。

使用终端与系统交互可能看起来是件艰巨的任务。但随着时间的推移,打开终端,快速对系统做出更改,会变得比在一个又一个的菜单中移动鼠标,找到要更改的选项更加自然。

0.2 执行命令

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 使用whichPATH中查找命令

$ which ruby
/Users/bperry/.rvm/rubies/ruby-2.1.5/bin/ruby
$ which echo
/bin/echo

知道了这些信息,你可以把需要测试的文件移动或复制到echo $PATH所列出的某个目录中(如代码清单0-1所示),然后就能执行命令了。本书中使用了which来确定命令的完整路径。在调试有问题的PATH环境变量时,which是一个有用的工具。

0.3 配置登录脚本

我们在书中会编写一些随后用于其他脚本中的脚本,因此能够轻松调用新脚本就显得很重要了。你可以配置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命令那样调用了。

0.4 运行shell脚本

我们到目前为止已经用到了几个命令,例如echowhichcat,但是只是单独使用,并没有把它们放到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版本也许略有不同)。

0.5 让shell脚本用起来更自然

不使用sh命令也可以执行脚本。如果在shell脚本intro中多加一行,然后修改脚本的权限,就可以像其他bash命令那样直接调用脚本了。在文本编辑器中更新intro脚本:

❶ #!/bin/bash
  echo "Hello World"
  echo $(which neqn)
  cat $(which neqn)

我们在脚本顶端加上了一行/bin/bash❶。这行叫作shebang1。shebang允许你指定用哪个程序来解释脚本。这里选择将文件作为bash脚本。你可能还碰到过其他shebang,例如针对Perl(#!/usr/bin/perl)或Ruby(#!/usr/bin/env ruby)的。

1shebang这个词其实是两个字符名称sharp-bang的简写。在Unix的行话里,用sharp或hash(有时候是mesh)来称呼字符“#”,用bang来称呼惊叹号“!”,因而shebang合起来就代表了这两个字符。详情请参考:http://en.wikipedia.org/wiki/Shebang_(Unix)。(本书除脚本#40之脚注为原书注,其余均为译者注。)

有了这行,还得设置文件权限才能像其他程序那样直接运行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脚本编程实践,在你以后精进技艺的过程中就会发现它的作用了。本书中大部分脚本都要像intro脚本这样设置执行权限。

这只是一个简单的例子,告诉你如何运行shell脚本,如何使用shell脚本运行其他的shell脚本。书中很多地方都用到了这种方法,在今后编写shell脚本的时候,你也会看到更多的shebang。

0.6 为什么要用shell脚本

你也许疑惑为什么偏要选择bash shell脚本,而不去用那些漂亮的新语言,比如Ruby或Go。尽管这些语言都试图在多种系统上实现可移植性,但它们通常并没有被默认安装。原因很简单:所有Unix机器上都已经有了一个基本的shell,而且绝大多数用的都是bash shell。本章开头也提到过,微软最近在Windows 10中也加入了多数Linux发行版和OS X中采用的bash shell。这意味着你的shell脚本几乎不需要做什么额外的工作,就拥有了比以往更好的可移植性。相较于其他语言,shell脚本能够更准确、更轻松地完成系统维护及其他任务。bash并非十全十美,但你可以在本书中学会如何弥补其中一些不足之处。

代码清单0-7中展示了一个方便的微型shell脚本(没错,只有一行),完全可移植。该脚本可以统计出OpenOffice文档目录中的文档共有多少页,这对于作者特别有用。

代码清单0-7 统计OpenOffice文档目录中文档页面数量的bash脚本

#!/bin/bash
echo "$(exiftool *.odt | grep Page-count | cut -d ":" -f2 | tr '\n' '+')""0" | bc

我们不会深究这个脚本的工作细节,毕竟才刚上路嘛!不过概括地讲,脚本从各个文档中提取出页数信息,使用加号将页数拼接在一起,然后通过管道将算式传给命令行计算器,计算出最终的页面总数。所有这一切全在这一行代码中完成。全书中还有更多像这样的酷炫脚本,做过一些练习之后,这个脚本的含义就一目了然了!

0.7 开始动手吧

如果你之前没有接触过shell脚本编程,那么现在对此应该有了基本的了解。创建小型脚本完成特定的任务是Unix哲学的核心。搞清楚如何编写脚本并根据个人需要扩展Unix系统,这样才能成长为一名高级用户。这一章只是道开胃菜,真正酷炫的shell脚本还在后面!

目录

  • 版权声明
  • 前言
  • 致谢
  • 第 0 章 shell脚本速成
  • 第 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 免费福利