第二十五章:启动一个项目

从本章开始,我们将开始构建一个程序。这个项目的目的是了解如何使用各种shell功能来创建程序,更重要的是,创建好的程序。

我们将编写的程序是一个报告生成器(report generator)。它将展示有关我们的系统及其状态的各种统计数据,并将以HTML格式生成此报告,以便我们可以使用Firefox或Chrome等网络浏览器查看。

程序通常分为一系列阶段构建,每个阶段都添加特性和功能。我们程序的第一阶段将生成一个不包含系统信息的最小HTML文档。稍后会讲到。

第二十五章:启动一个项目第一阶段:最小文档第二阶段:添加少量数据变量和常量为变量和常量赋值here文档总结

第一阶段:最小文档

我们需要知道的第一件事是格式良好的HTML文档的格式。它看起来像这样:

如果我们在文本编辑器中输入此内容并将文件另存为 foo.html ,我们可以在Firefox中使用以下URL查看文件:

我们程序的第一阶段将能够将此HTML文件输出为标准输出。我们可以很容易地编写一个程序来实现这一点。让我们启动文本编辑器并创建一个名为 ~/bin/sys_info_page 的新文件。

输入以下程序:

我们对这个问题的第一次尝试包含一个shebang、一个注释(总是一个好主意)和一系列 echo 命令,每行输出一个。保存文件后,我们将使其可执行并尝试运行它。

当程序运行时,我们应该看到屏幕上显示的HTML文档的文本,因为脚本中的 echo 命令将其输出发送到标准输出。我们将再次运行程序,并将程序的输出重定向到文件 sys_info_page.html ,以便我们可以使用web浏览器查看结果。

到目前为止,一切顺利。

在编写程序时,力求简单明了总是一个好主意。当程序易于阅读和理解时,维护就更容易了,更不用说它可以通过减少打字量使程序更容易编写。我们当前版本的程序运行良好,但可能更简单。我们实际上可以将所有 echo 命令组合成一个,这肯定会使向程序输出添加更多行变得更加容易。那么,让我们将程序更改为:

引号字符串可能包含换行符,因此包含多行文本。shell将继续读取文本,直到遇到结束引号。它在命令行上也是这样工作的:

前导 > 字符是PS2 shell变量中包含的shell提示符。每当我们在shell中键入多行语句时,它就会出现。这个特性现在有点模糊,但稍后,当我们介绍多行编程语句时,它将变得非常方便。

第二阶段:添加少量数据

现在我们的程序可以生成一个最小的文档,让我们在报告中添加一些数据。为此,我们将进行以下更改:

我们在报告正文中添加了页面抬头和标题。

变量和常量

variables —— 变量

constants —— 常量

然而,我们的脚本存在一个问题。请注意字符串“System Information Report”是如何重复的?使用我们的小脚本,这不是问题,但让我们想象一下,我们的脚本真的很长,而且我们有多个这个字符串的实例。如果我们想将标题更改为其他内容,我们必须在多个地方更改它,这可能需要大量的工作。如果我们可以安排脚本,使字符串只出现一次,而不是多次,那会怎么样?这将使脚本的未来维护变得更加容易。我们可以这样做:

通过创建一个名为 title 的变量(variable)并将其赋值为 System Information Report ,我们可以利用参数扩展并将字符串放置在多个位置。

那么,我们如何创建一个变量呢?很简单,我们只是使用它。当shell遇到变量时,它会自动创建它。这与许多编程语言不同,在许多编程语言中,变量在使用前必须显式声明或定义。shell对此非常松懈,这可能会导致一些问题。例如,考虑在命令行上运行的此场景:

我们首先给变量 foo 赋值 yes ,然后用 echo 显示它的值。接下来,我们显示拼错为 fool 的变量名的值,并得到一个空白结果。这是因为shell在遇到 fool 时愉快地创建了它,并将其默认值设置为 nothing 或空。由此,我们了解到我们必须密切注意拼写!了解这个例子中到底发生了什么也很重要。从我们之前对shell如何执行扩展的了解中,我们知道以下命令:

经过参数展开,结果如下:

相比之下,以下命令:

扩展到以下内容:

空变量扩展为零!这可能会对需要参数的命令造成严重破坏。这里有一个例子:

我们为两个变量foo和foo1赋值。然后我们执行cp,但拼错了第二个参数的名称。展开后,cp命令只发送一个参数,尽管它需要两个。

关于变量名有一些规则:

  1. 变量名可以由字母数字字符(字母和数字)和下划线字符组成。
  2. 变量名的第一个字符必须是字母或下划线。
  3. 不允许使用空格和标点符号。

