第24章:编写第一个脚本

在前面几章中,我们已经组织起了一个命令行工具的兵器库。这些工具可以解决很多类型的计算问题,不过我们还是仅限于手动使用它们、一个个的在命令行中解决问题。如果能用 shell 做更多的工作,不是更伟大吗?答案是可以的。通过将这些工具结合进我们自己设计的程序中,shell 可以自动执行复杂序列的任务。我们可以写一个 shell 脚本(shell scripts)来实现它。

什么是 Shell 脚本?

最简单地说,一个 shell 脚本是包含一系列命令的文件。shell 读取这个文件并执行命令,如同它们是直接被输入在命令行上一样。

shell 是有点特别的,它既是一款强大的面对系统的命令行界面,同时还是一款脚本语言解释器。我们将会看到,大多数能在命令行上解决的问题,同样也能用脚本解决,反之亦然。

我们已经学习了许多 shell 特性,但是仅仅是聚焦于直接使用命令行。shell 还提供一组特性,通常是(但不是总是)供写程序时使用。

怎样编写 Shell 脚本

要成功地创建并运行一个 shell 脚本,我们需要做三件事。

  1. 写一个脚本。shell 脚本是普通的文本文件。所以我们需要一个文本编辑器。最好的文本编辑器都会提供句法高亮(syntax highlighting,),让我们看到脚本元素的彩色视图。句法高亮能帮助我们识别某些类型的常见错误。vimgeditkate 等很多编辑器都是写脚本的上佳选择。

  2. 使该脚本可执行。系统很挑剔,不会允许把任何文本文件当作一个程序看待,而且有很好的理由这么做!我们需要设置脚本文件的权限,使其可执行。

  3. 把脚本放在 shell 可以找到它的地方。对于可执行文件,shell 在没有明确指定路径名时,会自动搜索某些目录。为了方便,需要将我们的脚本放在这些目录里。

脚本文件的格式

为了保持编程传统,我们将创建一个 "Hello World" 的程序来演示一个最简单的脚本。来启动文本编辑器,键入下面的脚本:

#!/bin/bash

# This is our first script.

echo 'Hello World!'

最后一行脚本相当熟悉;它只是一条 echo 命令和一个字符串参数。第二行也很熟悉。它看起来像是注释,我们已经在许多配置文件中检查并编辑过。还有一点,在 shell 脚本中的注释可以出现在一行的末尾,假定注释前至少有一个空白字符,像这样:

echo 'Hello World!' # This is a comment too

行内从 # 符号之后的所有字符都会被忽略。

和许多事物一样,这个规则在命令行中也是奏效的:

[me@linuxbox ~]$ echo 'Hello World!' # This is a comment too
Hello World!

尽管注释在命令行中的作用微乎其微,但还是奏效的。

脚本的第一行有点神秘。看起来它应该是注释,因为它开头是个 #,但是看起来目的性很强。#! 字符串序列,实际上是一个特殊结构,叫做 shebang。它用来告诉内核应该用于执行后面脚本的解释器的名称。每个脚本都会将其作为第一行。

来将我们的脚本保存为 hello_world

可执行权限

下一步,我们必须使脚本可执行。用 chmod 能轻易做到。

[me@linuxbox ~]$ ls -l hello_world
-rw-r--r-- 1 me      me      63 2009-03-07 10:10 hello_world
[me@linuxbox ~]$ chmod 755 hello_world
[me@linuxbox ~]$ ls -l hello_world
-rwxr-xr-x 1 me      me      63 2009-03-07 10:10 hello_world

对于脚本文件有两组常见的许可设置:755 则所有人都可以执行,700 则仅供属主使用。注意脚本必须是可读可执行。

脚本文件的位置

权限设置好后,我们已经可以执行脚本了:

[me@linuxbox ~]$ ./hello_world
Hello World!

为了让脚本运行,我们必须在脚本名前加一个明确的路径。如果没有,会得到:

[me@linuxbox ~]$ hello_world
bash: hello_world: command not found

为何如此?是什么导致我们的脚本和其它程序不一样?事实证明,没有什么不一样。我们的脚本是好的。它的路径有问题。在第 11 章中,我们讨论过 PATH 环境变量及其对系统如何搜索可执行程序的影响。回顾一下,如果没有明确指定路径,系统每次会检索一系列目录去找到一个可执行程序。这就是为何我们在命令行上输入 ls 后,系统知道去执行 /bin/ls/bin 目录是系统自动检索的目录之一。目录列表掌握在一个名为 PATH 的系统变量中。PATH 变量包含了一个冒号分隔符的可被检索的目录列表。我们可以看一下 PATH 的内容。

[me@linuxbox ~]$ echo $PATH
/home/me/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games

这里我们看到了目录列表。如果我们的脚本放置在列表中的任意一个目录中,问题就解决了。注意列表的第一个目录 /home/me/bin。大多数 Linux 发行版会在 PATH 变量中包含一个用户家目录中的 bin 目录以允许用户执行自己的程序。所以,如果我们创建一个 bin 目录并将脚本放入其中,它就可以像其它程序一样工作了。

