树莓派 2 Linux 和 Windows10 学习手册(二)

原文:Learn Raspberry Pi 2 with Linux and Windows 10

协议:CC BY-NC-SA 4.0

六、在命令行上编辑文件

信不信由你,在 Linux 上你最终要花大部分时间做的事情之一就是编辑文本文件。它们无处不在,你会发现它们被用于内容、应用程序源代码、配置文件和启动脚本。原因是尽管它们有些基础和枯燥,但它们对于存储人类可读信息非常有用。当你把这一点和易用性结合起来,你就知道你是赢家了。Linux 没有像 Windows 那样的注册表,应用程序很少不使用文本文件进行配置。即使 Sendmail 有一个历史上很糟糕的配置文件(事实上非常糟糕,您需要为另一个应用程序编写一个配置文件,然后该应用程序将为您创建 Sendmail 配置文件),它也将其配置文件存储为普通的旧文本。

在这一章中,我们将给你一个文本文件的简要概述,以及它们今天是如何被使用的。然后,我们将介绍本章将要涉及的两个编辑器(nano 和 vim ),然后我们将进入本章的真正内容,并向您展示如何实际完成工作。

什么是文本文件?

当计算机在磁盘上存储数据时,它可以以两种格式之一存储数据。它可以将数据存储为文本文件,也可以存储为二进制文件。很自然地,当计算机把一切都存储为二进制数据(那些好的旧的 1 和 0)时,究竟是什么使文本文件不同于二进制文件呢?毕竟,文本文件肯定也必须以二进制格式存储。如果你沿着这些思路思考,那么你是非常正确的。与其说是数据本身如何存储,不如说是数据如何读取。

欣赏这种差异的最好方法是想想你现在正在读的这一页。你能理解我写的原因是因为我们已经默认使用英语交流。这一页的内容有我们都理解的结构。我们用大写字母开始句子,我们用标点符号来表示句子何时结束或何时停下来喘口气。简而言之,我们这里有一个相当复杂的协议,但它的工作,因为我们都有能力读和写英语。我们既能理解结构(如何读写数据),也能理解语义(单词的实际意思)。那么,这句话怎么样:

早上好!你好吗?

我们马上就能看出这不是英语,毕竟,如果是英语,我们也能理解。除非你碰巧会说荷兰语(我们不会,但我们的好朋友会),否则你可能不知道它是用哪种语言写的。即使内容没有意义,但结构本身有意义。我们可以看到我们有两个句子,一个是陈述句,另一个是疑问句。在这种特殊情况下,我们可以猜测第一个单词的意思。如果我们像英语一样发音,它听起来非常类似于 good morning(这实际上是这个词的翻译)。由此我们大概可以猜到问题是“你好吗?”。

我们可以这样做的原因是,尽管我们不理解内容,但荷兰语和英语有着共同的结构或格式。我们共享一个字母表(尽管荷兰语确实有一些英语中没有的字符)和标点符号(句号、逗号和问号)。现在你可能发音不正确(毕竟它是一门外语),但是因为我们有共同的结构,即使我们不能理解它,我们至少可以处理它的内容。

图 6-1 更进一步。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-1。

Some Chinese Text

至少对于这本书的一般读者来说,图 6-1 会是无法解释的。实际上,这两个句子和我们在荷兰的例子中出现的是一样的,但是这次是用简体中文写的。对于门外汉来说,没有办法从这些字符中判断出它们应该如何发音。事实上,根据不同的方言,这些汉字可以用不同的方式发音。这是因为每个字符代表一个想法而不是一个声音,一旦你认识到这个想法,你就知道该说哪个单词。这就是为什么在中国,两个不会说同一种语言的人,通过读写汉字,完全可以很好地交流。

那么,我们为什么要偏离正题去学习外语呢?嗯,文本文件就像读写英语或荷兰语。内容是否有意义取决于读者会说英语还是荷兰语。这类似于两个应用程序及其配置文件。如果您给 Sendmail 电子邮件服务器一个 Apache web 服务器配置文件,它会把它吐回给您(Sendmail 不会说 Apache),但它可以打开并读取文件。

另一方面,中文是我们的二进制格式的例子。对于大多数西方人来说,这是非常陌生的(尽管可以说它比西欧语言中使用的语音书写要高效、优雅和复杂得多)。除非你懂中文,否则你无法理解内容或隐含的结构。用计算术语来说,你所拥有的是一个只有编写它的应用程序才有意义的原始数据块。

现在你可能认为以任意格式存储数据没什么大不了的。毕竟,写它的应用程序是会读它的应用程序,只要它理解文件,这就足够了。是也不是。如果程序中断会发生什么?如果您需要读取另一台机器上的文件,该怎么办?也许供应商已经提供了一些特殊的工具来做到这一点,但是问题就在这里——您需要特殊的工具。

文本文件则不然。文本文件可以在任何文本编辑器中打开,不需要特殊工具。由于绝大多数 Linux 应用程序都使用文本文件(我们想不出任何不使用文本文件的),所以您需要阅读或更改配置文件的全部内容就是您最喜欢的文本编辑器,Linux 有多种编辑器可供选择。因此,您可以熟悉一个工具,然后使用该工具重新配置您的所有应用程序,在编写脚本时编写源代码,甚至用它来写书(这正是我们现在正在做的)。

竞争者

所以文本编辑器是你的 Linux 瑞士军刀,是你永远不想离开家的东西。当然,就像瑞士军刀一样,有许多不同的文本编辑器可供选择,也像瑞士军刀一样,有些比其他的功能更多。有时你想要一个基本的文本编辑器,让你打开文件,编辑一些文本,然后保存结果。其他时候,您可能希望进行复杂的搜索,并替换或删除到行尾。当然,你可以很容易地使用一个更有特色的编辑器来完成你的所有任务,毕竟,如果你不需要的话,你可以不使用这些特性。也就是说,根据我们的经验,人们通常至少熟悉两个文本编辑器,一个是基本的,一个是全功能的。在本书中,这分别意味着纳米和 vim。

我们要介绍的第一个文本编辑器是 nano。这是一个轻量级的,易于使用的文本编辑器,它倾向于安装在大多数系统上。一些较新版本的 nano 有更多的功能(例如编写源代码时的语法高亮显示),但实际上看起来和工作方式都是一样的。如果你能在你的 Pi 上使用 nano,你就能在任何平台上同样很好地使用它。

Nano 实际上是作为 Pico 文本编辑器的替代品而编写的,Pico 文本编辑器过去与 Pine 一起提供,Pine 是一种基于文本的电子邮件客户端。Pico 仍然可以在较老的系统(通常更老)上找到,如果您找不到 nano,您可能仍然能够找到 pico。由于这是一个简单的替换,你习惯在 nano 中使用的相同的命令和基本特性也将在 pico 中可用。结合这一点,您可以了解大多数其他 Unix 平台以及 Linux。

Nano 的主要卖点是它的易用性,但它确实有许多有用的功能。然而,这些功能是从菜单中访问的,虽然它有足够的功能来简单地编辑配置文件,但有时您需要一些功能更强大的东西。这就是 vim 编辑器的用武之地。它能做 nano 能做的所有事情,但它能做的更多,这几乎是比较的终点。Vim 还具有语法高亮、不同的配色方案、剪切和粘贴、删除文本块、缩进文本块、打开多个文件并同时显示等功能。

现在您可能会想,如果 Vim 这么好,那么您只需学习如何使用这个文本编辑器就可以省去很多麻烦。像 nano 一样,它几乎在任何地方都以某种形式存在,它们都具有相同的基本功能,接受相同的命令。我们介绍 nano 的原因是,您可以在几秒钟内启动并运行。它的特点很容易解释,让它做你想做的事情真的很容易。尽管 vim 用户可能不同意,但根据我们的经验,vim 并没有获得任何易用性奖项。也就是说,如果你真的不想学习两个编辑器,并且觉得你宁愿只学习 vim,那也没关系,你不会真的错过任何东西,只是你可能需要更长的时间才能变得有效率。

THE HOLY WAR - VI VS EMACS

信不信由你,文本编辑器圣战(正如它已经成为众所周知的)已经持续了几十年,并且令人惊讶地比 Linux vs Windows 的争论更加激烈。事实上,它已经变得如此激烈,以至于 Richard Stalman(Emacs 的创始人和 GNU 自由软件基金会的创始人)装扮成来自“Emacs 教堂”的圣 Ignucius。他曾多次指出,“虽然 vi 是魔鬼的工具”(vi 在罗马数字中是 6),“使用 vi 不是一种罪恶,而是一种忏悔”。当然,虚拟世界的人群不会坐以待毙,并形成了“虚拟世界的崇拜”。emacs 最强的特性之一是你可以写代码让它做任何你能想到的事情。这使得 vi 人群说“Emacs 是一个伟大的操作系统,只是缺少一个像样的文本编辑器!”。

没有人真的期待这场辩论会结束,尽管有时会变得非常激烈,但通常都是在良好的气氛中进行的。一天结束时,对你来说最好的文本编辑器是让你最有效率的,也是你最喜欢使用的。尽管如此,阅读一些火焰战争和阅读它的历史可以为那些寻找事情做的人提供很好的娱乐…

从纳米开始

我们从 nano 开始,原因很简单,它真的很容易使用,不需要太多解释。你可以在 nano 中做任何你需要做的事情,虽然在另一个编辑器中你可以做得更快,但是 nano 会帮助你很好地完成工作。

可以使用 Nano 命令运行 nano。好吧,这并不奇怪,但是运行 nano 本身并不那么有用。毕竟你现在有了一个文本编辑器,你想做一些编辑。除非做一些笔记,否则没有人会只是写下文字而不想事后保存。一般来说,当你打开 nano 时,你想打开一个特定的文件进行编辑。该文件可能存在,也可能不存在,但是当按下保存按钮时,你可能知道你想给它起什么名字。为了便于介绍,我们将打开 nano,告诉它我们想要编辑 test.txt。我们可以通过以下方式完成:

[pi@raspberrypi ∼] $ nano test.txt