“变量”一词意味着一个变化的值,在许多应用程序中,变量都是这样使用的。然而,我们应用程序中的变量 title 被用作常量(constant)。常量就像变量一样,因为它有一个名称并包含一个值。不同之处在于常数的值不会改变。在执行几何计算的应用程序中,我们可以将 PI 定义为常数并将其赋值为 3.1415 ,而不是在整个程序中直接使用该数字。shell不区分变量和常量;它们主要是为了程序员的方便。一个常见的约定是使用大写字母表示常量,使用小写字母表示真变量。我们将修改我们的脚本以符合此约定:

我们还借此机会通过添加shell变量 HOSTNAME 的值来丰富我们的标题。这是计算机的网络名称。

注意:shell确实提供了一种通过使用带有 -r (只读)选项的 declare 内置命令来强制常量不可变的方法。如果我们这样分配标题: declare -r TITLE= "Page Title" shell将阻止对TITLE的任何后续赋值。此功能很少使用,但它存在于非常正式的脚本中。

为变量和常量赋值

这就是我们对扩展的了解真正开始得到回报的地方。正如我们所看到的,变量是这样赋值的:

variable=value

其中 variable 是变量的名称, value 是字符串。与其他一些编程语言不同,shell不关心分配给变量的数据类型;它把它们都当作字符串。您可以通过使用带有 -i 选项的 declare 命令来强制shell将赋值限制为整数,但是,与将变量设置为只读一样,这种情况很少发生。

请注意,在赋值中,变量名、等号和值之间不得有空格。那么,值可以由什么组成呢?它可以有任何我们可以扩展成字符串的东西。

可以在一行上完成多个变量赋值。

在展开过程中,变量名可能会被可选的花括号{}包围。这在变量名因周围上下文而变得模糊的情况下非常有用。在这里,我们尝试使用变量将文件名从 myfile 更改为 myfile1

此尝试失败,因为shell将 mv 命令的第二个参数解释为新的(空的)变量。这个问题可以通过以下方式解决:

通过添加周围的大括号,shell不再将后面的 1 解释为变量名的一部分。

注意:最好将变量和命令替换括在双引号中,以限制shell分词的影响。当变量可能包含文件名时,引用尤为重要。

我们将借此机会在报告中添加一些数据,即报告创建的日期和时间以及创建者的用户名。

here文档

我们研究了两种不同的文本输出方法,都使用 echo 命令。还有第三种方法称为 here documenthere script 。这里的文档是I/O重定向的另一种形式,在这种形式中,我们将一段文本嵌入到脚本中,并将其输入到命令的标准输入中。其工作原理如下:

command << token text token

其中 command 是接受标准输入的命令的名称, token 是用于指示嵌入文本结束的字符串。在这里,我们将修改我们的脚本以使用这里的文档:

我们的脚本现在使用 cat 和here文档,而不是使用 echo 。字符串 _EOF_(表示文件结束,一种常见的约定)被选为标记,并标记嵌入文本的结束。请注意,标记必须单独出现,并且行上不得有尾随空格。

那么,使用这里的文档有什么好处呢?它与 echo 基本相同,只是默认情况下,这里文档中的单引号和双引号对shell失去了特殊意义。下面是一个命令行示例:

正如我们所看到的,shell不注意引号。它把他们当作普通的字符。这允许我们在此处文档中自由嵌入引用。这可能对我们的报告程序很方便。

here文档中的文本经过参数展开、命令替换、算术展开,文字字符 $\ 必须用 \ 转义。

但是,当我们将起始标记括在引号中时,如下所示:

引号将被删除,并且不会对文本进行扩展:

here文档可以与任何接受标准输入的命令一起使用。在这个例子中,我们使用一个here文档向 ftp 程序传递一系列命令,以从远程 FTP 服务器检索文件:

如果我们将重定向运算符从 << 更改为 <<- ,shell将忽略here文档中的前导制表符(但不包括空格)。这允许对here文档进行缩进,从而提高可读性。

请注意,此功能可能有点问题,因为许多文本编辑器(和程序员自己)更喜欢使用空格而不是制表符来实现脚本中的缩进。

总结

在本章中,我们启动了一个项目,该项目将带领我们完成构建成功剧本的过程。我们介绍了变量和常量的概念以及如何使用它们。它们是我们将发现的参数扩展的许多应用中的第一个。我们还研究了如何从脚本中生成输出以及嵌入文本块的各种方法。