[me@linuxbox ~]$ mkdir bin
[me@linuxbox ~]$ mv hello_world bin
[me@linuxbox ~]$ hello_world
Hello World!

果然如此。

如果 PATH 变量不包含该目录,我们可以简单地将其加入 .bashrc 文件中的那一行中:

export PATH=~/bin:"$PATH"

变更完之后,它会影响每一个新的终端会话。要想将变更应用到当前终端会话,就必须让 shell 重新读取 .bashrc 文件。可以通过 "sourcing" 完成。

[me@linuxbox ~]$ . .bashrc

点命令(.)是 source 命令的同义词,是 shell 内建命令,它读取指定的 shell 命令文件并将其视为来自键盘的输入。

注意:Ubuntu(和大多数 Debian 系的发行版)中,如果当执行用户的 .bashrc 文件时, ~/bin 目录已经存在,则系统会自动将 ~/bin 目录添加到 PATH 变量中。所以,在 Ubuntu 系统中,如果我们创建了 ~/bin 目录然后登出再登录,所有的事情就都完成了。

对脚本文件而言的好位置

~/bin 目录是一个放置供个人用的脚本的好地方。如果我们写一个脚本,允许系统上所有的用户都能使用,则传统的位置是 /usr/local/bin。供系统管理员用的脚本,则常放在 /usr/local/sbin。在大多数情况下,本地提供的软件,无论脚本还是编译的程序,应该被放置在 /usr/local 层,而不应该在 /binusr/bin 中。这些目录是由 Linux 文件系统层次结构标准所指定,仅用于存放由 Linux 发行者提供并维护的文件的。

更多的格式化技巧

严肃的脚本编写有一个很重要的目标,就是易于维护(maintenance),即,一个脚本可以被其作者或其他人轻松修改以适应变更过的需求。使一个脚本易于被阅读和理解,是促进易于维护的一个方法。

长选项名

许多我们学过的命令同时支持短选项名和长选项名。例如,ls 命令有很多选项,既可以用短形式,也可以用长形式。例如:

[me@linuxbox ~]$ ls -ad

等价于:

[me@linuxbox ~]$ ls --all --directory

出于减少键入的原因,短选项在命令行中更受欢迎,但是当写脚本时,长选项更便于阅读。

缩进和续行

当使用长命令时,可以将命令分割在几行内来改善可读性。第 17 章中,我们看到特别长的一个 find 命令。

[me@linuxbox ~]$ find playground \( -type f -not -perm 0600 -exec chmod 0600 ‘{}’ ‘;’ \) -or \( -type d -not -perm 0700 -exec chmod 0700 ‘{}’ ‘;’ \)

显然,这条命令初看之下很难识别。在脚本中,如果写成这样,可以更容易理解:

find playground \
    \( \
        -type f \
        -not -perm 0600 \
        -exec chmod 0600 ‘{}’ ‘;’ \
    \) \
    -or \
    \( \
        -type d \
        -not -perm 0700 \
        -exec chmod 0700 ‘{}’ ‘;’ \
    \)

使用续行(反斜杠-换行序列)和缩进,这条复杂命令的逻辑对于读者来说就能更清晰地得到描述。该技术在命令行中也有效,尽管因为很难写、很难修改而很少用。脚本和命令行之间的一个区别是,脚本可以使用制表符来完成缩进,而命令行则不能,因为在命令行中,制表符用来补全命令了。

为编写脚本配置 vim

vim 文本编辑器有许许多多的配置设置。有一些常见的选项可以为编写脚本提供便利。

下面的命令会打开句法高亮:

:syntax on

通过这个设置,当查看一个脚本时,shell 句法的不同元素会用不同的颜色显示。这有助于识别某些类型的程序错误。看起来也很酷。注意,要使这些特性起作用,必须安装完整版的 vim,并且保证正在编辑的文件必须有 shebang 以指示文件是一份 shell 脚本。如果在使用上面这条命令时有困难,试着用 :set syntax=sh 替代。

下面这条命令打开了高亮搜索结果的选项。

:set hlsearch

如果要检索一个单词 echo。因为打开了这个选项,这个单词的每个实例都会被高亮显示。

下面这个设置设置一个制表符占几个字符栏宽:

:set tabstop=4

默认值是八栏。设置值是 4(这是实际操作中常见的),使得较长的行更易于适应屏幕。

下面的设置打开了「自动缩进」功能:

:set autoindent

这使得 vim 将新起的一行的缩进量与刚刚键入过的一行持平。这能加快在许多类型的编程结构中的编辑速度。要取消缩进,请按 Ctrl-d

这些变更会在 ~/.vimrc 文件中保存这些命令(没有前置冒号),使得这些变更永久有效。

总结

在编写脚本的第一章中,我们学习了如何写脚本,如何更方便在系统中执行脚本。还学习了使用不同的格式以增进脚本的可读性(也因此更易维护)。接下来的章节中,会反复遇到易于维护这个在良好的脚本编写中的中心原则。

扩展阅读

Last updated

Was this helpful?