一切正常的话,你应该得到这样的东西(图 6-2 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-2。

Freshly opened nano with a new file

我们已经使用 SSH 连接到我们的 Pi,但是因为 nano 是一个基于终端的应用程序,所以无论您如何运行它,它看起来都是一样的。关于这类终端应用的一个有趣的事情是,它们对你的终端有多大很敏感。固定终端(如物理控制台)有固定的屏幕尺寸。当您通过 SSH 连接或使用虚拟终端时,您可以调整屏幕的大小,这将导致 nano 随之调整大小。这使得它非常灵活,无论屏幕的形状或大小如何,都能满足您的需求。

那么这一切意味着什么呢?

Nano 的屏幕一开始可能会有点混乱(尤其是底部),但大多数时候你实际上不会注意到除了文本之外的任何东西。事实上,所有其他的东西都在背景中消失了。例如,在你屏幕的左上方,你可以看到我们正在运行“GNU nano 2.0.9”。在写这本书之前,我们不知道我们运行的是什么版本的 nano,老实说,直到我们必须在这里描述它,如果有人要求我们凭记忆绘制 nano 布局,我们会很难做到这一点。事实上,我们根本做不到!

从 nano 的标题栏中还可以收集到另外两条信息。正中间的是你正在编辑的当前文件的名字。在我们的例子中,它是“/home/pi/test.txt”。不完全是惊天动地的,但同时在多个窗口上工作是很常见的,能够快速找到你的方位会派上用场。

第三条有用的信息是文件是否被修改过,即是否有任何未保存的更改。这也可以派上用场,因为虽然您可以简单地重新编写文件,但有时如果您从另一个位置编辑了文件,您不确定您所做的更改,您只想知道是否有任何更改没有反映在您正在编辑的文件中。它还可以简单地唤醒你的记忆,提醒你保存你的工作。要查看这是什么样子,只需按空格键进行更改(图 6-3 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-3。

Modified file in nano

标题栏差不多就是这样了。在屏幕的底部实际上有两个部分。第一个是状态栏,显示重要信息(现在它告诉你这是一个新文件),第二个是快捷方式或菜单栏。我们不会对状态行说太多,因为它很容易理解。任何你需要知道的重要信息,纳米都会放在那里。更有趣的(事实上也是使用 nano 的关键)是快捷工具栏。我们在这里仔细看看(图 6-4 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-4。

The shortcut bar

当你在 Windows 或 Mac 上工作时,你可能已经到了不用鼠标来复制和粘贴东西,而是使用键盘组合的地步。在 Windows 上,您可以使用 control 和 c 键,而在 Mac 上,您可以使用 command 键和 c 键。当您同时使用多个键时,您最终会得到一个组合键。nano 的工作方式非常相似,但不是复制和粘贴,它所有的关键功能都是由组合键驱动的。因为要记住所有可能的选项有点困难(尽管你经常使用的选项会很快被你长期记忆),nano 在快捷栏中显示它们。实际上,它只显示了最常见的命令,但我们从来没有必要偏离这些命令。

要使用任何命令,你需要使用正确的组合键。你可以通过查看快捷栏找到你需要的组合。书写“控制键”的简写方式是使用克拉或“”符号。查看快捷栏,你可以看到,如果你想获得帮助,你需要使用“G”,或者换句话说,按住控制键,并按下 g 键。

保存您的文件

保存一个空白的文本文件是非常无聊的,如果你只有一个空格,很难判断它是否保存正确。我们要在这里找一首老歌,敲入“玛丽有只小羊羔”。

与一些编辑器不同(最明显的是 vim,您稍后会遇到),nano 没有不同模式的概念。也就是说,当您键入 nano 时,它将被解释为要放入文件中的文本。另一方面,Vim 以命令模式启动,如果您只是开始输入,您将会得到一些有趣的结果。正如您所料,在每一行之后按 enter 键会将您带到下一行的开头。输入童谣后,您应该会看到类似这样的内容(图 6-5 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-5。

Mary had a little lamb, nano style

我们把 nano 的窗口缩小了一点,这样我们就不会占用大量的空白空间。它也是一个很好的例子,说明了为什么你可能想要调整窗口的大小,以及当你实际这么做时它是什么样子的。在这种情况下,一切看起来都很好,虽然不可否认的是,要写一个合理长度的东西,你需要比这个大一点的东西。

你可能已经注意到状态线似乎已经消失了。这是因为一旦向文件中添加了任何内容,该文件就不再是新文件。我们修改过的警告仍然在右上角。是时候去掉它,让玛丽安全地进入磁盘了。

查看快捷工具栏,您可以看到我们可以使用的选项。你最常使用的是“写出”,意思是“保存文件”和“退出”。Exit 实际上是 WriteOut 的两倍,因为如果您试图在未保存更改的情况下退出 nano,它会询问您是否要保存更改。

让我们从 WriteOut 命令开始。按住 control 键并按下 O 键。Nano 现在看起来应该是这样的(图 6-6 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-6。

Using the WriteOut command

你会注意到我们这里有两个变化。首先是我们的状态线有所回升。这次它在询问信息,或者至少它想让我们确认。现在是保存文件的时候了,nano 要求我们确认文件名。因为我们在最初启动 nano 时给了它一个名称,所以这是 nano 默认提供的名称。如果我们现在按回车键,它将保存文件,但在我们这样做之前,让我们看看快捷栏。

您可以看到它变成了一套全新的选项。这是因为快捷栏实际上是与上下文相关的,也就是说,它会根据你当时正在做的事情向你显示最相关的内容。这些选项仅在保存文件时有用(甚至可用),所以这是你看到它们的时候。除了取消(当你突然决定根本不想保存时),我们实际上从来没有使用过这些选项。

好了,回到保存文件。如果您愿意,您可以更改文件的保存名称,nano 不仅会以您的新名称保存该文件,而且当您返回编辑同一文件时,还会继续使用该名称,您应该会收到如下消息(图 6-7 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-7。

File has been written to disk

你可以看到快捷方式栏已经恢复了它通常的样子,并且我们的状态行中有了一条新消息。您还会发现修改后的状态已经消失了。目前为止一切顺利。

让我们快速看一下,如果您在拥有一个已编辑文件的情况下尝试退出 nano 会发生什么(图 6-8 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-8。

Exiting before saving

我们在这里所做的只是在文件中添加一个签名(尽管不可否认这可能不是 Mary 写的),然后尝试使用 control 和 x 退出 nano。这一次,状态栏给了我们一个可怕的警告,快捷栏给了我们一些减少的选择。我们可以说“是”,在这种情况下,nano 将在退出前保存更改,或者我们可以说“否”,在这种情况下,nano 仍将退出,但我们将丢失自上次保存文件以来所做的所有更改。我们也可以更好地考虑退出,改变我们的想法。在这种情况下,我们可以使用 control plus C 取消我们的退出请求,这将把我们带回 nano。

在纳米里移动

你可以通过使用光标键来移动你的文本,就像你可能习惯于使用你选择的文字处理器一样。事实上,我们在这里提到这一点的唯一原因是因为一些编辑器根本不使用这些键,因为它们让你的手离开主行太远(也就是说,它们使用起来很慢,因为你必须移动你的手很长的距离才能够到它们)。因此,与其解释光标键,我们将向您展示我们在 nano 中最常用的两个功能,这两个功能恰好都与查找内容相关。

第一件事你可能会想使用的是搜索功能,有点奇怪的命名为“在哪里”在纳米。我们可以通过按 control 和 W 键来发出命令,以进入下一步(图 6-9 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-9。

Searching for some text

我们又有了一些新的选择。最常见的任务是简单地找到一个单词。所以让我们搜索玛丽,看看会发生什么(图 6-10 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-10。

Nano found Mary

按下回车键后,nano 将尝试找到您搜索的文本。在我们的例子中,我们已经到了文件的末尾,这就是为什么你可以在状态栏中看到“Search Wrapped”——nano 到达了文件的末尾并绕回以从开始处继续搜索。这是一个非常方便的功能!

你要找的单词会出现不止一次,这是很常见的。如果你想要第二个玛丽,你会想重复你的搜索。只需按下 control 和 W,然后立即按下 enter 键,就可以做到这一点。Nano 会记住你上次搜索的内容,如果你不提供新词,它会用旧词再次搜索。这意味着您可以轻松地在文档中循环查找您正在寻找的特定条目。

我们要介绍的最后一个特性是一种稍微更具体的搜索形式。我们不想搜索一个单词,而是想找到一个特定的行号。在日常的文本编辑中,你实际上不会经常这么做。然而,当您开始编写脚本并且它们产生错误时,您通常会被告知错误发生在哪一行。对于短程序来说,这通常并不重要,但是如果你正在构建一个相当宏大的东西,你会希望能够快速地到达那一行。即使在中等长度的程序中也是如此,因为如果你有一个特别棘手的问题,你将不得不重新访问一段特定的代码,而且你肯定不希望每次都必须使用光标键来找到它。

要转到特定的行号,您需要像之前一样使用 control+W 开始搜索,但不是键入任何文本,而是应该使用 control + T。这将把状态行更改为(图 6-11 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-11。

Go to a specific line number

你所要做的就是输入你想去的行号,然后按回车键。您还可以看到这里有一些其他有用的选项,允许您分别转到文件的开头和结尾。您也可以按下 command 和 T 键返回到搜索文本,或者按下 command 和 C 键取消搜索。

包装纳米

这就是我们将为 nano 介绍的全部内容,但是正如您所看到的,即使是我们介绍的有限的功能,也已经让您能够编辑配置文件,甚至编写自己的书。Nano 非常适合那些你不想分心,只想完成工作的任务。碰巧的是,这本书的大部分内容都是用 nano 编写的,这表明这个工具不仅仅是一个玩具!也就是说,现在让我们来看看 vim 编辑器能为您提供什么…

Vim 入门

Vim 比 nano 高级一点。尽管 nano 被设计得非常简单,但 vim 被设计成一个功能齐全的环境,可以执行各种任务。您甚至可以将其他应用程序(如 shell 和源代码控制)直接与 vim 集成,这意味着您可以在不离开文本编辑器的情况下完成大量工作。

这种原始力量的缺点是你必须学会如何使用它。与基本上可以立即使用的 nano 不同,vim 有模式的概念。一般来说,您要么处于正常模式,要么处于插入模式,这就是我们将在本节重点讨论的内容。

然而,我们想指出的是,即使我们自己经常使用 vim,我们也不是你所说的 vim 专家。我们知道如何使用有助于我们工作的功能,但是有大量的功能我们没有使用或者甚至不知道。由于这只是关于 vim 的初级读本,我们不认为这是一个问题,但是如果您认为 vim 是适合您的工具,那么有大量专门介绍 vim 的书籍以及大量关于它的一些更高级和更强大的特性的视频教程。

与其再打一首诗,不如让我们再给玛丽一次机会,让她回到 vim 的舞台上:

[pi@raspberrypi ∼] $ vim test.txt

不用说,启动 vim 和启动 nano 一样简单,但一旦启动,它看起来确实有点不同(图 6-12 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-12。

Starting up vim

漂亮的标题、状态行和快捷栏都不见了。在 vim 领域,就像 Linux 本身一样,我们应该知道自己在做什么。在屏幕的左下方,您可以看到我们正在编辑的文件的名称。接下来是 6L,它告诉我们文件中有多少行,后面是 115C,它告诉我们字符数。在屏幕的另一边,我们有另一条有用的信息 1,1。这告诉我们当前所在的行和所在的列。现在它告诉我们,我们在第一行第一列。这是有意义的,因为我们刚刚打开文件,如果你看光标,肯定我们已经坐在第一个 m 的上面。

Note

根据 vim 的版本和配置方式,它通常会将您带到上次停止编辑该文件时的位置。这在编程时真的很有用,因为它能让你立即到达动作发生的地方。

您还可以在文件的最后一行下面看到几行波浪号“∨”。这些在这里提醒我们,虽然屏幕有一定的大小,但玛丽下面没有内容。这在进行某些类型的编程时特别有用,并且您希望确保没有额外的空白。这真的只是一个视觉线索,不会妨碍你。

Vim 的模式

Vim 给人的感觉有点奇怪,因为不像文字处理器和更基本的文本编辑器,使用 vim 你不能马上开始输入。这是因为 vim 有不同的模式或个性。当您第一次启动 vim 时,它处于命令模式。与 nano 使用组合键发出命令不同,vim 有大量单字母或双字母命令。你可能会认为这非常令人困惑,但是你是对的。一旦您知道了让 vim 执行您的命令的神奇按键,您就会喜欢这种控制文本编辑器的方式。然而在那之前,它可能会让你分心。

所以让我们离开命令模式,进入更舒适的状态。我们可以通过按“I”键在 vim 中复制一个 nano 风格的环境。这将使我们脱离命令模式,进入插入模式。当你处于这种模式时,你可以分辨出来,因为你在左下角得到–插入(图 6-13 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-13。

Vim in insert mode

此时,您可以像使用 nano 一样在文本文件中移动。光标键按预期工作,您可以像在 nano 下一样添加和删除文本。当你想做除了写文本以外的事情时,这种差异就开始了。你如何保存你的改变?

保存您的更改

要在 vim 中保存您的更改,您需要做的第一件事是退出插入模式。这将把您带回到正常模式,在这里您可以发出命令,从而可以保存您的文档。首先,让我们把签字人改为约翰。这将为我们提供文档急需的更改。为了实际保存文档,我们使用以下命令:

:w

该命令实际上是一个冒号后面跟着一个小写的 W。Vim 区分大小写,因此如果您尝试这样做:W,您将得到一条错误消息,表明它不是有效的命令(图 6-14 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-14。

Whoops, W isn’t a command

不过不必惊慌,只需再次尝试该命令,您就会没事(图 6-15 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-15。

:w on the other hand works like a charm

如果您想在保存时更改文件名,您可以简单地在命令后键入您想使用的文件名,如下所示:

:w test2.txt

这将保存对新文件名的更改,然后继续编辑新文件。

走出维姆

这使我们只剩下一个新的功能,让您达到与 nano 相同的基本水平(我们稍后将介绍搜索)。一旦你完成了所有的编辑并保存了文件,你就会想要退出。用于此目的的命令是:

:q

只要您没有做任何更改,vim 将立即退出,您将回到命令行。然而,如果你已经做了一些改变,并且你试图这样做,你会再一次被 vim 激怒(图 6-16 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-16。

To quit without saving you’ll need an override

幸运的是,vim 已经告诉我们如何修复这个问题,但是基本上,如果您想在不保存更改的情况下退出,您需要明确地告诉 vim 这是您想要做的:

:q!

这就是离开维姆的全部!

在 Vim 中搜索

在这一节中,我们将只介绍基础知识,并向您展示如何进行与 nano 中相同的搜索。事实上,vim 具有非常高级的搜索功能(一开始可以使用正则表达式——参见 boxout ),但是当您在使用中变得更加高级时,您可能会依赖这些功能,但是您可能仍然会发现您大部分时间都在做简单的事情。

要在 vim 中搜索一个单词,您需要做的就是在它前面加上一个正斜杠。要尝试这样做,请在命令模式下(轻按两次 escape 键以确保您处于正确的模式)键入:

/Mary

这将找到 Mary 的所有实例,并为您突出显示它们(图 6-17 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-17。

Vim found Mary

你可以看到“搜索触及底部,继续在顶部”的信息,这相当于纳米的“搜索包装”。使用 vim 需要注意的一点是,因为它使用正则表达式进行搜索,所以默认情况下也是区分大小写的。这意味着如果你搜索的是 mary 而不是 Mary,你会收到投诉(图 6-18 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-18。

Vim can’t find mary

vim 优于 nano 的一个方面是如何进行重复搜索。要移动到下一个搜索结果,您只需按“n”键。在上面的示例文本中,如果您使用/Mary 搜索 Mary,然后一直按“n ”,您将看到 vim 会为您循环搜索结果。我们无法在出版物中展示这种影响,但如果你不想做其他事情,这是一个很好的分散注意力的几秒钟。如果你想在相反的方向搜索(例如,向文件的顶部而不是底部),只需使用大写字母“N”即可。

REGULAR EXPRESSIONS

右手中的正则表达式就像魔术一样。他们处理复杂的文本处理任务,就像一把热电锯穿过温热的黄油。他们还经常被用来给系统管理员尿床的噩梦,并吓唬小孩子。一旦你知道如何使用正则表达式,它就非常强大,但是这种强大是有代价的。尽管如此,正则表达式的基本知识将对您非常有用,您可以在 vim 中直接使用它们来搜索、替换和操作您的文本。

我们在这里没有足够的空间来讨论正则表达式(通常简称为 regex ),但是互联网上有一些很棒的资源可以帮助您入门,更不用说关于这个主题的完整书籍了。一个很好的起点是 Jan Goyvaerts 的网站 http://www.regular-expressions.info/ 。这个网站不仅充满了有用和有帮助的信息(它经常是我们的第一站),而且 Jan 还编写了一些非常强大的软件。我们在很多年前购买了 RegexBuddy,当我们试图调试一些复杂的正则表达式时,它仍然是我们的第一选择。如果你想要一个简单的工具来测试你的正则表达式,看看 http://rubular.com/ ,它用 Ruby 做实时正则表达式解析。

我们在 nano 中讨论的最后一件事是移动到特定的行号。这在 vim 中实际上要容易得多,可能是因为这是程序员必须一直做的事情。要跳转到特定的行号,只需将行号放在冒号后面。例如,要跳到第 5 行,您需要做的就是:

:5

一旦你按下回车键,你将被直接带到第 5 行。和以前一样,这个特性对于普通编辑来说不是特别有用,但是当你摆弄一个 simplify 拒绝按照你知道的方式工作的程序时,它就非常方便了!

在维姆四处走动

虽然我们已经向您展示了如何在文本文件中移动,但是我们还没有利用 vim 的强大功能。Vim 提供了额外的命令,可以让我们更快地浏览文本。

在命令模式下,你仍然可以使用光标键来移动你的文本,这很好也很方便。实际上,您可以使用另外两个命令,即“h”和“l”键来做同样的事情。h 键将光标向后移动一个字符,l 键将光标向前移动一个字符。诚然,这不是很令人兴奋,但我们现在可以引入’ b ‘和’ w '命令。它们的工作方式和前面的命令一样,不仅仅是后退一个字符,而是跳到单词的开头。突然,这变得更有用了,因为现在如果你有一长串的文本,而不是长时间按住光标键(很诱人,不是吗?),一次跳一个字就可以大大加快进程。

不过,我们可以更进一步。如果你想跳到一行的开头,你可以按“0”(零)键。要到达行尾,只需使用$(在我们的例子中,这意味着按住 shift 键并按下 4 键)。这非常有用,因为很多时候你想找到一行的开头或结尾,现在你只需按一个键就可以了。

让我们把它放在一个表格里,这样会更清楚一点:

| 行首 | 后退一个单词 | 后退一个字母 | 转发一封信 | 前进一个单词 | 行结束 | | --- | --- | --- | --- | --- | --- | | Zero | b | h | l | w | $ |

所以我们现在有一个渐进的方法来将光标从线的一端移动到另一端。我们再也不用摆弄回车键或空格键,或者等着光标在屏幕上滚动时睡着。这仅仅是个开始,因为这些键不仅仅可以用来移动文件,还可以用来指定输入 vim 的命令的范围。

在 Vim 中删除

要删除一个字符,你只需要按“x”键。这将删除当时光标下的任何字母。这本身有时是有用的(通常是在修改一个错别字的时候),但是它并没有给你带来比进入插入模式更大的优势。然而,“d”键提供了一个非常强大的删除命令,您现在可以结合您新获得的移动光标的知识来做一些非常令人印象深刻的事情。

我们要尝试的第一个删除命令是“dd”。让我们从删除文件中的第一行开始。为了确保我们都在正确的位置,使用下面的命令返回到第一行:

:1

现在,我们将通过键入以下命令来删除当前行:

dd

你的诗现在应该短一些,看起来像这样(图 6-19 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-19。

Deleting the first line

既然我们已经毁了我们的诗,现在是引入 vim 中另一个非常有用的特性——“撤销”命令的好时机。要取消我们刚才所做的更改,只需按“u”键,Mary 就会恢复往日的光彩。这不仅可以防止意外删除错误行时的恐慌,还意味着我们可以用许多有趣的方式删除这一行的一部分。

如果您想要删除多行代码,您可以通过在命令前面加上一个数字来告诉 vim 您想要它执行一个特定命令的次数。例如,如果我们想从我们的诗中删除三行,我们可以这样做:

3dd

这相当于按下 d 键六次,并给你这个快乐的结果(图 6-20 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-20。

Deleting three lines in one go

您可以在左下角看到,正如我们所希望的那样,现在“少了 3 行”。让我们用“u”键恢复 Mary,并尝试其他方法。将光标放在第一行的开头,尝试执行以下命令:

dw

这个命令结合了删除命令和我们前面看到的移动命令。在这种情况下,我们将它与“w”结合,表示“向右一个单词”。我们可以在这里看到该命令的效果(图 6-21 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-21。

Deleting a single word

我们没有移动光标本身,而是有效地选择了我们想要删除的文本。在这种情况下,“d”将从光标的当前位置开始删除,直到下一个单词的第一个字母。我们可以通过多次按下“x”键轻松实现这一效果,但使用“dw”显然更快、更精确。让我们再更进一步,结合我们所知道的。我们知道,通过在命令前面加上一个数字,我们可以让 vim 多次执行一个命令。我们还知道,我们可以将“d”命令与“w”结合起来删除一个完整的单词。让我们唤醒 Mary(再次按下“u”键)并尝试这个新的改进命令(图 6-22 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-22。

Deleting two words at once

2dw

那不是很有趣吗?我们已经能够组合一系列命令来表达一些相当复杂的任务。你当然可以以任何方式组合这些。例如,如果您想删除行尾的所有内容,可以使用 d$。如果您想删除从该行开始的所有内容,可以使用 d0。能够以这种方式组合命令使得 vim 成为处理文本的强大工具。尽管这些特性在处理英语散文时用处有限,但当你开始处理编程语言时,它们确实变得非常有用。

杂项小命令

在我们继续研究 Vim 的可视化模式的一些特性之前,我们将快速概述一下其他一些将会派上用场的简单命令。“I”进入插入模式,但有时您想在当前行的上方或下方书写。你可以通过分别使用 O 和 O 得到这个效果。这两种方法都会插入一个新行,然后切换到插入模式。有时你不需要实际编辑文件的内容,你只需要改变一个字母。这可能是因为打字错误,或者你只是想增加一个数字。您可以使用“r”键来完成此操作。这允许您用任何其他字符替换光标下的字符。只需按下“r ”,然后输入要替换的字符。简单却很有效!

视觉模式

我们自己并不经常使用视觉模式,但是当我们使用它的时候,它通常会节省我们大量的时间和精力。此模式允许您选择或突出显示文本块,以便进一步处理。这类似于在文字处理器中用鼠标高亮显示文本。有两种方法可以进入视觉模式,按“V”键或“V”键。如果你使用大写的 V,你将能够根据行选择一个文本块。使用小写 v,您可以获得额外的精度,因为您可以基于单个字母而不是整行进行选择。总的结果是一样的,你的选择只取决于哪一个对你来说最方便。

那么视觉模式到底能做什么呢?它本身并不做任何事情,它只是提供了一种简单的方法来告诉 vim 下面的命令应该应用于什么文本。按照我们之前的例子,让我们选择玛丽的诗的一大块。您不需要突出显示我们拥有的确切文本,而是尝试获得一个相当随机的块(图 6-23 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-23。

Highlighting text in visual mode

正如你从突出显示中看到的,我们使用了小写的“v”选项,因为我们想要突出显示某个特定的单词。除了一种戏剧性的感觉之外,没有其他真正的原因,所以它可能很容易是一串用“V”选择的线条。现在让我们应用我们最喜欢的命令,删除命令。按“d”你会发现高亮文本神奇地消失了。在这种情况下,您不需要向 vim 提供额外的信息来告诉它删除什么,因为实际上您已经通过在第一个位置高亮显示文本告诉了它。

当然,除了删除文本,你还可以做其他有趣的事情(尽管删除文本很有趣)。例如,如果要缩进这首诗的前两行,可以用 V 突出显示前两行,然后使用大于号和小于号( >和< respectively) as appropriate. We can’t show you how that would look in print (honestly moving huge chunks of text back and forth is surprisingly theraputic) but we can show you the end result of this command (Figure 6-24 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-24。

Indenting highlighted text

还有一个非常有用的特性我们还没有谈到,它非常适合视觉模式——复制和粘贴。

复制并粘贴 Vim 样式

在终端中复制和粘贴是一个不确定的命题。原因是运行在终端中的应用程序实际上生活在它自己的小世界中。例如,如果您从笔记本电脑的终端上复制文本,该文本将被复制到笔记本电脑的剪贴板上。远程应用程序(在本例中是 vim)不知道您这样做了。同样,如果你把笔记本电脑上的内容粘贴到 vim 中,vim 会简单地接收到一组击键,它不会知道这些内容来自剪贴板。一般来说这是可以的。如果你只是想移动一些文本,那么以这种方式使用剪贴板可能是好的。

有些时候,这并不能如你所愿。例如,如果你有一个分屏,你有两个并排的文件,你想选择左边的第一段,你现在有点卡住了。终端只是向你展示服务器上的内容,它只是一个显示器,因此终端并不真正了解它在显示什么。如果你试着复制和粘贴左手边,你会发现右手边也跟着来了。这远非理想(更不用说高度刺激性)。

您可能遇到的另一个问题是,您可能希望 vim 对文本做一些事情。如果你只是想粘贴一次,没什么大不了的,但是如果你想粘贴正好 10004 次呢?Vim 在那里帮不了你,因为它对你的本地剪贴板没有概念。你会发现,当这些问题突然出现时,它们会变得相当小众,你可以绕过它们。然而,这很可能会激怒你,因为你会想“要是我能复制这篇文章就好了!”当你坐在那里试图解决问题的时候。

我们需要的是一个远程剪贴板,远程应用程序可以在这里存储数据进行处理。这方面没有标准,但是许多基于终端的应用程序提供了一个解决方案,在 vim 的例子中,它提供了拖拽和粘贴功能。要提取某些内容,您需要使用“y”键。这使用了与’ d ‘命令相同的修饰符,因此您可以非常愉快地用’ yy ‘复制一整行(记住,字母只是对折),用’ yw '复制一个单词。同样像删除命令一样,我们也可以在可视模式下使用它。

首先,只需突出显示您想要复制的文本。为了简单起见,我们再来看看上面两行。用“V”键高亮显示后,按“y”键将其拖入剪贴板。这是第一阶段的完成,现在我们要做的就是把它粘贴回我们的文件。有两种方法可以将 paste 命令与’ P ‘或’ P '一起使用。与“V”和“V”命令一样,大写和小写版本通常以某种方式相关联。在这种情况下,“P”键在当前行之前插入内容,而“P”键在当前行之后插入内容。在点击其中一个键之前,请随意将光标移动到文件中的任何位置。效果确实如您所料,vim 只是将内容粘贴进去。当然,您可以将这些命令与数字前缀结合起来,重复插入内容的次数。下面是如果你结束 40p 会发生什么(图 6-25 ):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6-25。

Mass pasting

虽然你还不知道,但你实际上已经学会了剪切和复制文本。每当您使用“d”键时,您会从文件中删除文本,但副本会放在剪贴板中。如果您选择了一行并用’ dd ‘删除它,您可以用’ P ‘恢复它。请记住,如果您使用’ p ‘键,因为您已经删除了该行,您实际上将在您当前所在的行之后插入原始行。如果要将其恢复到原始位置,必须使用’ P '键粘贴到当前行之前。

摘要

在本章中,我们已经了解了文本文件和二进制文件的区别,以及为什么文本文件是配置软件的通用语言。我们已经讨论了您在旅行中可能会遇到的两个关键文本编辑器,并且我们已经为您提供了这两个编辑器的良好基础。当然,我们不能向你展示所有可能的事情,但是现在你应该可以自己探索和尝试新事物了。

在下一章中,我们将介绍 BASH shell,并向您展示一些系统管理员需要了解的更重要的事情…

七、管理您的 Pi

所以现在你有了这个神奇的圆周率,安装了 Raspbian,登录并以你喜欢的方式得到了你的圆周率,甚至在头脑中有一些事情要做。尽管您对 Linux 越来越熟悉,但问题是每次您的 Pi 关闭或断电时,您都需要回到它那里,登录并进行物理设置,然后才能再次使用它。嗯,不会再有了!

这一章是关于系统管理和一些基本的应用程序功能,这些功能将允许您管理系统在启动时的运行方式。为了提供帮助,我们还将为您提供一个用 BASH 语言编写代码的速成班,以便您可以创建自己的启动脚本来启动应用程序(我们也将介绍什么是脚本)。最后,我们将介绍系统的一些基本安全性,包括用户管理,以便您可以更改密码和向系统添加新用户。

远程访问 Pi

对于来自 Windows 或 OSX 环境的人来说,在 Linux 环境中工作最难适应的事情之一可能就是在 shell 中工作。首先,这并不美好,但更重要的是,人类是非常习惯于操纵事物来达到某种结果的生物。这意味着基于 GUI 的环境更直观——首先。由于来自 Windows 环境,我可以肯定地说,命令行是一个非常好的工作环境。与键盘和鼠标操作相比,点击、拖动和执行上下文操作所能完成的工作量是惊人的。能够纯粹从命令行在系统中工作确实是一项巨大的财富。但是你需要熟悉它,而唯一的方法就是练习!

当我们需要讨论管理您的 Pi 时,为什么我要讨论命令行呢?因为大多数 Linux 管理是通过命令行实现的,您想要使用的大多数(如果不是全部)系统功能都可以从命令行获得。此外,通过命令行访问您的系统比通过 GUI 访问要容易得多,而且占用的资源也少得多。要通过命令行远程访问您的系统,您只需使用安全 Shell(或简称 SSH),这是您在第三章第一次看到的。您还需要确保系统的网络访问在启动时也是可用的。这两个功能提供了从任何地方管理系统的基本功能,因为您已经启用了网络,然后可以使用 SSH 连接到您的系统。因此,让我们更仔细地检查一下这两个函数,以便您理解如何确保您可以始终访问您的 Pi 来管理它。

建立关系网

如果您和大多数人一样,有一个路由器为系统中的多个设备提供网络访问,您可能会意识到您的路由器会自动为所有设备提供网络 IP 地址。它不是通过任何魔法,而是通过一种叫做 DHCP 的特殊协议来实现的,DHCP 代表动态主机控制协议。DHCP 的目的是自动为机器分配 IP 地址,这意味着不需要手动分配。当 DHCP 被配置时,它会被网络服务自动调用。我们将在第九章 WiPi 中更详细地介绍如何配置网络。

但是,如果出于某种原因,您需要手动调用 DHCP 为您分配一个 IP 地址并让您连接到网络,您需要使用dhclient命令。您将需要以 root 用户身份运行它,因为您需要使用大多数这些命令,因为您正在更改核心系统功能。如果您只是照原样运行dhclient,您将尝试为系统上的每个接口获取一个新的 IP 地址,这可能并不理想。您实际上可以指定一个特定的接口供dhclient使用,只需将它作为命令的第一个参数(即dhclient eth0)。

域名服务器(Domain Name Server)

在使用您的系统网络时,可能需要记住的最重要的事情之一是 DNS,它是域名系统的缩写。DNS 是互联网上的每个系统能够将 URL(例如, www.apress.com )转换成 IP 地址(例如,173.203.147.183)的方式。Linux 机器上的 DNS 通过知道它应该将查询定向到哪里来工作。这由文件/etc/resolv.conf控制,该文件包含一个名称服务器指令,告诉您的系统应该查询的名称服务器的位置。通常这是你的路由器,但它也可以是互联网 DNS 服务器,如谷歌的 8.8.8.8。这些会这样出现在resolv.conf里:

nameserver 10.0.0.1

SSH 是一种可以远程获得到 Pi Shell 的安全和加密连接的方法,而不需要对它做任何物理上的事情。SSH 的安装和运行,以及访问你的系统的基本命令,在第三章中有所介绍,所以我们不会涉及太多细节,而是会深入 SSH 的功能。SSH 由在 Pi 上引导时运行的sshd守护进程提供。您可以用与您的系统相同的方式启动和停止这个命令,即使用sshd init 脚本。所有系统初始化脚本通常位于/etc/init.d/中,它们通常以 root 用户身份运行。要使用它们,请将您想要采取的操作(启动、停止和重新启动)添加到脚本的第一个参数中。所以要重启 SSH,运行/etc/init.d/ssh restart。此外,这些脚本是您的系统用于在引导时运行程序或命令的脚本。因此,让我们看看如何利用它来编写您自己的 init 脚本。

BASH:基本编码

除了“嘿,你能帮我修电脑吗?”我最常被问到的计算问题是“你能教我如何用 C 写这个应用程序吗?”。我的同事、朋友或亲戚没有一个月不来找我,让我教他们如何写软件。通常,他们会问如何用 c 编写应用程序的快速纲要。这在很大程度上不是一个不合理的问题,但通常当我问他们想做什么时,他们会想做一些简单的事情。虽然我很乐意提倡学习 C,但是有一种更简单的方法可以让他们了解软件设计的奇妙世界,而不用承担学习 C 的任务(对于他们通常追求的东西来说,这是一种过度的追求)。

这是本章这一部分的目标。我们旨在向您介绍编码、基本逻辑结构和一些基本的软件设计原则,以便您能够完成工作。我们将通过向您介绍 BASH 来实现这一目标。虽然许多人会嗤之以鼻,但 BASH 是介绍编程核心基础知识的完美方式。BASH 也是世界上使用最广泛的软件语言之一,因为 Linux 系统上的大多数应用程序都有某种程度的 BASH 软件,在这个过程中执行某种中介功能。

什么是 BASH?

如第三章所述,BASH 是 Bourne Again Shell 的缩写,是大多数 Linux 和 UNIX 环境的默认 Shell 环境。当您通过命令行登录到您的系统并看到提示时,这个提示是由 BASH shell 提供给您的。Shell 将接受命令和指令,并处理它们以执行系统功能。在通过命令行使用或管理系统时,您通常发出的大多数命令都是调用其他应用程序以便生成输出的命令。

虽然这看起来是一种非常明显和简单的方式,但它也是演示任何软件应用程序如何工作的核心规则的一种极好的方式。你接受一个给定的输入,对它进行某种计算,产生一个响应(称为输出)。事实上,这个过程是许多人认为理所当然的生活和工作的许多功能的基本过程。正是这个输入、计算、输出的基本模型,将构成我们理解编程如何运作的基本模型。

那么回到第一个问题:BASH 是什么?BASH 是一个 shell,shell 是一种获取输入并根据输入进行计算以生成输出的方法。BASH 有许多工具,允许您使用几乎所有编程语言的相同基本逻辑结构。这就是 BASH 作为编程语言和 shell 的工作方式——您可以编写许多这样的逻辑操作和命令,然后用它们来组成计算。现在,您对 BASH 是什么、shell 是什么、任何计算机程序的广泛用途是什么,以及如何使用所有这些函数来执行输入、计算和输出的基本操作有了初步的了解。现在,您已经对我们想要实现的目标有了一个非常宽泛的了解,我们可以将这些知识作为学习如何用 BASH 编写程序的基础。

从 BASH 开始

所以你知道什么是 BASH,什么是 shell,以及程序的基本逻辑是如何工作的,这通常被许多人认为是鹤立鸡群。事实上,一些面试官评论说,虽然他们的雇主对潜在的软件工程师有严格的提问,他们都可以支持我们所涵盖的内容,但他们抱怨说,许多员工和毕业生无法在一张纸上形成基本程序的逻辑。他们进而提出了一个编码测试,一个任何软件工程师都应该能够快速解决的非常简单的测试。这项测试被称为“嘶嘶嗡测试”,是基于一个同名的儿童游戏。基本原则是,你必须从 1 数到 100,对每个 3 的倍数说 Fizz,对每个 5 的倍数说 Buzz,最后对每个 3 和 5 的倍数说 FizzBuzz。听起来很简单;那是因为它是。毕竟,大多数编码只是构建小的逻辑块,并将它们附加到其他逻辑块上;将许许多多小的输入、计算和输出节点混合在一起,形成一个更大的计算系统,执行更大的输入、计算和输出过程。

为此,我们将从在 BASH 中编写我们自己的 FizzBuzz 问题解决方案开始,这样你们都可以通过基本的软件工程师能力测试!没错;我们将在 BASH 中编程。BASH 解释器提供的命令与常见的编程逻辑元素具有相同的功能。这些命令可以与普通的 shell 命令结合起来形成完整的程序。所以,让我们开始写吧(我们在第六章的中讨论了文本编辑器,所以你可以使用任何你喜欢的)。您所需要的就是能够编辑一个文件,然后在命令行上执行它。继续打开您最喜欢的文本编辑器,写下以下内容:

#!/bin/bash

这是任何想用 BASH 编写程序的人应该写的第一件事。这被称为 shebang,它是一个特殊的符号,当放在任何文件的第一行时,意味着它是我们要使用的脚本解释引擎的路径。在这种情况下,我们将使用 BASH 的解释器,它位于 Pi 和几乎所有其他 Linux 和 UNIX 操作系统的/bin/bash中。这将告诉外部命令提示符 shell,当它运行这个程序时,应该使用/bin/bash解释器来执行它。对于任何一种解释语言,你都应该在给定程序的开始部分包含某种形式的 shebang。

解释型与编译型

这很好地把我带到了下一个问题:解释语言与编译语言。出于这些目的,您不需要对编译语言有很深的了解,但知道它们的存在并了解解释语言和编译语言之间的一些差异总是好的。因此,尽管这两套语言都允许你编写自己的程序供计算机执行,但它们在这方面的方式是完全不同的。

解释语言(有时称为脚本语言)写下一系列编程命令,这些命令被提供给解释引擎(例如 shebang 提供的),解释引擎将推断它们的计算含义并执行程序想要的功能。这是一个相当于蛋糕食谱的纸笔,你写下制作蛋糕的确切方法,翻译为你烹饪并输出美味可口的蛋糕。

Note

这个食谱的性质也很像一个剧本,因为只要你有剧本并且能读懂它,你每次都会执行相同的动作。这就是解释语言通常被称为脚本语言的原因:它们的最终输出是一组文本,每次都可以用相同的方式创建编程输出。

编译语言采取了不同的方法。他们也有一个配方(称为源代码),但不是配方运行,而是它将被带到一个称为编译器的专门软件,该软件将获取配方并从中创建一个可执行包(称为二进制文件)。这个二进制包是用计算机自己的机器语言编写的,所以当执行时,它自己执行所有的动作。这相当于为一个专门的蛋糕制作系统编写计划,然后将它安装到您的系统中,并让它制作蛋糕。在这两种情况下,你都得到了蛋糕,但从你说开始的那一刻起,翻译的蛋糕可能需要更长一点的时间来制作。另一方面,创造专门的蛋糕制作机器需要更多的努力。现在你应该明白为什么我说像 C 这样的语言对于小问题来说是多余的了——C 是一种编译语言。

BASH 中的输出

回到我们的应用程序:如果你保存并运行你的程序,它不会为你做任何事情。所以首要任务应该是让你的程序输出一些东西。这是通过echo语句完成的,该语句也可以从命令提示符 shell 中获得;您使用echo让系统输出一组给定的文本。因此,让我们在您的脚本中添加一个输出,让它向世界上的每个人问好。修改您的脚本,使其看起来像这样:

#!/bin/bash

echo "Hello World!"

现在继续执行您的脚本;为此,您需要更改脚本的文件权限以允许它被执行。我的脚本名为fizzbuzz.sh,因此使其可执行并执行它所需的命令如下:

$ chmod +x fizzbuzz.sh

$ ./fizzbuzz.sh

该代码将生成以下输出:

Hello World!

恭喜你!您刚刚编写了世界上最简单的程序,Hello World 程序,这是大多数软件工程学生被要求创建的第一个程序。它展示了如何开始构建一个小软件,并完成任何软件程序的基本任务:它生成输出。在这种情况下,链是我们给它一个输入“请输出‘Hello World!’系统计算出这个值,然后给出我们想要的输出:Hello World!我用一对引号将Hello World!括起来。(这样做是为了让Hello World!行被当作一个字符串,而不是其他要执行的命令。)所有语言都需要区分什么是数据,什么是实际的编程逻辑。本例中的引号表明,引号内的所有内容都被视为一个文本字符串。

你可能会问,“那么如果我想输出一个引号呢?假设我想输出引号中的"Hello World!"。"没问题,我们会保护你的!所有的系统都有一个叫做逃逸的概念。转义是一种特殊的字符,它告诉解释器或编译器忽略下一个字符的特殊属性,只把它当作给定字符串的一部分。在大多数系统中,转义字符是反斜杠()。因此,如果我们想让我们的Hello World!有引号,我们将修改如下:

#!/bin/bash

echo "\"Hello World!\""

这表明我们想要转义那些内部的引号对,所以如果我们执行,我们应该看到如下内容:

$ ./fizzbuzz.sh

"Hello World!"

太棒了!如果您想在代码中输出一个反斜杠,您甚至可以进行转义。在编写软件时请记住这一点,因为这是人们最常犯的错误之一——代码中有未转义字符,这会导致软件的其他部分出现问题,因为字符串混淆了。

Note

大概最常见的转义字符是\n,是 newline 的简称。这可用于在下一行输出文本。

顺便说一下,这是具有语法突出显示的文本编辑器进入自己的领域的地方,因为它们在显示哪些字符是字符串的一部分,哪些不是字符串的一部分方面有很大的帮助。Vim 有语法高亮,nano 也有(但不是通过 SSH);不幸的是,默认的 GUI 编辑器 LeafPad 没有,但是有很多编辑器,你只需要找到一个适合你的。它们还有括号匹配、大括号匹配等其他功能,但现阶段对我们帮助不大。

概述

那么到目前为止我们学到了什么?我们已经介绍了如何使用 shebang 启动 BASH 应用程序,这样我们就可以选择合适的解释器。您学习了如何将基本输出打印到屏幕上,以及如何在字符串中使用转义符。这很好地把我们带到了列表的下一点:变量。

变量

变量是给定数据的抽象表示。你说什么变量是计算机存储给定信息的一种方式,以便可以再次检索。这是程序中大多数信息存储和操作的方式,因为大多数程序不像我们简单的Hello World!那样工作,它们接受各种形式的信息,并将其转化为其他信息。所以让我们离开第一个例子,回到编写 FizzBuzz 应用程序的问题上来。我们需要从 1 数到 100。

我们可以简单地写出每个数字,每行一个,然后对每个数字进行 FizzBuzz 计算,但这并不是编写这个程序的好的逻辑方式。我们需要的是一个可以包含“数”的抽象变量;然后,我们可以对这个“数字”运行 FizzBuzz 测试,然后我们将简单地将“数字”增加 1,冲洗并重复,直到数字 100 被测试。那我们就完了。

现在你应该对变量有所了解了。这是一种我们可以表示任何单一信息的方式。我们不一定要有具体的信息,因为变量就是——本质上的变量?然而,有一些变量的限制需要考虑。首先,在更广阔的编程世界中,定义了许多不同类型的变量,例如布尔型、整型、双精度整型、长整型、字符型、字符串型、浮点型、向量型、数组型等等。对于 BASH,只有几种变量类型,但是我们应该知道其他变量类型中最重要的,因为这在其他编程语言中也会起作用:

  • 首先是 integer,它是一个数值变量,只用来表示一个数字(在大多数情况下,只表示一个整数)。定义这种类型是为了方便使用数学和其他数字函数,如加、减、乘等。
  • 第二个是字符串,它是一种书写数据,代表一长串书写字符。字符串通常与用户输入和输出相关,因为由于文本字符串中包含的内容的高度可变性,在应用程序中处理它们太麻烦了。
  • 第三个是数组,这是一种特殊类型的变量,因为它是元变量,基本上是许多其他变量的载体。数组中的子变量称为元素,通过在数组变量的末尾添加一对方括号和一个与所讨论的数组元素相对应的数字来引用。数组不是从 1 开始的;相反,它们从 0 开始,因为 0 在逻辑上是计数序列中的第一个数字。所以如果你想从一个数组中获取第四个元素,你需要在数组变量的末尾加上[3]

现在你知道了什么是变量,我们可以开始形成解决 FizzBuzz 问题的基本程序逻辑了。

逻辑运算:如果

现在我们既有能力输出东西,也有能力存储东西,这涵盖了计算机程序输入和输出的第一部分和最后一部分。我们现在需要做的是对变量进行一些计算,这样我们就可以让程序做一些有意义的事情。这就是我们展开逻辑运算的地方。在编程中,有两个基本的逻辑运算你需要熟悉。这两个逻辑操作构成了在任何地方进行的大多数编程的基础。他们这样做是因为他们允许人们执行测试,并根据测试的结果采取不同的行动。

这些语句中的第一个是if语句。一个if语句的执行就像它的名字所暗示的那样——如果某事为真,就执行一个动作。正是if语句的测试性质为它提供了所有的力量,因为当与变量结合使用时,我们可以测试任何我们想要的东西。所以看看 FizzBuzz 的例子,我们知道我们有初始变量“number ”,它代表 1 到 100 之间的计数。我们需要对“number”进行测试,看看它是能被 3 整除、能被 5 整除,还是能被 3 和 5 整除。如果它匹配这些条件中的一个,我们需要输出正确的单词。

所以现在你需要理解一个if语句是如何写入程序的。在 BASH 中,基本语法如下:

if [ <TEST> ]; then

DO SOMETHING

fi

前面是一个简单的 BASH if语句,实际上并没有那么复杂。你会注意到,随着if和测试和做某事,还有一个fifi表示要执行的事情的结束,因为您可以在一个if语句中执行多个动作。所以,现在我们有了我们的if语句,所以我们需要让它做事情。所以我们来快速看一下一些数学运算。

Note

记得左方括号两边各留一个空格,右方括号前留一个空格,分号后留一个空格;否则,您将会得到一个语法错误。

基于测试的算法

我们知道我们需要有一个相等的测试,那么有人如何确认任何一个给定的数是否能被另一个给定的数整除呢?简单的答案是检查当除以一个给定的数时,结果是一个没有任何“余数”的“整数”事实上,这是一个听起来很难编程的概念。尽管乘法、加法、减法甚至除法都会给出有限的可检验的答案,但检查一个数是否能被另一个数整除则需要不同的运算。谢天谢地,有一个专门为这个问题设计的数学运算:模数。模数返回任意两个数相除的余数,用符号%表示。所以模数给你这样的结果:

12 % 3 = 0

12 % 5 = 2

突然我们的测试出现了:如果数模 3 等于 0,我们输出 Fizz,如果数模 5 等于 0,我们输出 Buzz,如果数模 3 和 5 等于 0,我们输出 FizzBuzz。因此,如果我们将所有这些结合起来,我们会得到以下结果:

if [ number % 3 = 0 ]; then

echo "Fizz"

fi

if [ number % 5 = 0 ]; then

echo "Buzz"

fi

if [ number % 3 = 0 ]; then

if [ number % 5 = 0 ]; then

echo "FizzBuzz"

fi

fi

好吧,这是一个看起来不错的尝试,但是为了使 BASH 成为有效的可执行 BASH,我们仍然需要解决一些与 BASH 相关的问题。

让我们从简单的变量开始。在 BASH 中,变量有两种操作模式。首先是赋值模式,当我们创建一个新变量并给它赋值时就会出现。在这些实例中,变量的工作方式与前面的数字一样,所以我们可以这样将number变量创建为 1:

number=1

这很好,看起来就像我们的例子,但是当我们想要使用number变量的当前值时,我们需要访问该变量,这是在解引用模式下完成的。在 BASH 中,这意味着在我们想要使用值的每个变量前面添加一个美元符号($)。

概括地说,变量有两种模式:一种是加载变量(称为赋值),另一种是从变量中取出值(称为解引用)。在第一种模式中,我们按原样使用变量;在第二个例子中,我们在变量前面使用了一个$。好的,没问题。让我们更新代码:

if [ $number % 3 = 0 ]; then

echo "Fizz"

fi

if [ $number % 5 = 0 ]; then

echo "Buzz"

fi

if [ $number % 3 = 0 ]; then

if [ $number % 5 = 0 ]; then

echo "FizzBuzz"

fi

fi

所以我们的代码看起来稍微好一点,但是你可能已经注意到了当我们给一个数字赋值 1 时发生的下一个问题。等号(=)用于给变量赋值,因此在这种情况下,我们似乎试图给变量 5 赋值 0,这将导致if中的测试出现各种问题。大多数语言都使用一对等号(==)来表示对等式的测试,但是在 BASH 中,我们有许多特殊的算术测试操作符来测试这一点:

  • -eq为了平等
  • -ne为不相等
  • -gt对于大于
  • -lt因少于那个
  • -ge对于大于或等于
  • -le小于或等于

我们可以在代码中利用这些操作符来比较模数和 0 的结果。因此,再次对您的代码进行这些更改,您应该得到以下内容:

if [ $number % 3 -eq 0 ]; then

echo "Fizz"

fi

if [ $number % 5 -eq 0 ]; then

echo "Buzz"

fi

if [ $number % 3 -eq 0 ]; then

if [ $number % 5 -eq 0 ]; then

echo "FizzBuzz"

fi

fi

最后,当进行算术运算(比如模数)时,我们需要告诉 BASH 这意味着是算术运算。为此,我们使用一个$和一对括号将实际的算术位括起来,因此5 % 3的操作意味着我们需要$((5 % 4)),我们可以进行更改:

if [ $(($number % 3)) -eq 0 ]; then

echo "Fizz"

fi

if [ $(($number % 5)) -eq 0 ]; then

echo "Buzz"

fi

if [ $(($number % 3)) -eq 0 ]; then

if [ $(($number % 5)) -eq 0 ]; then

echo "FizzBuzz"

fi

fi

你有它;您已经为 FizzBuzz 应用程序编写了三个简单的测试;并使用了if语句、变量、打印和特殊算术运算符。这有很多代码,所以让我们测试一下,确保我们现在得到的能够工作!

我们现在可以通过创建一个只有一个值的number变量来做这个简单的测试。让我们把它指定为 15,这样我们可以保证一些输出。添加了number变量后,您的脚本应该如下所示:

#!/bin/bash

number=15

if [ $(($number % 3)) -eq 0 ]; then

echo "Fizz"

fi

if [ $(($number % 5)) -eq 0 ]; then

echo "Buzz"

fi

if [ $(($number % 3)) -eq 0 ]; then

if [ $(($number % 5)) -eq 0 ]; then

echo "FizzBuzz"

fi

fi

执行时,输出如下:

$ ./fizzbuzz.sh

Fizz

Buzz

FizzBuzz

解决纷争

哦亲爱的。这种输出是一个问题。我们同时说出了这三个词,而不仅仅是当数字被 3 和 5 整除时发出的嘶嘶的声音。我们刚刚失败了!啊!好的,深呼吸几次;这不是世界末日。事实上,这是一个引入新特性的好时机。当测试为真时,if语句会做一些事情,但是我们还可以添加另一部分:elseelseif的另一面,逻辑是这样的:如果测试为真,就做一些事情,否则就做另一件事情。我们可以结合使用这两种方法来测试 FizzBuzz。一个ifelse语句的语法如下:

if [ TEST ]; then

DO SOMETHING

else

DO A DIFFERENT THING

fi

语法几乎与原始的if语句完全相同,因此很容易进行一些修改,使您的if语句变成ifelse语句。但是现在我们遇到了更大的问题——这也是大多数人在编程时遇到问题的原因;这是逻辑顺序流程。我们需要创建一个测试序列,以便我们可以确定某个东西是被 3 整除、被 5 整除还是被两者整除;并且在每种情况下执行完全不同的动作。这种想法使得编程对许多人来说如此困难,而且没有诀窍:你只需要简单地解决它。所以让我们一起来解决这个问题。

如果我们找到能被 3 或 5 整除的东西,并在检查它是否能被这两者整除之前输出它,我们会遇到输出嘶嘶声、嗡嗡声和嘶嘶声的可能性,这不是我们想要的。让我们先来测试一下这两者:

if [ $(($number % 3)) -eq 0 ]; then

if [ $(($number % 5)) -eq 0 ]; then

echo "FizzBuzz"

fi

fi

好了,现在我们有了 FizzBuzz,但是如果答案能被 3 整除却不能被 5 整除呢(Fizz 就是这种情况)?然后我们可以输出气泡。因此,如果我们在被 5 整除的测试中添加一个else,我们将保证得到一个被 3 整除而不是被 5 整除的数。这意味着我们避免了同时输出 Fizz 和 FizzBuzz 的问题。我们可以在这里使用新的else语句来实现这一点,所以当我们将else添加到可被 5 整除的测试中时,我们得到这样的结果:

if [ $(($number % 3)) -eq 0 ]; then

if [ $(($number % 5)) -eq 0 ]; then

echo "FizzBuzz"

else

echo "Fizz"

fi

fi

太棒了。现在我们只剩下一种情况:当一个数不能被 3 整除,但能被 5 整除。我们可以在另一个else语句中做这个测试。通过将此作为else语句的一部分,并添加第二个可被 5 整除的测试,我们可以确保当一个数字可被 5 整除时,当我们输出它时,它还没有被 3 整除。这意味着我们避免了以前遇到的问题,所以添加这个测试,您的代码变成如下所示:

if [ $(($number % 3)) -eq 0 ]; then

if [ $(($number % 5)) -eq 0 ]; then

echo "FizzBuzz"

else

echo "Fizz"

fi

else

if [ $(($number % 5)) -eq 0 ]; then

echo "Buzz"

fi

fi

这应该可以了!我们要进行一系列新的测试。继续替换脚本中的旧语句,并运行这个新语句:

$ ./fizzbuzz.sh

FizzBuzz

完美!这是 15 的正确结果!您可以更改number的值,再执行几次来进行测试,但是这个测试应该经得起考验,因为我们已经仔细考虑了我们的编程。现在我们需要计算从 1 到 100 的数字。为此,我们将使用第二个逻辑运算!

逻辑运算:循环

循环是一种特殊类型的逻辑操作,其核心功能类似于if语句,但如果语句为真,它将一遍又一遍地运行语句,而不是运行代码,只要语句为真。这就是我们如何将数字的值一次又一次地增加 1,一直数到 100。BASH 中的循环使用以下语法:

while [ TEST ]; do

DO SOMETHING

done

从设计上来说,循环与if语句非常相似。两者都在测试某个条件是否为真,然后都将执行一段代码。不同之处在于,if语句将只执行一次,而while循环将一直执行到某个条件不再为真。这给 be 带来了关于循环的第一个也是最大的警告:如果你忘了为你的循环设置一个离开条件,那么你将永远不会离开它。这就是所谓的无限循环,在这个循环中,你的程序会被卡住,什么都不做,永远运行下去。在少数情况下,这是可取的,但并不多。幸运的是,我们有 Ctrl+C 快捷方式,它将发送一个终止信号给我们正在运行的程序,我们可以用它来使程序脱离无限循环。

你可能会想,“哦,这就是为什么这么多程序被卡住了!”这是 100%真实的。有时你可能没有考虑到的情况会出现,你的程序可能会永远循环运行,所以你要小心了。

对于我们的 FizzBuzz 程序,我们想从 1 数到 100,所以我们可以对小于或等于 100 的数字进行简单的测试。因此,对于我们的数字小于或等于 100 的所有情况,我们将执行循环。因此,让我们继续围绕现有的测试块编写循环。完成后,它应该如下所示。先不要执行它,因为我们没有办法将数字从 1 增加到 100(这意味着我们会得到一个无限循环,因为我们永远不可能达到 100 并离开循环)。

#!/bin/bash

number=1

while [ $number –le 100 ]; do

if [ $(($number % 3)) -eq 0 ]; then

if [ $(($number % 5)) -eq 0 ]; then

echo "FizzBuzz"

else

echo "Fizz"

fi

else

if [ $(($number % 5)) -eq 0 ]; then

echo "Buzz"

fi

fi

done

好了,我们有了代码块,现在我们需要添加递增的数字。我们只需要在每次循环时将number的值增加 1。但是我们也想在改变数字之前执行所有的计算。我们可以通过使用已经有的算术运算和赋值运算来实现这一点。我们可以将这个代码块添加到代码底部的done语句之前:

number=$(($number +1))

一旦你做了这个改变,执行你的脚本!它将运行并给出如下输出:

Fizz

Buzz

Fizz

Fizz

Buzz

Fizz

FizzBuzz

...

这很棒,看起来它工作了,但是我们应该继续下去,为循环中的每一次旅行打印当前的值number。我们可以在 Fizz 或 Buzz 或 FizzBuzz 之前输出开始时的值,但这意味着每行都有数字。如果我们能把它整合到我们的echo声明中就更好了。为此,我们只需在要打印的文本字符串中添加$number变量。您之前已经了解到,引号意味着所有内容都被视为一个文本字符串,但是有一个标记可以取代它:符号操作符$。当您想要从文本块中的变量打印出数据时,这个操作符可以使您的工作变得更容易。

我们还应该添加一个最终的else语句,它将负责所有“其他”数字的输出。试着自己解决这个问题;然后对照以下代码进行检查:

#!/bin/bash

number=1

while [ $number -le 100 ]; do

if [ $(($number % 3)) -eq 0 ]; then

if [ $(($number % 5)) -eq 0 ]; then

echo "$number - FizzBuzz"

else

echo "$number - Fizz"

fi

else

if [ $(($number % 5)) -eq 0 ]; then

echo "$number - Buzz"

else

echo $number

fi

fi

number=$(($number +1))

done

这就是了!可行的 FizzBuzz 解决方案。恭喜你!

解决纷争

你的代码有问题可能有点像噩梦。也许您可以使用的最好的工具是echo命令。您可以输出任何您想要的东西,所以如果您不确定您的应用程序在代码中的位置,编写一个echo来输出内容,然后检查。是不是没有正确进入循环?输出进入测试的变量并手动比较。不确定为什么if声明不起作用?添加一个else,输出完整的测试用例,看看你进去的是否正确。使用这些输出来跟踪您在代码中的位置是进行诊断的最佳方式。否则,注意任何错误;大多数应该提供一个行号,并告诉你(在某种程度上)哪一行有什么问题。如果你有疑问,请随意搜索,因为几乎可以肯定的是,在你之前,某个地方的某个人也犯过同样的错误,并寻求解决方案。

实用 BASH:一个 Init 脚本

您现在应该知道,大多数 Linux 应用程序都是由一个叫做init脚本的特殊脚本启动的。这些脚本并不神奇;它们只是 BASH 中的脚本,接受给定的值,并根据命令执行一系列动作——就像任何好的程序一样。init脚本中确实有少量特殊信息,但这些实际上都不是程序逻辑。

让我们一起浏览一遍,并检查这些init脚本中的一个是如何工作的。以下是XBMCinit脚本,你将在本书后面再次看到:

#! /bin/bash

### BEGIN INIT INFO

# Provides:          xbmc

# Required-Start:    $all

# Required-Stop:     $all

# Default-Start:     2 3 4 5

# Default-Stop:      0 1 6

# Short-Description: Start XBMC

# Description:       Start XBMC

### END INIT INFO

DAEMON=/usr/bin/xinit

DAEMON_OPTS="/usr/lib/xbmc/xbmc.bin"

NAME=xbmc

DESC=XBMC

RUN_AS=root

PID_FILE=/var/run/xbmc.pid

test -x $DAEMON || exit 0

set -e

case "$1" in

start)

echo "Starting $DESC"

start-stop-daemon ˗˗start -c $RUN_AS ˗˗background ˗˗pidfile $PID_FILE ˗˗make-pidfile ˗˗exec $DAEMON ˗˗ $DAEMON_OPTS

;;

stop)

echo "Stopping $DESC"

start-stop-daemon ˗˗stop ˗˗pidfile $PID_FILE

;;

restart|force-reload)

echo "Restarting $DESC"

start-stop-daemon ˗˗stop ˗˗pidfile $PID_FILE

sleep 5

start-stop-daemon ˗˗start -c $RUN_AS ˗˗background ˗˗pidfile $PID_FILE ˗˗make-pidfile ˗˗exec $DAEMON ˗˗ $DAEMON_OPTS

;;

*)

echo "Usage: /etc/init.d/$NAME{start|stop|restart|force-reload}" >&2

exit 1

;;

esac

exit 0

您会注意到的第一件事是有许多行是以#开头的。这几行是注释;因为#是 BASH 中的注释符号,所以任何以#开头的行都不会作为应用程序的一部分执行。事实上,INIT INFO的开头几行对于一个init剧本来说非常重要。这些是特殊的注释,可以被处理以显示由脚本控制的应用程序是如何运行的。这些积木看起来像这样:

### BEGIN INIT INFO

# Provides:          xbmc

# Required-Start:    $all

# Required-Stop:     $all

# Default-Start:     2 3 4 5

# Default-Stop:      0 1 6

# Short-Description: Start XBMC

# Description:       Start XBMC

### END INIT INFO

这些块提供了一些描述,并说明它们为哪个应用提供了功能;在这种情况下,提供的应用是XBMC。他们还提到在这个应用程序可以启动或停止之前需要运行什么应用程序。$all符号表示该应用程序将最后启动,这样可以保证它所依赖的所有其他应用程序都在它之前启动。可能更重要的一组操作符是Default-StartDefault-Stop。这些操作符与 Linux 系统的运行级别相关联,管理 Linux 系统引导过程的各个阶段。

Linux 规范定义了以下运行级别:

  • 0 级:Halt 关机和断电
  • 级别 1:单用户模式;仅基本系统功能(用于维修)
  • 级别 2 % 3:多用户;添加网络和多用户功能
  • 级别 4:用户定义
  • 5 级:正常系统运行状态
  • 第 6 级:重启

启动和停止开关旁边的数字与这些级别相关,因此XBMC将在运行级别 2-5 启动,并在级别 0、1 和 6 关闭。

选择并搭配案例陈述

一旦所有初始化完成,脚本设置了一些初始变量,它执行一些快速测试,然后继续执行case语句。一条case语句非常类似于针对同一个变量的一系列运行的if语句。只要你有一个给定的变量,对于这个变量的一系列给定值中的每一个,你就执行了一个函数。在大多数init脚本中,这用于确认它要执行哪个动作。

这个start脚本中的case语句正在处理特殊的$1变量。此变量表示从命令行传递给脚本的第一件事;因此,当尝试启动xbmc应用程序时,您将执行以下命令:

/etc/init.d/xbmc start

这给了$1变量start的值。case语句为case语句的潜在价值提供了许多不同的选项:

  • start:不言自明。
  • stop:不言自明。
  • restart|force-reload:restartforce-reload的值,在一个过程中执行停止申请,然后执行启动申请。它们之间的管道(|)用于表示 OR 操作,这意味着如果这里的任何一个值匹配,我们就认为这是一个匹配。
  • 还有别的吗?

在每个case值之后,有一小段在这种情况下执行的代码,后面跟着一对分号。这些分号就像一个fi,用来表示一个case语句代码块的结束。

应用程序中的应用程序:分叉

一旦进入case语句,您可以看到每一行都将使用start-stop-daemon执行一些操作。但这不是一个变量或任何特殊的东西;这是另一个程序。BASH 最强大的部分是我们还没有接触到的部分。BASH 能够通过使用程序名在程序中执行任何命令行过程。因为 BASH 是我们在其余大部分时间使用的 shell,所以这实际上很有意义。

一个init脚本的最后一个功能:它将使用start-stop-daemon为您创建一个新的进程。这个动作被称为分叉,因为你正在从当前运行的应用程序中分叉一些东西。start-stop-daemon是一个小应用程序,用于派生进程并为你管理它们的运行。start脚本收集它的参数,计算出需要执行什么动作,然后告诉start-stop-daemon需要对哪个应用程序采取什么动作。

现在您已经掌握了这些知识,您应该能够编写自己的简单的init脚本来启动和停止任何您想要的进程,方法是提供开始注释块来描述脚本应该如何加载,使用case语句来确定要采取的操作,最后执行start-stop-daemon进程来启动和管理您的应用程序!

更新运行文件

现在,您已经准备好了一个完整的工作脚本。你需要使用chmod在你的文件上设置可执行标志,然后你可以用startstop命令测试它。它确实如预期的那样——太棒了!现在我们需要将它添加到系统的引导逻辑中。从历史上看,这涉及到以一种特殊的方式将文件链接到不同的运行级别。然而,有了update-rc.d应用程序,这变得容易多了。update-rc.d应用程序利用了我们添加到 BASH 脚本中的特殊注释头,以便知道哪个运行级别应该有哪个快捷方式。

要将init脚本添加到引导序列中,我们需要做的就是发出带有我们想要的init脚本名称的update-rc.d命令,在本例中是xbmc。然后我们需要添加参数,说明我们应该使用脚本中注释的默认值;这个论点不出所料defaults。这给了我们命令update-rc.d xbmc defaults,它需要以 root 用户身份执行。运行时,输出应该如下所示:

$ sudo update-rc.d xbmc defaults

update-rc.d: using dependency based boot sequencing

仅此而已;如果您重新启动,您的应用程序应该在启动时运行!

创建您自己的初始化脚本

我们已经讲述了如何创建init脚本;现在我们可以创建一个。这个过程相对简单,因为我们只需要重新创建结构,并在正确的位置输入我们自己的代码来执行我们需要的功能。你可以让这些init脚本做任何你想做的事情,但是对于这个例子,我将用touch命令创建一个文件,然后在完成后用rm删除它。touch命令用于在磁盘上创建一个空文件;如果它在现有文件上运行,它将更改文件的上次修改时间。如前所述,你可以让你的程序由start-stop-daemon管理,或者你可以简单地执行系统功能,就像我在我的例子中将要做的那样。

正如您所记得的,我们在这个脚本中首先需要的是 shebang,它是所有 BASH 脚本的起点,因此也是最好的起点。

#! /bin/bash

现在我们有了自己的脚本,回想一下我们之前使用的XBMC start 脚本:该脚本中的第一件事是开始注释块,它为我们提供了关于哪些init级别将运行该脚本的细节,等等。所以接下来让我们补充一下。你应该在这里填写你自己的所有细节。我的脚本将被称为touchfile.sh,并将提供触摸文件服务。我的脚本的开始部分将如下所示:

### BEGIN INIT INFO

# Provides:          touchfile

# Required-Start:    $all

# Required-Stop:     $all

# Default-Start:     2 3 4 5

# Default-Stop:      0 1 6

# Short-Description: Run TouchFile

# Description:       Run TouchFile

### END INIT INFO

现在我已经定义了注释块,我可以定义我的变量了。它们将是覆盖我的应用程序的变量。任何你能想到的启动它所需要的可以配置的东西都应该被定义为一个变量。在我的touchfile命令中,我需要定义为变量的东西是我的touchfile的文件名。请记住,这是您的代码,因此您可以将您想要的任何内容定义为变量。我的变量是这个:

TOUCHFILE="/var/tmp/touch.file"

既然已经定义了我的touchfile,我需要添加case语句来覆盖可用的动作。在这种情况下,我希望有以下操作:

  • Start:触摸文件
  • Stop:删除被触摸的文件
  • Restart:移除然后重新触摸文件
  • Reload:触摸文件
  • 告诉人们如何使用这个文件

这意味着我的案例中有四个case陈述。我将把case语句(执行动作的 sans 代码)定义如下:

case "$1" in

start)

;;

stop)

;;

restart)

;;

reload)

;;

*)

;;

esac

exit 0

现在我们已经有了case条目,我们只需要让它们做一些事情。我之前提到了在每种情况下我想要做的事情,所以让我们把命令写到每个块中,并把它们组合到我们已经有的脚本中。这将为我们提供以下脚本:

#!/bin/bash

### BEGIN INIT INFO

# Provides:          touchfile

# Required-Start:    $all

# Required-Stop:     $all

# Default-Start:     2 3 4 5

# Default-Stop:      0 1 6

# Short-Description: Run TouchFile

# Description:       Run TouchFile

### END INIT INFO

TOUCHFILE="/var/tmp/touch.file"

case "$1" in

start)

echo "Creating $TOUCHFILE"

touch $TOUCHFILE

;;

stop)

echo "Removing $TOUCHFILE"

rm $TOUCHFILE

;;

restart)

echo "Recreating $TOUCHFILE"

rm $TOUCHFILE

touch $TOUCHFILE

;;

reload)

echo "Re-Touching $TOUCHFILE"

touch $TOUCHFILE

;;

*)

echo " Usage: touchfile.sh <start|stop|restart|reload>"

;;

esac

exit 0

就这样。代码是一个简单的init脚本,它接受startstoprestartreload命令,输出它正在做的事情,并根据我们提供的参数执行动作。这可以放在/etc/init.d中,并且可以用chmod +x设置为可执行。一旦完成,您就可以测试脚本以确保它能够工作。最后,您可以像我们之前所做的那样,使用update-rc.d将它添加到您的系统引导过程中。通过这种方式,您可以编写自己的启动脚本来运行您设计的应用程序。

安全性和用户管理

安全性是系统管理中最容易被忽视的领域之一。因为它被许多人认为是一种黑色艺术,所以经常被忽视。

拉斯比安的安全规则

有一些非常简单的方法可以让你的系统安全;只要遵循几条规则:

  • 不要以 root 用户身份登录,但如果必须登录,请在登录后注销。
  • 对于管理任务,尽可能使用sudo
  • 选择一个重要的密码——一个足够长且足够复杂的密码,如果不花很长时间,它是不容易被猜到或用手算出的。
  • 定期更改您的密码。
  • 定期查看您的系统日志,特别是/var/log/auth.log,因为它列出了您系统的所有用户认证。
  • 如果您不需要运行应用程序,就不要运行它。
  • 尽可能少地将您的系统暴露在互联网上。
  • 尽可能限制文件权限。

这些规则中的大部分看起来像是常识,因为它们确实如此。然而,大多数人似乎陷入了认为他们不会受到攻击的陷阱,并忽视了大多数建议。现在你知道了这些规则,你可能会说,“只要你告诉我怎么做,我就会改变我的密码!”从 Linux 命令行更改密码相对容易:您只需使用passwd命令,系统会提示您输入新密码(两次,这样您就不会在第一次不小心拼错了)。

您还可以作为 root 用户更改其他用户的密码。为此,只需将您要更改的用户名作为命令的第一个参数。这在配置新用户详细信息或重置密码时非常有用。

添加新用户

因为您不应该一直使用 root,所以能够创建新用户并理解用户创建是如何发生的非常重要。在 Linux 操作系统中,用户由文件/etc/passwd管理。您可以自己看一下,您会看到类似这样的几行:

root:x:0:0:root:/root:/bin/bash

daemon:x:1:1:daemon:/usr/sbin:/bin/sh

这些行显示用户,X 表示密码,用户的 ID 号,用户的组 ID 号,用户的组标识符,用户主目录的路径,以及用户的 shell。这些是用户在 Linux 环境中工作所需要的基础知识。

虽然该文件有一个密码字段,但它不再被使用,通常包含一个占位符(在本例中为X)。密码存储在影子文件/etc/shadow中。影子文件被分离,以便只有 root 用户可以访问密码数据,所有更常见的用户数据都可以被其他更通用的系统应用程序读取。

现在您已经了解了 Linux 是如何存储用户的,添加一个名为raspberry的新用户。要添加这个用户,我们将使用useradd命令和一些参数。还有另一个命令adduser,它执行用户添加,但是它不会提供参数,而是提示您输入细节。当你忘记了useradd的一个必要参数时,这个命令非常有用。

首先我们想要指定用户的主目录,这是通过–d标志和我们想要使用的目录的完整路径来完成的。大部分用户的主目录都是/home/<username>,树莓我们也会这么做。目前,/home/raspberry还不存在,但是在你冲出去添加它之前,我们可以用–m标志告诉useradd帮我们添加!这给了我们这个命令:

$ sudo useradd –d /home/raspberry –m raspberry

我们可以更进一步,甚至用–g <groupname>指定组,为用户设置主要组。或者甚至用–s <shell path>指定用户将使用的 shell,或者最后用–p <password>指定密码。一旦您执行了您的命令,您就可以去检查对/etc/passwd的更改,在您的文件底部您应该会看到类似这样的内容:

raspberry:x:1001:1001::/home/raspberry:/bin/sh

就是这样;您现在已经成功添加了一个新用户!如果您没有指定密码,您可以使用我们前面提到的passwd命令来更改您的新用户的密码,然后您就可以开始了。然而,假设您忘记了用–s设置 shell 路径,并且您想改变它,所以您从/bin/bash而不是/bin/sh开始。嗯,首先你可以编辑passwd文件来改变 Shell,或者你可以使用usermod命令。usermod命令的功能与useradd命令完全相同,包括参数;它只是调整数值。因此,运行以下命令:

$ sudo usermod -s /bin/bash raspberry

我们可以预期影子文件将被更改,是的,它是:

raspberry:x:1001:1001::/home/raspberry:/bin/bash

太棒了!有了这些工具,您应该能够创建新的用户帐户来允许人们访问您的 Pi。

摘要

这一章涵盖了相当多的内容。我们介绍了 SSH、DHCP 和 DNS。我们讲述了这些系统如何工作以及守护进程如何通过init脚本启动的一些基础知识。然后我们深入学习 BASH,这样我们就可以编写我们自己的init脚本。最后,我们讨论了一些常见的安全注意事项;然后继续讲述 Linux 系统如何管理用户及其密码的基础知识。我们甚至创建了一个全新的用户供使用。

这些技能应该使您能够管理您的 Pi 的启动和联网,并且应该已经向您介绍了软件开发的奇妙世界!

八、一盏你自己的灯

很多来到 Linux 的人都想知道的一个关键问题是,如何通过建立一个自己的小网站来建立自己。许多人很难找到开始的地方。哪些操作系统、哪些应用程序、哪些硬件,以及具体如何做到这一切?所有这些都是合理的问题,因为更大的 Linux 环境有如此多的方面,以至于很容易迷失。

我们的目标是为您提供能够在 Linux 环境中导航的第一批垫脚石。这条道路上的一个关键步骤是向您展示如何安装、配置和维护 web 服务器。虽然您可以将这个服务器连接到 Internet,但是在这些例子中,我们将只介绍如何将您的 Pi 用作本地网络服务器(换句话说,用于运行内部网)。此外,还有一个额外的挑战就是学习如何制作一个基本的交互式网站。虽然这可能看起来很难接受,并且其他人花费了整本书和生活来寻找这个问题的绝对最优的解决方案,我们的目标是在一章中涵盖它。

为此,我们将使用一盏灯。不,不是那些卧室发光设备中的一个,而是一个 Linux,Apache,MySQL 和 PHP 系统,旨在使传递网络内容变得容易。这是创建您自己的 web 服务器的最简单的方法之一,在最初的 LAMP 上有许多变体,包括但不限于以下内容:

  • WAMP (Windows、Apache、MySQL、PHP)
  • MAMP (Mac OS、Apache、MySQL、PHP)
  • 用 Python 或 Perl 替换 Ps
  • 用 MariaDB 替换 M

因此,从人们所做的大量混合、匹配和改进中可以看出,这是一个让强大的 web 服务器运行起来的好方法。最重要的是,它设置简单,可以在任何硬件上运行。

Pi 最常被引用的潜在用途之一是支持构建、运行和维护网站的能力。还有什么比教他们如何用最小的成本制作自己的网站更能激励一代年轻开发者的呢!

第一步

现在是时候面对现实了,我们可以在这里完成什么。在这一章中,我们将向你提供建立一个完整的网站堆栈的技巧,并向你展示如何使用 PHP 将 MySQL 数据库的功能集成到一个网站中,以动态生成网站内容。然而,有几件事我们不能在这里为你做:

  • 设置和配置 DNS 或域名
  • 高级 SQL 和数据库管理
  • HTML 和 HTML 开发方法的全面覆盖
  • 全面覆盖 PHP 开发和 PHP 开发方法
  • Java Script 语言
  • 半铸钢ˌ钢性铸铁(Cast Semi-Steel)

你可以利用许多资源来获得这些其他领域的帮助,包括其他重要的文章。我们还有工作要做,所以让我们开始吧。

l 代表 Linux

设置灯组只有几个先决条件。你需要

  • 安装并配置 Raspbian 操作系统(LAMP 的 L)
  • 将内存分割设置为 240/16(运行sudo raspi-config)
  • 熟悉使用命令行在 Raspbian 操作系统中工作
  • 对如何编写简单的软件有一个基本的了解
  • 让您的 Pi 连接到网络和互联网(否则我们如何提供内容?)

树莓灯只有几个其他的附带条件。我们将在 shell 中完成所有这些工作。鉴于网站将从 Pi 之外被查看,没有必要使用任何额外的资源来运行 GUI。既然我们知道我们需要做什么,那就开始吧。

a 代表阿帕奇

可能任何 web 服务器最重要的部分就是 web 服务器应用程序。web 服务器是一个应用程序,它接受对网站的请求,然后返回所请求的网页内容。大多数 web 服务器还可以提供大量其他功能,这些功能有助于增强它们向最终用户提供 web 内容的能力。对于我们的 LAMP 堆栈,我们将使用 Apache web 服务器。

许多人认为 Apache 是世界上首屈一指的 web 服务器。它于 1995 年首次发布,并因向大众提供简单、强大、免费的 Web 服务器而在万维网的发展中发挥了作用而闻名。

作为这一事实的佐证,据估计,Apache 提供了世界上大约 54%到 58%的网站。

关于 Apache 的命名有两个故事:

  • 第一个原因是,多年前首次开发时,它被命名为“一个拼凑的 web 服务器”。
  • 第二个(从讲故事的角度来看更好)是,据说阿帕奇是以美国的阿帕奇部落命名的。

以一个部落命名并不是用词不当,因为表现得像一个部落是 Apache 的关键特性之一。Apache 启动时的第一个任务是创建自己的“工人”小部落,负责网站内容的实际服务。既然您对 Apache 有了更清晰的理解,那么第一步就是安装、启动并运行 Apache。

对于我们的安装,我们将(一如既往地)依赖始终如一的apt-get工具:

$ sudo apt-get install apache2

Reading package lists... Done

Building dependency tree

Reading state information... Done

The following extra packages will be installed:

apache2-mpm-worker apache2-utils apache2.2-bin apache2.2-common libapr1

libaprutil1 libaprutil1-dbd-sqlite3 libaprutil1-ldap ssl-cert

Suggested packages:

apache2-doc apache2-suexec apache2-suexec-custom openssl-blacklist

The following NEW packages will be installed:

apache2 apache2-mpm-worker apache2-utils apache2.2-bin apache2.2-common

libapr1 libaprutil1 libaprutil1-dbd-sqlite3 libaprutil1-ldap ssl-cert

0 upgraded, 10 newly installed, 0 to remove and 71 not upgraded.

Need to get 1,348 kB of archives.

After this operation, 4,990 kB of additional disk space will be used.

Do you want to continue [Y/n]?

现在您已经安装了 Apache,我们应该去验证它是否已经启动并运行。有几种方法可以做到这一点。首先,运行以下命令:

$ ps -ef | grep apache

root      2306     1  0 Sep17 ?        00:00:09 /usr/sbin/apache2 -k start

www-data  2309  2306  0 Sep17 ?        00:00:00 /usr/sbin/apache2 -k start

www-data  2311  2306  0 Sep17 ?        00:00:00 /usr/sbin/apache2 -k start

www-data  2315  2306  0 Sep17 ?        00:00:00 /usr/sbin/apache2 -k start

从这个输出中,您可以看到 Apache 确实已经启动并运行了。从这一点来看,还有几件重要的事情需要注意。下面一行列出的第一个过程不同于其他过程:

root 2306 1 0 Sep17 ? 00:00:09 /usr/sbin/apache2 -k start

这就是前面提到的阿帕奇部落的酋长。它归 root 所有(如第一列所示),其进程 ID (PID)是 2306。另外三个由名为www-data的用户拥有,这是一个专门针对 Apache 的用户。虽然每个工作进程都有不同的 PID,但是它们都有一个 PPID(父进程 ID)2306,这意味着 Apache 主进程创建了它们(其 PID 是 2306)。

现在是第二个更有趣的测试。在这个测试中,我们实际上会让 Apache 显示它的默认起始网页!为此,您可以登录 GUI 或者使用ifconfig获取系统的 IP 地址。您的输出应该如下所示,并突出显示相关的 IP 地址:

$ ifconfig

eth0      Link encap:Ethernet  HWaddr b8:27:eb:8a:46:ba

inet addr:``10.0.0.20

UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1

RX packets:213812 errors:0 dropped:0 overruns:0 frame:0

TX packets:5119 errors:0 dropped:0 overruns:0 carrier:0

collisions:0 txqueuelen:1000

RX bytes:19226371 (18.3 MiB)  TX bytes:495394 (483.7 KiB)

lo        Link encap:Local Loopback

inet addr:``127.0.0.1

UP LOOPBACK RUNNING  MTU:16436  Metric:1

RX packets:8 errors:0 dropped:0 overruns:0 frame:0

TX packets:8 errors:0 dropped:0 overruns:0 carrier:0

collisions:0 txqueuelen:0

RX bytes:1104 (1.0 KiB)  TX bytes:1104 (1.0 KiB)

假设您的系统(像我的一样)连接到网络,您可以使用第一个 IP 地址(10.0.0.20)。eth0块中的地址代表其网络端口。lo块中的地址为环回地址,用于内部自参考,始终为 127.0.0.1。

下一个测试是访问 Apache 默认网页;为此,您只需在浏览器中输入这些 IP 地址中的一个(当然,假设您与 Pi 在同一个网络上!).如果您想使用 Pi 的板载浏览器进行测试,那也完全没问题,您可以选择任何一个地址(10.0.0.20 或 127.0.0.1)。你还在等什么?打开浏览器窗口,查看默认网页,如图 8-1 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-1。

It works!

正如你现在应该知道的,它起作用了!这是 Apache web 服务器的默认网页,因为它表明还没有添加任何内容,我们将在后面介绍。现在,让我们看看这个缺省页面是如何生成的,因为这将让您对 Apache 的真正工作原理有所了解。

Apache 配置

坦率地说,了解 Apache 配置有点像一场噩梦。如果你感兴趣,就去看看/etc/apache2中的内容,了解我的意思。这里有许多具有不同含义的文件夹,这些文件夹之间的相互关系会改变 Apache 服务器实例的工作方式。谢天谢地,一旦你明白你需要在哪里做出改变,一切都变得容易多了。

Apache 配置中第一个值得注意的文件是apache2.conf文件。这是一个控制配置文件,它说明了其他各个部分的位置,以及应用程序应该如何加载它们。第二件要注意的事情是mods-availablemods-enabled文件夹。如果你看一下mods-available文件,你会发现有大量的.load.conf文件。这些文件分别表示 Apache 应该从哪里加载一个模块以及这个模块需要的任何配置信息。

接下来是mods-enabled,最初看这个文件夹会让它看起来就像mods-available。然而,如果你在这个目录上运行一个ls –l,你会看到这个:

$ ls -l /etc/apache2/mods-enabled/

total 0

lrwxrwxrwx 1 root root 28 Sep 17 21:44 alias.conf -> ../mods-available/alias.conf

lrwxrwxrwx 1 root root 28 Sep 17 21:44 alias.load -> ../mods-available/alias.load

lrwxrwxrwx 1 root root 33 Sep 17 21:44 auth_basic.load -> ../mods-available/auth_basic.load

lrwxrwxrwx 1 root root 33 Sep 17 21:44 authn_file.load -> ../mods-available/authn_file.load

...

这表明,实际上,mods-enabled中的每个文件实际上都是指向mods-available中的一个文件的链接!这是 Apache 配置的第一个复杂性,尽管在任何给定的时间都有许多可用的模块,但实际上只有一定数量的模块被加载。像这样使用符号链接意味着在给定时间只需要保存和维护配置的一个副本。更重要的是,对其中一个的任何更改都会影响到两个。

如果你查看sites-availablesites-enabled文件夹,你会看到相同的布局,除了可能有更少的站点。这将我们带到我们搜索的真正目标:Apache 系统中第一个可用的默认站点。所以要么开/etc/apache2/sites-available/default要么开/etc/apache2/sites-enabled/000-default,因为无所谓;他们是一回事!所以,现在你在里面了,回顾一下配置中的每一个东西的含义。如果你想添加额外的网站或者修改网站的显示方式,其中的许多对你来说是很重要的。

<VirtualHost *:80>

ServerAdmin webmaster@localhost

DocumentRoot /var/www

<Directory />

Options FollowSymLinks

AllowOverride None

</Directory>

<Directory /var/www/>

Options Indexes FollowSymLinks MultiViews

AllowOverride None

Order allow,deny

allow from all

</Directory>

ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/

<Directory "/usr/lib/cgi-bin">

AllowOverride None

Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch

Order allow,deny

Allow from all

</Directory>

ErrorLog ${APACHE_LOG_DIR}/error.log

# Possible values include: debug, info, notice, warn, error, crit,

# alert, emerg.

LogLevel warn

CustomLog ${APACHE_LOG_DIR}/access.log combined

</VirtualHost>

第一行可能是最重要的一行;它指定该 web 服务器可用于端口 80 上的任何传入地址。更改这意味着您可以在同一个站点上运行多个 web 服务器,方法是使用不同的端口来引用每个服务器。

Note

通过在 URL 末尾添加:<port number>,您可以在连接到几乎任何应用程序时指定一个端口。

VirtualHost地址之后,下一个相关的东西是DocumentRoot。这很关键,因为它说明了 Apache 将从哪里加载所有 web 服务器内容。这意味着任何在/var/www文件夹中的东西都可以通过基本网站的 80 端口获得。例如,如果你进入/var/www文件夹,你会看到一个文件:index.html。如果您检查这个文件,您会看到一些非常熟悉的内容:

<html><body><h1>It works!</h1>

<p>This is the default web page for this server.</p>

<p>The web server software is running but no content has been added, yet.</p>

</body></html>

是的,这就是用来生成 Apache 当前显示的基本起始页的网站内容。我试着这样想:web 服务器为您提供对该文件夹(及其所有子文件夹和文档)的访问。为了证明这一点,请尝试以 root 用户身份执行以下命令:

$ echo "Hello World" > /var/www/foo

Note

执行sudo su –可以获得 root。

一旦你成功执行了这个命令,将你的浏览器指向http://<IP Address>/foo,你应该会看到hello world显示在那里。你可能想知道为什么我们不需要在对我们网站的第一个请求中使用index.html(即使用 http://10.0.0.2/index.html )。这是因为名为“index”的文件比较特殊。如果没有其他内容可用,则它们是显示的默认页面。

您应该知道的配置的下一部分是Directory配置部分。这些是目录列表,可以提供关于如何访问它们甚至谁可以访问它们的特殊规则。在这个实例中,Apache 对两个目录进行了配置设置:根目录//var/www。虽然您可以对目录值做许多非常有趣的事情,但是在这里我们不需要做太多。要了解更多信息,您应该访问 Apache 网站,通读那里提供的更详细的文档。

在目录细节之后是ScriptAlias,它控制应用程序如何访问 CGI 脚本。ScriptAlias是一个特殊的目录,这些 CGI 程序在其中运行和执行。由于 PHP 是一个很像 BASH 的脚本系统(在第七章的中讨论过),你可能认为这有直接的关系,但事实并非如此。PHP 由一个特殊的解释模块(modphp)运行,而不是由一个单独的 CGI 程序运行。

ScriptAlias下面的目录值与之相关。最容易泄露是这两个引用/usr/lib/cgi-bin/。这个目录包含了+ExecCGI指令。同样,这与脚本和 PHP 之类的东西是如何执行的有关。最后,配置文件以几个非常有用的配置行结束。这些值是 Apache 服务器的日志文件指令;它们说明了日志文件将放在哪里,以及它们将写入哪些文件。这里包括了访问日志(这样你就可以监控每个访问你的人)和更多的标准错误日志。中间的LogLevel指令决定了关于服务器运行的日志应该在哪个级别输出,现在它被设置在WARN,这是合理的。

考虑到您以后在使用 PHP 时可能需要利用这些日志,您现在应该熟悉它们了。但是,首先让我们对 Apache 将包含的日志做一个小小的更改。让我们将日志级别更改为 notice,继续进行更改,然后保存新编辑的文件。一旦文件被更改,我们需要告诉 Apache 这一点,这样它将重新加载配置。与对您的网站进行内容更改不同,我们需要将配置更改通知 Apache。幸运的是,Apache 通过包含一个我们可以向它发出的重载函数使这变得容易了。继续发出这个:

$ sudo /etc/init.d/apache2 reload

[....] Reloading web server config: apache2apache2: Could not reliably determine the server’s fully qualified domain name, using 127.0.1.1 for ServerName

. ok

这告诉 Apache 重新加载它的配置并再次运行。现在看看日志文件,看看我们所做的更改。Apache 日志目录是/var/log/apache2,我们感兴趣的文件是error.log。一旦你往里面看,你应该会看到这样的东西:

[Tue Sep 18 22:46:56 2012] [notice] SIGUSR1 received.  Doing graceful restart

apache2: Could not reliably determine the server’s fully qualified domain name, using 127.0.1.1 for ServerName

[Tue Sep 18 22:46:56 2012] [notice] Apache/2.2.22 (Debian) configured ˗˗ resuming normal operations

这表示我的 Apache 实例收到了一个信号,它执行了一次优雅的重启;然后,它在同一秒钟内恢复正常运行。这是我们刚才执行的配置重新加载。我们还可以查看访问日志,看到我们自己正在访问 web 服务器上的内容,然后打开/var/log/apache2中的access.log文件进行查看。您应该会看到类似这样的内容:

10.0.0.104 - - [18/Sep/2012:20:31:17 +1000] "GET / HTTP/1.1" 200 482 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.10 (KHTML, like Gecko) Chrome/23.0.1262.0 Safari/537.10"

10.0.0.104 - - [18/Sep/2012:22:21:22 +1000] "GET /favicon.ico HTTP/1.1" 404 498 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.10 (KHTML, like Gecko) Chrome/23.0.1262.0 Safari/537.10"

10.0.0.104 - - [18/Sep/2012:22:28:36 +1000] "GET /foo HTTP/1.1" 200 274 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.10 (KHTML, like Gecko) Chrome/23.0.1262.0 Safari/537.10"

这几行显示 IP 10.0.0.104 上有人在 web 服务器的/上发出了一个GET请求,这个请求是/var/www文件夹和其中的index.html文件(因为 index 是默认文件)。它还显示有人正在访问这个目录中的 foo 文件。他们都是我按顺序访问网站。您还会注意到,在中间有一个请求,是对一个favicon.ico的请求。这表示我的浏览器试图请求该网站的收藏夹图标;不幸的是,没有。

解决纷争

在很大程度上,设置 Apache 服务器是轻而易举的事情。只有当你开始做很多改变时,你可能会遇到麻烦。但是,如果您发现自己遇到了问题,请尝试以下方法。检查您是否可以看到 Apache 进程使用ps –ef运行。如果 Apache 没有运行,请尝试启动它。如果失败,请尝试检查错误日志文件,因为它将包含 Apache 遇到的任何错误的信息。错误日志中的大多数条目都是不言自明的。然而,如果你发现自己不知所措,那就去 Apache 网站,那里有大量的帮助可以提供给那些有需要的人。

m 代表 MySQL

现在你已经建立并运行了你的 web 服务器,你可能会问,“我发现你可以通过把内容写入 web 服务器来创建一个网站,然后它就会显示出来。我可能还需要什么软件?”这是真的。现在,你可以用基本的超文本标记语言(HTML)写一个网页,然后就完事了。单独用 HTML 编写的问题是 HTML 是静态的,这意味着任何时候你想改变显示的内容,你都需要打开并编辑文件来改变内容。当你想向不同的人展示不同的东西时,这不是很好的管理。你需要手动坐在那里编辑一大堆东西——这毫无意义。

这是我们开始看到整个灯堆的力量。我们有 Apache 来提供内容,PHP 将处理动态部分,MySQL 数据库实际上将包含您想要显示的各种不同的信息。只有当你拥有所有这些组件并一起工作时,你才能看到灯具系统的真正力量。

安装 MySQL

数据库是一个便于存储和检索数据的系统。它们接受大量的输入,然后对数据进行结构化,以便可以通过查询轻松检索信息。所有 SQL 数据库都是以结构化查询语言命名的,它们使用结构化查询语言来处理信息检索请求。MySQL 是世界上部署最广泛的开源数据库。这就是所谓的关系数据库系统,因为它易于建立和使用,因此被广泛采用。为了安装 MySQL,我们将再次求助于我们的老朋友apt-get;这一次,我们将安装mysql-server包。执行以下操作:

$ sudo apt-get install mysql-server

Reading package lists... Done

Building dependency tree

Reading state information... Done

The following extra packages will be installed:

heirloom-mailx libaio1 libdbd-mysql-perl libdbi-perl libhtml-template-perl

libmysqlclient16 libnet-daemon-perl libplrpc-perl mysql-client-5.5

mysql-common mysql-server-5.5 mysql-server-core-5.5

Suggested packages:

libipc-sharedcache-perl libterm-readkey-perl tinyca

The following NEW packages will be installed:

heirloom-mailx libaio1 libdbd-mysql-perl libdbi-perl libhtml-template-perl

libmysqlclient16 libnet-daemon-perl libplrpc-perl mysql-client-5.5

mysql-common mysql-server mysql-server-5.5 mysql-server-core-5.5

0 upgraded, 13 newly installed, 0 to remove and 84 not upgraded.

Need to get 9,770 kB of archives.

After this operation, 91.5 MB of additional disk space will be used.

Do you want to continue [Y/n]?

一旦实际的 MySQL 安装完成,您将被要求为您的数据库提供一个 root 密码;这是您的数据库的主管理员密码。一定要记住;你以后会需要它的。现在安装已经完成,请确认该过程已经开始,如下所示:

$ ps –ef | grep mysql

输出应该显示一些不同的东西,包括 MySQL 守护进程,这里突出显示了它:

root      5254     1  0 20:23 ?        00:00:00 /bin/sh /usr/bin/mysqld_safe

mysql     5592  5254  3 20:23 ?        00:00:03 /usr/sbin/mysqld ˗˗basedir=/usr ˗˗datadir=/var/lib/mysql ˗˗plugin-dir=/usr/lib/mysql/plugin ˗˗user=mysql ˗˗pid-file=/var/run/mysqld/mysqld.pid ˗˗socket=/var/run/mysqld/mysqld.sock ˗˗port=3306

root      5593  5254  0 20:23 ?        00:00:00 logger -t mysqld -p daemon.error

这是一个很大的进程命令,但它确实表明 MySQL 已经启动并运行了。我们可以查看 MySQL 的配置,但幸运的是,该系统将满足我们所有的基本需求,不需要修改配置。鉴于此,让我们开始研究一些插入和检索数据的基本方法。

结构化查询语言

结构化查询语言(SQL)是一种用于在 SQL 数据库中插入和检索信息的语言。在 SQL 中,您可以执行许多不同的查询,从简单到极其复杂。考虑到(和其他东西一样)许多其他资源花费了更多的时间和更多的关于语法和起源的细节,我们在这里就不讨论了。此外,出于我们的目的,我们只需要五种类型的查询:

  • 基本管理命令
  • 插入新数据
  • 查找数据
  • 更新数据
  • 删除数据

虽然这听起来非常简单,但是如果你想做一些更复杂的事情,很容易迷失在语法中。如果是这种情况,并且您希望做一些比我们在这里介绍的稍微复杂的事情,请访问 MySQL 网页,因为它们有完整的文档说明如何准确地执行每个查询,以及每个命令需要的大量选项。如果你想了解更多关于 web 开发的知识,看看 Jason W. Gilmore 的《PHP 和 MySQL 5 入门》。

反正足够的植入式广告;让我们从基本的管理工作开始,做一个数据库。这个的语法非常简单:

create database <database name>

要在 MySQL 实例上执行命令,您需要连接到它并进入mysql shell。执行此操作的命令如下:

mysql –uroot –p

因此,进入 shell,提供您的 root 密码,并执行create database pi;,这会生成以下输出:

mysql> create database pi;

Query OK, 1 row affected (0.00 sec)

好吧,太棒了。我们有一个数据库叫做pi。在 SQL 数据库系统中,数据库是最高级别的构造。下面是一个表,它是保存数据的对象(一个数据库保存多个表)。表中的每一项数据都称为一行。所以我们的下一步是创建一个表;问题是我们需要给我们的表一些结构。我们需要告诉它应该保存什么样的数据,以及我们将如何布置这些数据,这意味着我们需要一个项目。现在最明显的是一个简单的待办事项列表,所以让我们做一个。

Note

所有 MySQL 命令都以分号(;)结尾。它表示这是一个语句的结束。这样做是因为您可以将一个命令的行放在多行中,这样更容易理解。

创建表格

很快地,我稍微偏离了正常的项目计划方法,这样我们可以一起运行一个基本的数据库设置。当进行一个大项目时,最好提前做好计划,并在向数据库中添加任何数据之前很久就完全了解将会有什么内容进入数据库。

因此,对于我们的待办事项列表,我们希望为每个“待办事项”保留以下信息:

  • 描述
  • 执行任务的人(所有者)
  • 截止日期(日期)
  • 位置
  • 重要
  • 谁设置的任务(创建者)

好,有了这些信息,我们就有了粗略的表格结构;现在让我们继续创建它。创建表的语法如下:

create table <tablename> (

<column name> <column type>,

<column name> <column type>,

...

<column name> <column type>

);

这是基本的语法,但是在创建表时,我们还需要确保最后一件事:一个键。鉴于我们需要确保每次都能获得正确的价值,我们需要让每一份数据都具有独特性。除了一两个小的变化之外,这些任务中的许多可能以几乎相同的方式结束。出于这个原因,我们将需要所谓的主键,它是每一行的唯一标识符。在这种情况下,最好只使用一个简单的计数,它会随着我们添加的每一行而自动增加。现在我们知道我们想要什么;让我们把它充实到前面的语法中,看看我们有什么。

create table todolist (

idnumber <column type>,

description <column type>,

owner <column type>,

date <colunm type>,

location <column type>,

importance <column type>,

creator <column type

);

好了,看起来更好了,但是我们仍然没有那些<column types>,也没有任何东西会说我们的idnumber是主键,或者应该为每个新行自动更新。这很好地向我们介绍了列类型是什么。MySQL 将需要知道我们的每个数据元素将是什么类型的值,这样它就可以知道如何存储它们以及它们可以参与什么类型的查询。MySQL 中至少有 30 种不同的数据类型,它们可以执行各种操作,但是对于我们的简单目的来说,我们只需要关心三种:一种是称为VARCHAR的文本或文本串,并被赋予最大字符数,第二种是称为INT,的数字或整数,最后一种是称为DATE的日期值。假设我们现在知道了一些数据类型,我们可以将它们设置到我们的create table命令中。现在就开始吧,记住文本字符串(VARCHAR)需要被赋予最大长度的字符数:

create table todolist (

idnumber INT,

description VARCHAR(200),

owner VARCHAR(40),

date DATE,

location VARCHAR(40),

importance VARCHAR(10),

creator VARCHAR(40)

) ;

这看起来更好,可能会运行,但请记住,我们希望该号码是我们的唯一标识符,并自动更新,以使我们的生活更容易。idnumber自动递增(更新为+1)的语法是PRIMARY KEY NOT NULL AUTO_INCREMENT。这给了我们最后的命令:

create table todolist (

idnumber INT PRIMARY KEY NOT NULL AUTO_INCREMENT,

description VARCHAR(200),

owner VARCHAR(40),

date DATE,

location VARCHAR(40),

importance VARCHAR(10),

creator VARCHAR(40)

);

所以,继续运行它:

ERROR 1046 (3D000): No database selected

哦,哎呀。我们需要告诉 MySQL 我们正在使用哪个数据库,因此这个表将驻留在哪个数据库下。要改变我们正在使用的数据库,我们需要使用USE命令。要更改为使用 Pi 数据库,我们只需执行USE pi;,MySQL 会告诉我们数据库已经更改。如果你忘记了你的数据库名,你可以使用SHOW命令来查看东西,SHOW DATABASES;会显示你系统上的所有数据库。现在继续,再次执行表创建。这次你应该看到这个:

Query OK, 0 rows affected (0.91 sec)

太好了,我们有桌子了。我们来看看,看能不能看出来。发出SHOW命令,但这次是针对表。您应该看到下面的输出,它列出了 Pi 数据库中的所有表:

mysql> SHOW TABLES;

+˗˗˗˗˗˗˗˗˗˗˗˗˗˗+

| Tables_in_pi |

+˗˗˗˗˗˗˗˗˗˗˗˗˗˗+

| todolist     |

+˗˗˗˗˗˗˗˗˗˗˗˗˗˗+

1 row in set (0.00 sec)

雅虎!现在,比方说,你已经忘记了桌子到底是什么样子;您将希望 MySQL 描述该表是如何组成的。您可以使用DESCRIBE命令来做到这一点,所以让我们试着描述一下我们的新todolist表,这样我们就可以看到 MySQL 是如何理解它的。

mysql> DESCRIBE todolist;

+-------------+--------------+------+-----+---------+----------------+

| Field       | Type         | Null | Key | Default | Extra          |

+-------------+--------------+------+-----+---------+----------------+

| idnumber    | int(11)      | NO   | PRI | NULL    | auto_increment |

| description | varchar(200) | YES  |     | NULL    |                |

| owner       | varchar(40)  | YES  |     | NULL    |                |

| date        | date         | YES  |     | NULL    |                |

| location    | varchar(40)  | YES  |     | NULL    |                |

| importance  | varchar(10)  | YES  |     | NULL    |                |

| creator     | varchar(40)  | YES  |     | NULL    |                |

+-------------+--------------+------+-----+---------+----------------+

7 rows in set (0.00 sec)

牛逼;你甚至可以看到我们的idnumberPRI键,在末尾有自动递增!一切注册成功。我们已经成功地创建了一个数据库和一个表。我们给我们的桌子赋予了结构。现在是时候好好利用它了,但是在我们开始处理数据之前,我还想介绍最后一个管理命令:创建一个非 root 用户。这样,我们就不必经常使用根用户,这将减少潜在的安全风险。该命令(称为GRANT)的语法如下:

GRANT ALL ON <databse>.<table> TO ’<username>’@’<user location>’ IDENTIFIED BY ’<password>’;

因此,假设我们想授予系统上的默认 pi 用户使用密码 raspberry 访问 todolist 表的权限。该命令将变成这样:

GRANT ALL ON pi.todolist TO ’pi’@’localhost’ IDENTIFIED BY ’raspberry’;

运行这个命令,然后通过键入quit退出 MySQL shell。现在尝试用新的用户名和密码重新登录。记住,它的语法是:

$ mysql –u<username> -p

Note

当在 MySQL 中输入任何不是 MySQL 已经“理解”的文本数据时(例如,表名和列名),都需要引号。

插入数据

现在你作为pi用户重新登录,让我们开始学习一些将数据插入 MySQL 的实际命令。第一个命令是插入数据的命令。如果没有可用的数据,我们还能指望如何处理 MySQL 数据呢!插入数据的命令是名副其实的INSERT命令。INSERT的基本语法如下:

INSERT INTO <TABLE> (<FIELD1>, <FIELD2>, ... <FIELDX>) VALUES (’<VAL1>’, ’<VAL2>’, ... ’<VALX>’);

现在我们知道了插入应该是什么样子,让我们继续在数据库中创建一个。让我们在待办事项列表中插入一对任务。第一个将是我写的这一章。所以命令应该是这样的(确保我们输入了USE pi):

INSERT INTO todolist (description, owner, date, location, importance, creator) VALUES (’Finish LAMP Chapter’, ’David’, ’2012-09-22’, ’Australia’, ’HIGH’, ’David’);

Query OK, 1 row affected (0.43 sec)

奏效了。让我们添加另一个,只是为了更好的措施。让我们现在让彼得做点什么:

INSERT INTO todolist (description, owner, date, location, importance, creator) VALUES (’Finish GUI Chapter’, ’Peter’, ’2012-09-22’, ’Hong Kong’, ’HIGH’, ’David’);

Query OK, 1 row affected (0.48 sec)

现在我们有两张唱片可以玩了。但是我们如何确定他们是正确的呢?假设我们甚至没有将它们添加到我们的insert语句中,我们如何检查idnumber是否增加了?为此,我们需要向我们的 SQL 数据库发出一个查询!

查询数据库

与大多数其他语句不同,数据查询不是用QUERY命令完成的。这是因为我们一直在执行的所有命令都被认为是查询本身。检索数据的命令称为SELECT,其语法如下:

SELECT <Fields1>, <Field2>... <FieldX> FROM <TABLENAME> WHERE <INFORMATION QUERY>

是的,我知道语法有点奇怪,但是一旦我们把它填好,你会对它为什么是这样有更多的了解。首先,让我们把所有东西都拿走。通常,您可以输入您希望得到的字段名,这限制了传输的额外数据量,但是在这种情况下,我们可以使用特殊的通配符*。因此,为了查询我们的todolist表中的所有内容,我们将执行以下命令:

mysql> SELECT * FROM todolist;

+----------+---------------------+-------+------------+-----------+------------+---------+

| idnumber | description         | owner | date       | location  | importance | creator |

+----------+---------------------+-------+------------+-----------+------------+---------+

|        1 | Finish LAMP Chapter | David | 2012-09-22 | Australia | HIGH       | David   |

|        2 | Finish GUI Chapter  | Peter | 2012-09-22 | Hong Kong | HIGH       | David   |

+----------+---------------------+-------+------------+-----------+------------+---------+

2 rows in set (0.00 sec)

你会注意到我省略了WHERE,这已经完成了,因为我们想要的东西没有实际的限制,我们想要一切。在这之后,你可以看到我们输入的所有内容都是我们给定的格式,更重要的是 ID 号在递增!这将验证我们之前插入的所有内容。现在你已经理解了非常基本的语法,我们可以做一些稍微高级一点的查询。比方说,我想找出谁应该为分配给我(大卫)的任务负责。在这种情况下,相关的信息是创造者,因为这就是我们想要的,责备谁。这将给出查询的前半部分:

SELECT creator FROM todolist

现在,我们需要的下一部分是指定所有者必须是 David 的部分。其语法非常简单,给出了最终的查询:

SELECT creator FROM todolist WHERE owner = "David";

+---------+

| creator |

+---------+

| David   |

+---------+

1 row in set (0.00 sec)

太美了。我现在知道,这种混乱只能怪我自己。同样,让我们试试另一个。我想知道今天(在本例中是 9 月 20 日)之后到期的所有任务的描述和优先级。所以我们再次开始构建我们的查询。这次我们需要描述和重要性字段,所以它们进去了。我们还希望日期大于 2012 年 9 月 20 日。幸运的是,MySQL 理解日期数据,所以我们需要做的就是给它我们的日期和大于号,也就是>。这将为您提供以下查询:

mysql> SELECT description, importance FROM todolist WHERE date > "2012-09-20";

+---------------------+------------+

| description         | importance |

+---------------------+------------+

| Finish LAMP Chapter | HIGH       |

| Finish GUI Chapter  | HIGH       |

+---------------------+------------+

2 rows in set (0.01 sec)

一个SELECT查询的最后一个简单部分可能稍后会有关联。这是可以添加到查询末尾的ORDER BY语句。以我们的最后一个为例;假设我们希望它们按照添加的顺序排序(由idnumber)。这将使我们的疑问变成这样:

mysql> SELECT description, importance, idnumber FROM todolist WHERE date > "2012-09-20" ORDER BY idnumber;

+---------------------+------------+----------+

| description         | importance | idnumber |

+---------------------+------------+----------+

| Finish LAMP Chapter | HIGH       |        1 |

| Finish GUI Chapter  | HIGH       |        2 |

+---------------------+------------+----------+

2 rows in set (0.00 sec)

就这样,排序完毕。好的,所以我们看不到它,因为它是按升序排列的,之前也是这样显示的。为了撤销(或强制)订单,我们可以在语句的末尾添加一个ASCDESC,所以让我们使用与之前相同的查询来尝试一下,并观察变化:

mysql> SELECT description, importance, idnumber FROM todolist WHERE date > "2012-09-20" ORDER BY idnumber DESC;

+---------------------+------------+----------+

| description         | importance | idnumber |

+---------------------+------------+----------+

| Finish GUI Chapter  | HIGH       |        2 |

| Finish LAMP Chapter | HIGH       |        1 |

+---------------------+------------+----------+

2 rows in set (0.00 sec)

那更好;您现在可以看到顺序的变化了!让东西按顺序返回是很棒的,因为这意味着数据库系统正在为您进行排序——这是编程很容易做到的事情。如果我们试图编写这样的排序,可能会比数据库执行起来花费更长的时间。现在我们已经讲述了第二个基本命令,我们可以插入和检索数据。我们将研究的下一个命令是更新数据的命令。

更新数据库

现在我们已经过了使用SELECT命令进行查询的愚蠢阶段,我们又回到了使用UPDATE命令命名的领域。更新在某种程度上是插入和SELECT查询的混合,这是理所当然的,因为我们既需要找到某个东西,又需要更新那个东西。UPDATE的基本语法是这样的:

UPDATE <table name> SET <column name1> = "<value1>",<column name2> = "<value2>"... <column nameX> = "<valueX>" WHERE <Information Query>

让我们来看一个例子。比方说,我需要延长这一章的最后期限,因为我工作太努力了,想放松一晚上。这意味着我想把我的截止日期从 22 号增加到 23 号。所以我们知道我们想要更新什么,这给了我们这个命令:

UPDATE todolist SET date="2012-09-23" WHERE

现在我们只需要一个查询;我们不能选择 22 号的东西,因为有两件东西使用了那个日期;严重性和创造者也是如此。我们可以使用所有者或描述或idnumber。在这种情况下,我会选择 ID 号,因为我们已经将它设置为主键,因此是完全唯一的标识符。这使得我们的UPDATE查询变成

mysql> UPDATE todolist SET date="2012-09-23" WHERE idnumber=1;

Query OK, 1 row affected, 1 warning (0.48 sec)

Rows matched: 1  Changed: 1  Warnings: 0

你会注意到我没有在第一个数字的末尾加上引号。这是因为 MySQL 将数字视为INT不同于将数字视为VARCHAR;虽然这似乎是语义。这是一个重要的区别,因为数学运算不能针对VARCHAR,执行,但可以针对INT。MySQL 没有显示数据的实际输出;要查看它,我们需要发出另一个 select 命令,所以让我们继续检查更改:

mysql> SELECT * FROM todolist WHERE idnumber=1;

+----------+---------------------+-------+------------+-----------+------------+---------+

| idnumber | description         | owner | date       | location  | importance | creator |

+----------+---------------------+-------+------------+-----------+------------+---------+

|        1 | Finish LAMP Chapter | David | 2012-09-23 | Australia | HIGH       | David   |

+----------+---------------------+-------+------------+-----------+------------+---------+

1 row in set (0.00 sec)

这就是你要的。我刚刚给自己放了一晚上假。但正因为如此,彼得和我都必须更加努力地完成剩下的章节。这意味着我们需要将两个章节的重要性设置为最高!第一部分很简单:

UPDATE todolist SET importance="HIGHEST" WHERE

这是我们可以再次改变查询方式的地方。我们希望做所有的事情吗?并通过移除WHERE将一切设置为HIGHEST?让我们搜索HIGH,并将其设为HIGHEST,这是我们最后的查询:

mysql>  UPDATE todolist SET importance="HIGHEST" WHERE importance="HIGH";

Query OK, 2 rows affected (0.49 sec)

Rows matched: 2  Changed: 2  Warnings: 0

同样没有输出,因此我们必须再次检索数据:

mysql> SELECT * FROM todolist;

+----------+---------------------+-------+------------+-----------+------------+---------+

| idnumber | description         | owner | date       | location  | importance | creator |

+----------+---------------------+-------+------------+-----------+------------+---------+

|        1 | Finish LAMP Chapter | David | 2012-09-23 | Australia | HIGHEST    | David   |

|        2 | Finish GUI Chapter  | Peter | 2012-09-22 | Hong Kong | HIGHEST    | David   |

+----------+---------------------+-------+------------+-----------+------------+---------+

2 rows in set (0.00 sec)

我们能够将这两个HIGH值更新为HIGHEST。现在是剩下的最后一项任务:删除。

删除数据

我们希望能够删除任务,因为他们已经完成。DELETE命令的语法构造与SELECT命令几乎完全一样:

DELETE FROM <table name> WHERE <information query>;

这是最后一个例子。假设我休息一晚的原因不是我累了;因为我提前看完了这一章!呜哇!所以让我们建立删除。再次让我们通过idnumber来确定。命令应该是这样的:

mysql> DELETE FROM todolist WHERE idnumber=1;

Query OK, 1 row affected (0.42 sec)

UPDATE一样,除了简短的输出说我们删除了一行之外,没有返回任何信息。让我们再次发出一个SELECT,看看发生了什么变化:

mysql> SELECT * FROM todolist;

+----------+--------------------+-------+------------+-----------+------------+---------+

| idnumber | description        | owner | date       | location  | importance | creator |

+----------+--------------------+-------+------------+-----------+------------+---------+

|        2 | Finish GUI Chapter | Peter | 2012-09-22 | Hong Kong | HIGHEST    | David   |

+----------+--------------------+-------+------------+-----------+------------+---------+

1 row in set (0.01 sec)

只有彼得的那一章还在,他必须最努力地工作,因为它在最上面。好吧,让我们友好一点——最后一个命令。这个去掉了一张桌子;它被称为DROP命令,非常简单:

DROP TABLE <table name>;

让我们删除我们的todolist表,因为我们已经完成了这些 MySQL 示例,我们不希望它占用空间:

mysql> DROP TABLE todolist;

Query OK, 0 rows affected (0.49 sec)

好了,现在我们只剩下 pi 数据库了。如果你想删除它,你只需要在DROP命令中将世界TABLE替换为单词DATABASE。这取决于你;我们以后会重用这个。现在,让我们来看看 LAMP 堆栈的最后一部分:PHP。

p 代表 PHP

PHP 是一个允许你在网页中添加动态功能的系统。最初,所有的网页都是由 HTML 单独制作的,这不允许基于输入和动作的太多灵活性。HTML 只是显示一个静态的内容页面,需要修改以显示不同的内容。为了解决这个最初的问题,开发了公共网关接口(CGI)。这为 web 服务器接受请求提供了一种方法,允许它们返回内容。

最初,大多数 CGI 是成熟的应用程序,根据它们的输入输出不同的 HTML 片段,这些应用程序的整个部分都致力于一遍又一遍地输出大块的相同 HTML。这就是 PHP 开发的由来。PHP 被设计成一种语言,在这种语言中,人们可以将实际的动态代码片段添加到静态 HTML 中,然后很快:一个动态网页就形成了。

自从 PHP 诞生以来,它已经变得非常普及,安装量达到了 2000 万。这一成功在很大程度上是因为与原始的应用程序表单 CGIs 相比,PHP 可以轻松地用作 CGI。现在你已经了解了 PHP 是什么,让我们把它安装到树莓 Pi 上。和以往一样,我们将依靠apt-get的服务来为我们获取和安装 PHP。要运行的命令是:

$ sudo apt-get install php5

Reading package lists... Done

Building dependency tree

Reading state information... Done

The following extra packages will be installed:

apache2-mpm-prefork libapache2-mod-php5 libonig2 libqdbm14 php5-cli php5-common

Suggested packages:

php-pear

The following packages will be REMOVED:

apache2-mpm-worker

The following NEW packages will be installed:

apache2-mpm-prefork libapache2-mod-php5 libonig2 libqdbm14 php5 php5-cli php5-common

0 upgraded, 7 newly installed, 1 to remove and 84 not upgraded.

Need to get 5,707 kB of archives.

After this operation, 16.3 MB of additional disk space will be used.

Do you want to continue [Y/n]?

注意,PHP 正在修改我们的 Apache 安装。这是为了将 Apache tribesman 模块替换为那些更适合使用 PHP 的模块。一旦 PHP 完成安装,我们就要继续测试它是否正常工作。我们可以编写一个简单的 PHP 页面,它将显示我们所有的安装设置,并确认 PHP 已经启动并运行。为了测试这个第一页,我们需要用一个新的index.php替换/var/www中的index.html,其中包含以下代码行:

<?php phpinfo(); ?>

一旦您删除了index.html并添加了index.php,将您的浏览器指向您的 Apache 服务器。图 8-2 将是你的奖励。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-2。

PHP info page

这是 PHP 信息页面,它描述了新安装的 PHP 实例的整个当前配置。因为我们正在查看它,这也证明了 PHP 解释器工作正常。这也表明index.php已经被 Apache 选中为默认页面。我们添加的生成代码如下:

<?php phpinfo(); ?>

这是 PHP 代码的一个很好的例子;首先,我们有开始标记,显示这是 PHP,并被解释为 PHP(<?php?>关闭),我们有一个对phpinfo();的函数调用,显示我们所有的数据。

函数是对我们通过调用函数名运行的一段预定义代码的引用。函数总是以一对括号结束,括号中可以包含数据(将传递到函数中使用的变量)。除了函数,PHP 还使用数组,就像你在第七章中看到的数组一样。概括地说,数组是包含许多值而不是一个值的变量。数组可以作为一个整体来操作,也可以由每个单独的元素来操作。要访问数组元素,需要在数组变量的末尾添加一对方括号,并输入想要访问的元素的编号。这些元素编号从第一个元素 0 开始递增。

PHP 需要知道的其他重要事情是:

  • 所有 PHP 语句都以分号结尾
  • 所有 PHP 变量都以一个$符号开始(回想一下第七章中【BASH 的变量);PHP 变量也用于同样的目的)
  • 所有 PHP 数组变量都以一个@符号开始
  • 所有 PHP 函数的内部代码都用花括号{}括起来

接下来,我们需要浏览一下 PHJP 信息页面;从搜索 MySQL 开始。哦,亲爱的,没有提到它。PHP 将需要了解如何与 MySQL 通信,以便我们能够在网页上显示数据库内容。谢天谢地,又来救援了。这一次,我们需要安装php5-mysql包:

$ sudo apt-get install php5-mysql

Reading package lists... Done

Building dependency tree

Reading state information... Done

The following extra packages will be installed:

libmysqlclient18

The following NEW packages will be installed:

libmysqlclient18 php5-mysql

0 upgraded, 2 newly installed, 0 to remove and 84 not upgraded.

Need to get 711 kB of archives.

After this operation, 3,547 kB of additional disk space will be used.

Do you want to continue [Y/n]?

同样,您将看到这次安装添加了一个用于通信的新 MySQL 客户端库和新的 PHP MySQL interleave。它还会自动重启 Apache web 服务器,并为您更改几个 PHP 配置文件。再一次,打开浏览器,进入 Apache web 页面,搜索 MySQL,就这样!(参见图 8-3 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-3。

PHP MySQL config info

我们确认了以下内容:

  • 我们有一个工作的 PHP 系统
  • Apache 可以呈现 PHP
  • MySQL 和 PHP 知道如何通信

这意味着我们刚刚完成创建一个工作灯设置!现在让我们使用它。

简单的 Web 应用程序

现在我们已经建立了新的灯堆栈,它已经在您的 Pi 上运行了,是时候好好利用它了。好的用途可以是你想要的任何东西,因为你现在有了自己的 web 服务器,可以在上面编写 web 应用程序。然而,对于你们中的许多人来说,这似乎是一项艰巨的任务,因为你们不熟悉 PHP 甚至 HTML。不要害怕;就像一般的 Linux 一样,这一开始看起来令人生畏,并且有错综复杂的地方,但是没有什么是不可克服的,也没有什么是你不需要一点帮助就能克服的。我总是发现最好的学习方法是实际去做,这意味着我们可以一起承担一个项目,这将说明如何使用您的新 LAMP 堆栈来创建一个 web 应用程序。

为了构建一个 web 应用程序,我们需要了解 HTML 和 PHP。最后一件事:这将是一个基本的网页,可能看起来有点粗糙。有很多方法可以改善你的网页的视觉效果,但是有整本书都致力于此,它们将涵盖更多的内容和交互。所以考虑到这一点,让我们来看看理解 HTML。

超文本标记语言

超文本标记语言(HTML)是创造互联网的语言。HTML 为所有传输的 web 应用程序内容提供了基础,它由一系列标签组成,并作为 HTML 文档发送到您的浏览器。然后,您的浏览器使用这些标签来组装您的网页版本。这意味着网页内容可以非常小,任何有能力解码 HTML 标签的系统都可以看到。这里的缺点是,不同的软件可以显示略有差异的网站。

HTML 标签是由尖括号括起来的单词。这些标签是用所谓的标记语言编写的。HTML 标签有两种类型,单独的或者成对的,成对的更常见。这里有两组 HTML 标签。

  • 一个开始和一个结束的 HTML 标签(这些标签告诉你的浏览器 HTML 页面从哪里开始和结束)
  • <hr/>:一个单独的hr标签(<hr/>会在你的网页上画一条线,因为它代表标题规则)

第二个 HTML 标签前面的斜线表示这是这个标签的结尾。标签末尾的斜线表示它应该独立存在。看看一个很基础的网站:

<html>

<head>

<title>Pi Brand - Todo List App</title>

</head>

<body>

<h1>Pi Todo List App</h1>

</body>

</html>

如果你把它放到 Pi 上的index.php中,打开你的浏览器,你会看到一个基本的白色页面,上面用黑色大字体写着短语 Pi Todo List App。此外,你的浏览器顶部也应该显示同样的信息。这个网站完全由我们刚刚输入的 HTML 标签生成。让我们来看一下这些,作为对网站工作原理的解释:

  • 首先,您会看到所有的内容都被包装在一对 HTML 标记中,这表示其中的所有内容都是 HTML。
  • 下一级是headbody标签,它们代表下一级标签、页面的标题信息和页面的实际主体。
  • title标签用于设置页面顶部的标题栏。
  • 正文中有一对h1标签,它们是一大块文本。h1是 header 1 的缩写,代表 header 标签的最大尺寸。你可以使用几乎无限数量的hx标签;只要定义了它们,大部分浏览器只会走到h4或者h5而不会单独定义一个。

所以我们现在已经讨论了五个基本的 HTML 标签,但是还有更多。要参考大量常见的 HTML 标签、一些例子和很棒的教程,可以去wc3schools.com或者看看 Craig Cook 和 David Schultz 的“用 CSS 和 XHTML 开始 HTML”。

这里还有一些 HTML 标签以及它们的用法:

  • <p>:段落标签,用于将文本创建到段落块中
  • <br> : Break 标签,用于在页面中插入一个分隔符
  • <hr>:水平标尺,在屏幕上创建一条水平线
  • <a>:超链接标签,用于链接到另一个页面
  • <img>:图像标签,用于向页面添加图像
  • 表格标签
    • <table>:表格的顶层标签
    • <th>:表格标题行的元素
    • <tr>:表格的行
    • <td>:表格行内的单元格
  • 表单标签
    • <form>:表单的顶层元素
    • <input>:表单的输入元素

这些是一些基本的 HTML 标签,我们将在项目中使用它们来创建输出,并将输入驱动到待办事项列表中。除了 raw 标记之外,还有许多不同的选项可以用来改变这些元素显示的各个方面;不幸的是,要涵盖每一个元素需要的时间比本书的其余部分都要多,所以我们将不得不在它们出现的时候讨论它们。现在,您已经熟悉了 HTML 的功能和其中一些标签的外观,我们可以继续尝试在这里添加一些 PHP。但是首先,让我们来看看实际设计我们的网页。

开始我们的页面

我们的网页的基本概念将是一个网站分为两部分;首先将是一个包含待办事项列表的页面,列出了数据库中的所有元素。接下来将是一个表单,用于向底部的数据库提交新元素。我们还将添加一个复选框,允许您删除不再需要的元素。现在我们已经有了基本的布局,还需要做出一些关于如何处理页面的功能决策。由于设计和输出将保持不变,只进行一些处理来连接到数据库以加载元素或删除元素,因此我们可以继续前进并拥有一个页面,该页面将尝试执行它需要的任何操作(在显示页面之前进行添加和删除)。

现在我们可以开始写出这一页了。首先,将所有内容添加到页面,并获得输出。然而,首先我们需要建立我们的数据库,以便我们有数据显示。我们已经创建了 Pi 数据库,并且有了之前的待办事项列表结构,所以让我们使用它们。该表的create声明如下:

create table todolist (

idnumber INT PRIMARY KEY NOT NULL AUTO_INCREMENT,

description VARCHAR(200),

owner VARCHAR(40),

date DATE,

location VARCHAR(40),

importance VARCHAR(10),

creator VARCHAR(40)

);

既然我们有了要处理的数据集和数据布局,我们就可以开始添加显示元素了。因为我们的数据是表格形式的,所以我们可以使用表格元素来显示它。这将使安排数据变得容易得多。所以,我们有了之前的基本页面布局;接下来,我们应该建立我们的数据连接。MySQL PHP 连接使用mysqli接口。因此,为了建立到数据库的连接,我们必须创建一个新的mysqli,它包含我们的数据库和连接的详细信息:

<?php

$mysqli = new mysqli(’localhost’, ’pi’, ’raspberry’, ’pi’);

if ($mysqli->connect_error) {

die(’Connect Error (’ . $mysqli->connect_errno . ’) ’

. $mysqli->connect_error);

}

$mysqli->close();

?>

前面是一小段 PHP 代码,它执行以下操作:

  • 创建一个新的mysqli对象来连接到系统,在本地机器上使用用户名pi和密码raspberry可以使用这个对象。
  • 检查与数据库的连接是否成功;如果没有,则显示一条错误消息。
  • 关闭连接,因为我们不想留下一个潜在的未使用的打开的连接。

现在我们有了这个数据库连接的代码块,将它添加到index.php中的原始代码块:

<html>

<head>

<title>Pi Brand - Todo List App</title>

</head>

<body>

<h1>Pi Todo List App</h1>

<?php

$mysqli = new mysqli(’localhost’, ’pi’, ’raspberry’, ’pi’);

if ($mysqli->connect_error) {

die(’Connect Error (’ . $mysqli->connect_errno . ’) ’

. $mysqli->connect_error);

}

$mysqli->close();

?>

</body>

</html>

显示数据库内容

我们现在有了之前的 HTML 的原始片段,并添加了一个小的mysqli连接对象。除非出现错误,否则这不会有太大作用,因为不会显示任何其他内容。现在是时候添加另一个代码块来显示待办事项列表了。这将采取两种形式:台块和台头。然后,我们需要显示 MySQL 表中的所有内容。显示它们意味着创建一个循环来显示每一行的内容,并将它放在正确的 HTML 标记集中。让我们从基本的 HTML 表格布局开始:

<table>

<tr>

<th>Description</th>

<th>Owner</th>

<th>Due Date</th>

<th>Location</th>

<th>Importance</th>

<th>Creator</th>

</tr>

...

</table>

这段代码显示了表格及其第一行,它们都被标记为表格标题元素。现在添加 PHP 来显示 MySQL 表中的所有内容:

<?php

$result = $mysqli->query("SELECT * FROM todolist");

while($row = $result->fetch_assoc()){

print "<tr>";

print "<td>".$row["description"]."</td>";

print "<td>".$row["owner"]."</td>";

print "<td>".$row["date"]."</td>";

print "<td>".$row["location"]."</td>";

print "<td>".$row["importance"]."</td>";

print "<td>".$row["creator"]."</td>";

print "</tr>";

}

?>

PHP 部分很好地完成了这一任务。首先,它创建一个新变量$ result,它包含在mysqli上执行查询的输出。查询的当然是SELECT * FROM todolist。然后,$result->fetch_assoc()调用在while循环中逐一传递结果的每一行,并将其分配给row变量。对于输出的每一行,我们打印我们请求的每个字段的行值。您可以看到 PHP 的输出部分和原始 HTML 之间的相似之处。这是故意的,因为我们希望 PHP 的输出与表头结合起来。

现在,我们应该将它添加回原始代码块,但是在这样做的时候,我们需要再做一处修改,即将$mysqli->close();移动到新代码块的底部,就在 PHP 代码段结束之前。需要进行这种移动,因为否则我们将在实际从数据库中提取数据之前关闭数据库连接。

...

<body>

<h1>Pi Todo List App</h1>

<?php

$mysqli = new mysqli(’localhost’, ’pi’, ’raspberry’, ’pi’);

if ($mysqli->connect_error) {

die(’Connect Error (’ . $mysqli->connect_errno . ’) ’

. $mysqli->connect_error);

}

?>

<table>

<tr>

<th>Description</th>

<th>Owner</th>

<th>Due Date</th>

<th>Location</th>

<th>Importance</th>

<th>Creator</th>

</tr>

<?php

$result = $mysqli->query("SELECT * FROM todolist");

while($row = $result->fetch_assoc()){

print "<tr>";

print "<td>".$row["description"]."</td>";

print "<td>".$row["owner"]."</td>";

print "<td>".$row["date"]."</td>";

print "<td>".$row["location"]."</td>";

print "<td>".$row["importance"]."</td>";

print "<td>".$row["creator"]."</td>";

print "</tr>";

}

$mysqli->close();

?>

</table>

</body>

</html>

网站数据插入

这个 HTML 现在与 PHP 协同工作,生成一整页的内容。静态 HTML 提供了一个框架,然后我们有两部分 PHP:一部分用于建立连接,另一部分用于从待办事项列表中提取结果并将其添加到页面中。现在我们已经有了基本的显示工作,我们需要添加一个表单来给自己一个提交新内容的表单。这个基本表单应该为我们将要插入到表中的每个元素提供一个输入流。我们还需要另外一个元素,一个特殊的隐藏元素,它将告诉我们的处理器对数据做什么。在这种情况下,我喜欢使用一个名为"action"的变量,并根据需要对其赋值。我们需要的最后一个元素是提交元素,它允许我们将数据推送到服务器进行处理。除了所有这些元素,我们还需要给表单一个actionmethod变量,说明如何在我们的 web 服务器上调用 CGI,以及应该使用什么方法。这块积木拼起来看起来像这样:

...

</table>

<form action="index.php" method="POST">

<input type="hidden" name="action" value="insert" />

Description: <input name="description" /><br/>

Owner: <input name="owner" /><br/>

Date: <input name="date" /><br/>

Location: <input name="location" /><br/>

Importance: <input name="importance" /><br/>

Creator: <input name="creator" /><br/>

<input type="submit" />

</form>

</body>

</html>

然后可以将这个代码块添加到表格下方,甚至用一个<hr />标记分隔,这意味着您将拥有一个显示内容的表格,并且在表格下方有一个区域,您可以添加新内容。当把它们放在一起并安装到 web 服务器上时,它生成的内容将如图 8-4 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-4。

To-do list app with insert table

如果您按下 submit 按钮,您将回到页面上,但是您可以看到该页面在 URL 中被引用。这是发送 CGI 命令以在此页面上运行的动作块。所以现在我们需要在 PHP 中添加一些 CGI 处理来处理这些数据。

回想一下,我们之前设置了一个方法POST,这是我们可以用来将数据从页面传递到 CGI 系统进行处理的两种方法之一。另一种方法是GET,两者之间的区别是有限的,因为两者都以基本相同的方式传递数据。实际上,唯一的区别是GET会在网址上显示内容数据,而POST会将其隐藏。您可以通过将POST更改为GET,然后按下提交按钮来验证这一点。你的网址看起来会像http://10.0.0.20/index.php?action=insert&description=&owner=&date=&location=&importance=&creator =

实际上有大量的数据需要被传输和处理。幸运的是,PHP 有办法让这变得容易得多;它有特殊的变量,这些变量会自动填充来自 CGI 请求的数据。您可以访问三个特殊的变量(就像我们访问 SQL 关联变量一样):_POST_GET_REQUEST

为了处理我们的 CGI,我们需要做很多事情。首先检查action变量是否置位,是否包含数据;当我们按下 submit 按钮时,它将被设置为 insert(稍后有一个 remove 动作)。一旦我们确保动作被设置,我们可以检查它被设置为什么。一旦我们知道我们正在执行哪个操作,我们就可以简单地分离输出的剩余部分,然后在数据库上执行所需的操作。最后,如果我们在主页面加载之前这样做,我们实际上会自动显示最新版本的数据!

我们的 CGI 应该看起来像这样:

if(isset($_REQUEST["action"])){

switch($_REQUEST["action"]){

case "insert":

$SQL="INSERT INTO todolist (description, owner, date, location, importance, creator) VALUES (";

$SQL=$SQL."’".$_REQUEST["description"]."’,";

$SQL=$SQL."’".$_REQUEST["owner"]."’,";

$SQL=$SQL."’".$_REQUEST["date"]."’,";

$SQL=$SQL."’".$_REQUEST["location"]."’,";

$SQL=$SQL."’".$_REQUEST["importance"]."’,";

$SQL=$SQL."’".$_REQUEST["creator"]."’";

$SQL=$SQL.");";

if ($mysqli->query($SQL)=== FALSE) {

printf("Error – Unable to insert data to table " . $mysqli->error);

}

break;

case "delete":

print "Delete function yet to be added!";

break;

}

}

首先要看表单内隐藏字段指定的action变量是否被设置。如果是,我们就可以确定需要执行一个动作。接下来,如果动作被设置,我们进入一个switch(或case)语句来计算出我们正在执行哪个功能。我已经为插入和删除添加了一个用例,但是删除函数只是打印,我们稍后将添加它。此外,如果没有一个针对它的submit方法和一个表单中的动作字段,我们将如何访问它呢?

插入到数据库中

在插入的情况下,我们只需要创建和构建 SQL 命令。我们试图构建的命令与前面的 SQL 插入完全一样。首先,我展示了静态内容:INSERT语句的框架、表名以及我们将要写入的字段。然后,我开始一个接一个地给这个语句添加变量。你可以看到增加的内容有很多有趣的东西,因为每个变量都需要用单引号括起来,并且在末尾有一个逗号。每一小段文本都需要这样对待,这意味着用一对双引号将它们括起来,如下所示:

$SQL=$SQL."’".$_REQUEST["description"]."’,";

这会将当前SQL值(由$SQL=$SQL给出)的值赋给SQL变量,然后给它(由句点标记)加上一个单引号(由"’"给出)和REQUEST变量描述的值,然后加上一个单引号,再加上一个逗号。我知道这看起来工作量很大,但我们已经能够提取出我们需要的每个变量,并在一个小变量中包含整个 SQL 语句。

Caution

在这一点上,还需要注意的是,上述内容并不安全。这些变量实际上可以是任何东西,您可以使用一些特殊的函数来对这些值进行健全性检查。执行这些健全性检查的推荐函数是mysqli_real_escape_string

一旦我们创建了 SQL 变量,我们需要通过简单地再次调用mysqli查询函数来插入它。在这个例子中,我还添加了一个检查来查看查询的执行是否失败。仅此而已;这段代码将处理从 PHP 插入数据库的 CGI。剩下的工作就是将代码块正确地插入到代码中的正确位置。我选择将它附加到我们添加的第一个 PHP 代码块中,而不是给它自己的代码块。这意味着页面的流程首先是绘制页面的标题,然后创建数据库连接并执行任何 CGI 操作,然后显示待办事项列表的实际内容,最后显示最终的表单。一旦输入了代码,您就可以通过将值放入表单并按 submit 来测试它是否工作。这应该继续并添加一个额外的行将显示在你的表中,如图 8-5 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-5。

All work and no play…

删除条目

既然添加功能已经工作,我们需要创建一个删除功能。如同生活中的所有事情一样,有许多方法可以接近它。最简单的方法是为每个单独的行添加一个表单和删除选项,这意味着如果要删除,需要一次发出一个。第二个选项是有一系列的复选框,这些复选框将删除所有选中的元素。我是第二种选择的支持者,因为它在删除时提供了更多的灵活性。

我们需要做两个改变来实现这个过程。第一个变化是在表周围添加一个表单,它将成为删除表单,一个带有删除操作的隐藏字段,表单下面的提交按钮,以及每个元素的复选框。要添加复选框,我们还需要在表格标题行的开头添加一个空元素。图 8-6 是添加了复选框和提交按钮的页面的外观。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-6。

Now with extra check boxes

表单内容生成的代码部分如下所示。您可以看到另一个包装表格的表单具有相同的方法和操作。接下来是新的隐藏输入值,它将操作设置为 delete,允许我们访问case语句中的正确部分。可能最重要的变化是添加了空的td对和类型复选框的额外输入。这个复选框可能是我们已经添加的最复杂的元素集—它包含复选框的类型,即专门配置为可作为数组访问的值,这是通过在前后添加方括号来实现的。最后一个元素是值,我已经将它设置为复选框的值。这意味着当我们进入 CGI 模式时,我们将获取复选框的数组值,它将包含我们希望删除的元素的idnumber值列表。

以下是我们为添加复选框而对表单所做的所有更改的简短摘要:

...

<h1>Pi Todo List App</h1>

<form action="index.php" method="POST">

<input type="hidden" name="action" value="delete" />

<table>

<tr>

<td></td>

...

</tr>

<?php

$result = $mysqli->query("SELECT * FROM todolist");

while($row= $result->fetch_assoc()){

print "<tr>";

print "<td><input type=’checkbox’ name=’checkboxes[]’ value=’".$row["idnumber"]."’ /></td>";

print "<td>".$row["description"]."</td>";

print "<td>".$row["owner"]."</td>";

print "<td>".$row["date"]."</td>";

print "<td>".$row["location"]."</td>";

print "<td>".$row["importance"]."</td>";

print "<td>".$row["creator"]."</td>";

print "</tr>";

}

$mysqli->close();

?>

</table>

<input type="submit"/>

</form>

...

从数据库中删除数据

我们要做最后一个更改,即在case语句中添加删除处理。我们可以使用与insert语句类似的逻辑,创建一个 SQL 变量,然后一个接一个地为idnumber添加 remove。在这种情况下,我们将需要一个for循环,该循环将遍历_REQUEST[’checkboxes’]变量的每个元素,我们可以从变量上运行的 count 函数中获得这些元素。这给了我们一个完整的for循环,如下所示:

for($i=0; $i < count($_REQUEST[’checkboxes’]); $i++){

一旦我们进入循环,我们只需要每次在循环中取出_REQUEST[’checkboxes’]数组的第$i个元素。此外,我们需要在每个元素的末尾添加一个or,这样我们就可以在idnumber之前删除每个元素;我们使用or,因为我们希望它删除变量,如果idnumber是第一个数字,第二个数字,或者第三个数字,等等。

然而,我们对or有一个问题:我们将把它添加到最后一个我们不想要的元素的末尾。我们将需要使用rtrim功能从末端修剪掉orrtrim函数将从字符串的右边删除一个给定值,这将给出我们想要的 SQL。最后,我们执行与 insert 中相同的查询操作,并将再次检查是否有错误。这会给我们一个类似这样的代码块:

$SQL="DELETE FROM todolist WHERE";

for($i=0; $i < count($_REQUEST[’checkboxes’]); $i++){

$SQL=$SQL . " idnumber=" . $_REQUEST[’checkboxes’][$i] . " or";

}

$SQL= rtrim($SQL, "or");

if ($mysqli->query($SQL)== FALSE) {

printf("Error Unable to delete value " . $mysqli->error);

}

图 8-7 为最终结果。我已经使用 delete 删除了原来的条目,所以我们只剩下一个。请记住,本书中的所有示例都可以通过 Apress 网站在线获得。如果你不确定这些代码是如何组装的,请下载一份看看!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-7。

Lowering my workload

解决纷争

现在,让我们来看看解决一些问题的一些方法。首先,尝试按顺序更改每个代码,然后重新加载页面。您可以使用view source命令查看 PHP 生成的完整 HTML,并查看值是否正确显示。如果您不能做到这一点,可以看看错误日志(从一开始我们配置 Apache 时的日志开始)。这个文件将列出所有发生的 PHP 错误。看一下,确保你的引号正确地打开和关闭,并且没有留下任何悬置。检查你的每条语句的末尾是否有一个分号(我总是忘记这个)。检查圆括号、方括号和圆括号是否正确打开和关闭,没有重叠。

PHP 和 web 开发中的许多东西都是反复试验的。您应该检查您添加的每个语句或代码块是否按预期运行和生成。您可以使用print语句在生成变量时输出变量,以查看您到底在做什么,这是诊断 SQL 语句问题的一个好方法。最后,请记住,您可以访问整个系统,继续测试将值插入到 SQL 中,并根据需要删除它们;使用系统找出它在做什么,什么导致了你的问题。

从这里去哪里?

我们已经完成了所有这些开发,我们有一个功能性的待办事项列表,您可以使用它来显示、添加和删除条目。我们使用了各种不同的编程工具来做事情,但是从这里我们可以做很多事情。以下是你可以对你的待办事项列表做出的一些改变:

  • 向每个插入值添加isset();检查,以检查您实际插入的是值,而不仅仅是空格。
  • mysqli_real_escape_string函数包装每个插入的值。这将增加待办事项列表的安全性,因为它防止人们将“讨厌的”值写入您的应用程序,这些值将作为单独的 SQL 查询执行。
  • 更改每个提交元素的value选项,让它们说明我们使用它们的目的:插入或删除。
  • 在表单元素周围创建一个表格用于插入,并为每个标签和插入字段指定它们自己的行和单元格。把它们放在桌子里会让展示更加统一。
  • 开始研究 CSS,因为它可以让你的待办事项列表看起来有很大的不同。推特“自举”是一个很好的起点。

摘要

所以从我们开始以来,我们已经走了很长的路。我们已经做了很多。我们已经安装并配置了 Apache、MySQL 和 PHP。您已经学习了如何创建简单的 SQL 语句来创建和删除数据库和表,然后学习了如何在这些表中插入和删除数据。最后,您已经学习了如何从 Apache web 服务器显示 web 内容。然后,我们将所有内容结合起来,通过一些 PHP 和 HTML 生成一个完整的应用程序,提供对数据库待办事项列表的访问。

这是一项不朽的事业,因为我们不仅安装了三个不同的应用程序,创建了一个互连的应用程序堆栈,还使用了三种语言,在应用程序堆栈之上创建了一个应用程序。

祝贺您——干得好!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值