Linux-系统管理的哲学-全-

Linux 系统管理的哲学(全)

原文:The Linux Philosophy for SysAdmins

协议:CC BY-NC-SA 4.0

一、Linux 理念简介

Unix 哲学是使 Unix 1 独特而强大的重要部分。已经有很多关于 Unix 哲学的文章。而且 Linux 哲学本质上与 Unix 哲学是一样的,因为它是 Unix 的直系后裔。

最初的 Unix 哲学主要是为系统开发人员设计的。事实上,以肯·汤普森 2 和丹尼斯·里奇 3 为首的 Unix 开发者以一种对他们有意义的方式设计了 Unix,创建了规则、指导方针和过程方法,然后将它们设计到操作系统的结构中。这对于系统开发人员来说很有效,对于系统管理员来说至少部分有效。这些来自 Unix 操作系统创始者的指导被整理成了一本优秀的书,由 Mike Gancarz 所著的《Unix 的哲学》,随后被 Gancarz 先生更新为《Linux 和 Unix 的哲学》。 4

**Eric S. Raymond 的另一本好书《Unix 编程的艺术》提供了作者在 Unix 环境中编程的哲学观点。它在某种程度上也是作者所经历和回忆的 Unix 发展史。这本书的全文也可以在网上免费获得。 6

我从这三本书中学到了很多。它们对 Unix 和 Linux 程序员都有很大的价值。在我看来, Linux 和 Unix 哲学Unix 编程的艺术应该是 Linux 程序员、系统管理员和开发人员的必读书目。

我从事计算机工作已经超过 45 年了。天哪,那是很长的一段时间!直到我开始使用 Unix 和 Linux,并开始阅读一些关于 Unix、Linux 和它们共享的共同理念的文章和书籍,我才理解为什么 Linux 和 Unix 世界中的许多事情都是这样做的。

在撰写本文时,我已经在 Unix 和 Linux 上工作了 20 多年,我发现 Linux 哲学极大地提高了我作为一名系统管理员的效率和效力。我总是试图遵循 Linux 哲学,因为我的经验是,不管一大群头发尖尖的老板(PHB)施加多大的压力,严格地坚持它,从长远来看总是会有回报的。

最初的 Unix 和 Linux 理念是为那些操作系统的开发者准备的。尽管系统管理员可以将许多原则应用到他们的日常工作中,但是许多重要的原则,特别是对于系统管理员来说,却是缺失的。

在我的 Unix 和 Linux 职业生涯中,我很幸运有几个优秀的导师。他们帮助我获得了失败的信心。当我失败时,我学到的比事情顺利时要多得多,因为他们让我解决了自己给自己带来的问题。这些专家,比我有更多年系统管理员经验的人,从来没有因为我失败而斥责我或惩罚我——他们的信条是,“如果你失败了,你就会学到东西。”我学到了很多。他们教给我的很大一部分是 Linux 哲学,但他们也教给我他们自己的哲学,这些哲学有助于填补原著中缺失的部分。

因此,在我从事 Linux 和 Unix 工作的这些年里,我形成了自己的哲学——它更直接地适用于系统管理员的日常生活和任务。我的哲学部分基于最初的 Unix 和 Linux 哲学,以及我的导师的哲学。当我决定写我自己的书,一本旨在解决当今系统管理员的需求的书时,我从那些原则开始,但是随着我的进步和这本书的结构向我展示,哲学的结构和本质变得比以往任何时候都更加清晰。事实证明,这种哲学与最初的 Linux 哲学有很大的不同。直到那时,我才意识到需要一种新的哲学,一种专门针对系统管理员的哲学。很自然地,我把这种新哲学称为“系统管理员的 Linux 哲学”

这本书是我创造新哲学的成果——它提供了成为更好的系统管理员的独特的实践方法。这本书和它所揭示的哲学是我试图回馈在我成长过程中养育我并帮助我变得更加自信的社区。

因为“系统管理员的 Linux 哲学”这个名字有点长,为了简单起见,在本书中大多数时候我将它称为“哲学”。

我是系统管理员吗?

因为这本书是为系统管理员准备的,所以知道你是否是一名系统管理员会对你有所帮助。维基百科 7 将系统管理员定义为“负责维护、配置和可靠运行计算机系统的人;尤其是多用户计算机,比如服务器。”根据我的经验,这可能包括计算机和网络硬件、软件、机架和机箱、机房或空间等等。

典型的系统管理员的工作可能包括大量的任务。在小型企业中,系统管理员可能负责所有与计算机相关的事情。在较大的环境中,多个系统管理员可能共同负责保持系统运行所需的所有任务。在某些情况下,您可能甚至不知道自己是系统管理员;你的经理可能只是告诉你开始维护办公室里的一台或多台电脑——不管你喜不喜欢,这都会让你成为一名系统管理员。

还有一个术语“DevOps”,用于描述以前分离的开发和运营组织的交集。在过去,这主要是教系统管理员编写代码,但现在的重点转移到教程序员如何执行操作任务。参与系统管理员的任务使这些人也成为了系统管理员,至少在一部分时间是如此。当我在思科工作时,我有一份 DevOps 类型的工作。一部分时间我编写代码来测试 Linux 设备,其余时间我在测试这些设备的实验室中担任系统管理员。这是我职业生涯中非常有趣和有意义的一段时间。

我创建了这个简短的列表来帮助你确定你是否是一个系统管理员。你知道你是一个系统管理员,如果…

  1. 你认为这本书可能是一本有趣的读物。

  2. 人们经常请你帮助他们使用电脑。

  3. 你每天早上在做其他事情之前都要检查服务器。

  4. 您编写 shell 脚本来自动化甚至简单的任务。

  5. 您共享您的 shell 脚本。

  6. 您的 shell 脚本获得了开源许可。

  7. 你知道开源意味着什么。

  8. 你记录下你所做的一切。

  9. 你黑了无线路由器安装 Linux 软件。

  10. 你会发现计算机比大多数人更容易交流。

  11. 你懂:(){ :|: & } ;:

  12. 你觉得命令行好玩。

  13. 你喜欢完全掌控一切。

  14. 你是根。

  15. 当应用于软件时,你会理解“像啤酒一样免费”和“像演讲一样免费”的区别。

  16. 您在机架 Shell 中安装了一台计算机。

  17. 您已经用散热更好的风扇替换了标准的 CPU 冷却风扇。

  18. 你购买零件,自己组装电脑。

  19. 你用液体冷却你的 CPU。

  20. 你可以在任何东西上安装 Linux。

  21. 您的电视机上连接了一个树莓派。

  22. 你用树莓派作为家庭网络的防火墙。

  23. 您运行自己的电子邮件、DHCP、NTP、NFS、DNS 和/或 SSH 服务器。

  24. 你入侵了你的家用电脑,用一个更快的处理器来替换它。

  25. 您已经升级了计算机中的 BIOS。

  26. 因为经常更换组件,所以您没有盖上电脑的盖子。

  27. 您的 ISP 提供的路由器处于“通过”模式。

  28. 你用 Linux 电脑当路由器。

  29. …等等…

你明白了。我可以列出更多能让你成为系统管理员的东西,但是会有上百条。我相信你能想出更多适合你的。

哲学的结构

对于系统管理员来说,Linux 哲学有三个层次,类似于马斯洛的需求层次。这些层次也象征着我们通过逐渐更高层次的开悟而成长。

底层是基础——我们作为系统管理员需要知道的基本命令和知识,以便执行最底层的工作。中间层由那些建立在基础之上的实用原则组成,并告知系统管理员的日常任务。顶层包含满足我们作为系统管理员的更高需求的原则,它鼓励并使我们能够分享我们的知识。

本书分为三个部分,分别对应于图 1-1 所示的哲学层次。在哲学的第一层也是最基本的一层,奠定了基础。我们将了解“Linux 真相”、数据流、标准 IO (STDIO)、转换数据流以及“一切都是文件”的含义随着我们的工作生活开始有所启发,我们发现自己正在学习许多新的命令,如何在简单的命令行程序中有效地使用它们,以及如何利用一切都是文件的事实。这是我们哲学的基础层,在本书的第二部分有所探讨。

img/462716_1_En_1_Fig1_HTML.png

图 1-1

面向系统管理员的 Linux 理念的层次结构

然后,我们的旅程超越了简单地在键盘上敲击命令,我们开始探索中间层,哲学的功能方面成为我们的指南。为了更好地利用命令行,我们开始扩展我们的命令行程序来创建经过测试和可维护的 shell 程序,我们保存这些程序并可以重复使用,甚至共享。我们变成了“懒惰的管理员”,开始自动化一切。我们适当地使用 Linux 文件系统层次结构,并以开放格式存储数据。哲学的功能部分可以在第三部分找到。

在哲学的顶层,我们进入启蒙阶段,这将在第四部分讨论。随着我们开始超越仅仅执行我们的系统管理员任务和仅仅完成工作,我们对 Linux 设计中的优雅和简单的理解是完美的。我们开始努力优雅地完成自己的工作,保持解决方案简单,简化现有的复杂解决方案,并创建可用和完整的文档。我们开始探索和实验只是为了获取新知识。在这个启蒙阶段,我们开始将我们的知识和方法传递给那些新的专业人员,并且我们积极支持我们最喜欢的开源项目。

在现实生活中,哲学的层次很少是清晰的。我们如何工作和应用哲学的原则可能会因环境、头发浓密的老板、我们的训练水平和我们目前对哲学的理解而异。

谁应该读这本书

如果你是或者想成为一名系统管理员,你应该看看这本书。如果你正在履行系统管理员的一些职责,即使这不是你的职位,你应该读这本书。如果你在德文郡工作,你应该读读这本书。如果您是一台或多台 Linux 计算机的 root 用户,您应该阅读这本书。如果你经常使用和喜欢命令行,你应该读这本书。如果你觉得命令行很好玩,很强大,你应该看看这本书。看看下图中的奶牛,她也想让你读这本书。

如果你想了解让最优秀的 Linux 系统管理员变得比普通人更强大的秘密;如果你想理解解开这些秘密的概念;如果你想成为当字节击中 CPU 冷却风扇时其他人都求助的系统管理员,那么这本书就是为你准备的。

这本书不是关于学习新的命令。相反,它是关于使用您应该已经熟悉的常见和众所周知的命令,在命令行阐明 Linux 的底层结构。想想这本书和你将在练习中使用的命令,如医生用来揭示人体内部的 X 射线、CT 扫描和 MRI。这本书将向你展示如何使用一些简单的 Linux 命令来揭示 GNU/Linux 的底层结构。

面向系统管理员的 Linux 哲学旨在揭示和说明命令行的强大功能和灵活性,以及支持这些特性的设计和使用哲学。这种对如何从 Linux 命令行中获取最多信息的理解可以帮助您成为一名更好的系统管理员。

我假设本书的读者至少有一整年的使用 Linux 命令行界面的经验,最好是使用 bash shell,但是任何 shell 都可以。您应该熟悉许多 Linux 命令。

我的期望是,您已经知道如何使用适当的命令执行大部分系统管理员的工作,并为使用适当的设备进行调整。例如,当我告诉您“在/mnt 上挂载 USB 设备”时,您将明白我的意思,并能够确定要挂载哪个设备文件,使用 mount 命令执行挂载,并根据需要访问已挂载的设备以创建或查看内容。

您还应该在一台或多台 Linux 计算机上拥有 root 访问权限,并且至少已经执行了六个月的系统管理员职责。如果你已经在家里的一台或多台电脑上安装了 Linux,你就符合这个要求,应该读这本书。

但是我不符合那些要求

也许你不符合任何先前陈述的要求,但无论如何都想读这本书。无论你是想成为一名系统管理员,还是仅仅因为你认为这可能很有趣,如果你还想读这本书,那么就去读吧。

如果你对学习面向系统管理员的 Linux 哲学感兴趣,不管我上面说了什么,你都想继续读这本书,那么,就这么做吧。在这种情况下,我已经试图提供足够的信息,使你有可能进行这些实验的大部分。如果您确实遇到了问题,请联系您当地的 Linux 用户组。世界上有很多这样的组织,我发现这些组织的成员往往非常乐于助人。

如果您有兴趣学习更多关于使用 Linux 命令行和学习系统管理技巧的知识,我推荐三本书。当你进行本书中的实验时,它们会成为很好的参考。

  1. Pro Linux 系统管理;马托克、丹尼斯、特恩布尔、詹姆斯、利弗丁克、彼得;压力;国际标准书号 978-1-4842-2008-5

  2. 开始 Linux 命令行;范·武格特,桑德;压力;国际标准书号 978-1-4302-6829-1

  3. Linux 命令、编辑器和 Shell 编程实用指南第三版;索贝尔,普伦蒂斯霍尔;国际标准书号 978-0-13-308504-4

这三本书应该让您开始使用 Linux 命令行,并帮助您学习系统管理。但是最好的方法是尽可能多地亲自动手。

谁不应该读这本书

如果你只是想使用你的网络浏览器,发送电子邮件,也许使用 LibréOffice Writer 程序创建一些文档,如果你不关心 Linux 幕后发生的事情,如果你依赖别人来解决你的计算机问题,这本书不适合你。不读了。

如果你的唯一目的是想了解高级命令以及如何使用它们——这本身就是一个令人钦佩的目标——这本书不适合你。

Linux 真相

最初引用 Unix 的下面这段话暗示了 Linux 命令行的惊人威力。它也适用于 Linux。

Unix 并不是为了阻止用户做蠢事而设计的,因为那也会阻止他们做聪明的事情。

——道格·怀特

这句话总结了 Unix 和 Linux 压倒一切的真理——操作系统必须信任用户。只有通过扩展这种完全的信任度,用户才能获得操作系统的全部能力。这个真理适用于 Linux,因为它是 Unix 的直接后代。

限制性操作系统

保护用户不受其拥有的能力影响的操作系统是从这样一个基本假设开始开发的,即用户不够聪明或知识不足以信任计算机实际上可以提供的全部能力。这些操作系统是限制性的,并且具有用户界面——命令行和图形界面——这些界面通过设计强制执行这些限制。这些限制性的用户界面迫使普通用户和系统管理员等进入一个没有窗户的封闭房间,然后把门砰地关上并上三道锁。那个锁着的房间阻止他们做葛温先生提到的任何聪明的事情。

这种限制性操作系统的命令行界面提供了相对较少的命令,对任何人可能参与的可能活动提供了事实上的限制。一些用户觉得这是一种安慰。从你正在读这本书的事实来判断,我不知道,显然你也不知道。

Linux 是开放和免费的

从某种意义上说,Linux 从一开始就被设计成开放和免费的,用户和系统管理员应该在他们自己的领域内对操作系统的所有方面拥有完全的访问权。结果是我们可以用 Linux 做那些非常聪明的事情。开放和免费还有其他含义,如免费 Libré开源软件(FLOSS)和免费啤酒,但这种讨论是其他书籍的问题。

即使是最有经验的用户也可以使用 Linux 做“蠢事”。我的经验是,通过开放操作系统的全部功能,从我自己并非罕见的愚蠢中恢复变得容易得多。我发现大多数时候几个命令就可以解决问题,甚至不需要重启。有几次,我不得不切换到较低的运行级别来解决问题。我只是很少需要启动到恢复模式来编辑一个配置文件,我设法破坏得如此严重,它导致了严重的问题,包括无法启动。需要了解 Linux 的基本原理、结构和技术,才能完全释放它的力量,尤其是当东西坏了的时候。Linux 只需要系统管理员的一点理解和知识就可以完全释放它的潜力。

真正的知识

任何人都可以记忆或学习命令和程序,但死记硬背不是真正的知识。如果不了解这一理念以及它是如何体现在 Linux 的优雅结构和实现中的,就不可能将正确的命令作为工具来解决复杂的问题。我见过那些对 Linux 有着丰富知识的聪明人无法解决一个相对简单的问题,因为他们没有意识到表面之下结构的优雅。

作为一名系统管理员,在我的许多工作中,我的部分职责是协助招聘新员工。我参加了许多人的技术面试,这些人已经通过了许多微软认证,他们的简历都很好。我也参加了许多面试,在这些面试中,我们寻找 Linux 技能,但是这些申请者中很少有人有证书。当时微软认证是件大事,但在数据中心使用 Linux 的早期,很少有申请人获得认证。

我们通常以旨在确定申请人知识范围的问题开始这些面试。然后,我们将进入更有趣的问题,这些问题将测试他们通过问题找到解决方案的推理能力。我注意到一些非常有趣的结果。很少有 Windows 证书所有者能够通过我们给出的场景进行推理,而很大一部分 Linux 申请人能够这样做。

我认为这一结果部分是由于获得 Windows 证书依赖于记忆而不是实际的动手经验,以及 Windows 是一个封闭的系统,这使得系统管理员无法真正了解它是如何工作的。我认为 Linux 申请人做得更好,因为 Linux 在多个层面上是开放的,逻辑和理性可以用来识别和解决任何问题。任何使用 Linux 一段时间的系统管理员都必须了解 Linux 的架构,并且在应用知识、逻辑和推理来解决问题方面有相当丰富的经验。

启迪

这本书的大部分内容都发生在 Linux 命令行上,但它不是关于命令本身。在本书中,命令是工具,如果你知道如何照亮它,Linux 的底层结构之美就会通过它们而闪耀。这本书将通过向你展示如何使用这些常见的命令来探索这种美,从而帮助你获得启迪。

除了本书中出现的一两个命令之外,您应该已经熟悉了所有的命令。这本书将使您能够使用那些常见的命令来探索 Linux 的底层,并自己发现 Linux 的真相。

别忘了——这应该会很有趣!

**

二、做好准备

这本书定义了一种哲学,但它也旨在用你可以进行的实验来阐明这种哲学的实际方面。因为我们系统管理员是一群亲自动手的人,所以这本书提供了一些简单的实验,你可以通过这些实验来更全面地欣赏和理解哲学的原则。大多数实验通常由一行 bash shell 命令或程序组成。有些实验确实使用了不止一条线。

这一章将会告诉你更多关于实验的期待。它将描述适用于这些实验的 Linux 计算机的最佳配置,并且它将为您提供一个准备在一些实验中使用的 USB 拇指驱动器的机会。

实验

作为一名动手的系统管理员,我喜欢尝试命令行,以便学习新的命令和执行任务的新方法。我为这本书设计的大部分实验都是我在自己的探索中完成的,可能做了一些小的改动以适应它们在这里的使用。

请注意,有些原则不适合实验;因此,不是你在本书中读到的每一个原则都会用实验来说明,但是尽可能多的人会做实验。许多实验说明了更多的原理,而不仅仅是它们出现的原理。

所有的系统管理员都是亲力亲为的人,尽管我们有不同的学习方式。我认为对系统管理员来说,拥有这些原则的实践经验是很有帮助的,这样才能充分想象和欣赏它们所体现的真理。这就是实验的目的——提供一个超越理论并以实际方式应用原则的机会。尽管有些实验是为了说明某一点而做的,但它们仍然是有效的。

这些启发性的实验并没有藏在每一章或每本书的结尾,在那里它们很容易被忽略——它们嵌入在文本中,是这本书流程中不可或缺的一部分。我建议你在阅读这本书时进行实验。

每个实验的命令和结果会出现在“实验”部分,如下所示。许多实验只需要一个命令,因此只有一个“实验”部分。其他的实验可能更复杂,因此分成两个或更多的实验部分。

实验示例

这是一个实验的例子。每个实验都有说明和代码,让你在电脑上输入。

许多实验会有一系列像这一段这样的散文格式的说明。只要按照说明做,实验就会顺利进行。

  1. 有些实验会有一系列要执行的步骤。

  2. 第二步。

  3. 等等…

Code that you are to enter for the experiments will look like this.

实验到此结束。

这些实验中的大部分都可以作为非 root 用户来执行;这比以 root 用户身份做任何事情都要安全得多。但是,对于其中一些实验,您需要成为 root 用户。

这些实验被认为在指定用于培训的计算机或虚拟机上使用是安全的。不管它们看起来有多好,您都不应该在生产系统上执行这些实验。

警告!

这些实验不应该使用生产计算机。您应该使用专门用于培训的计算机或虚拟机。

有时候,我想展示一些有趣的代码,但是你不应该在实验中运行这些代码。对于这种情况,我会将代码和任何支持文本放在如下所示的代码示例部分中。

代码示例

旨在说明某一点但你甚至不应该考虑在任何计算机上运行的代码将包含在这样的一个部分中。

echo "This is sample code which you should never run."

系统需求

你需要一台安装了 Linux 的计算机来进行这些实验。这台计算机的规格相对不重要,因为即使最小的 Linux 计算机也提供终端模拟器或控制台会话来访问命令行。为了获得最佳效果,您可以考虑的最低配置是英特尔或 AMD 硬件,至少 2GB 内存和 i3 处理器或同等配置。必要时,安装了最新版 Raspbian 的树莓派 3B 也能派上用场。

你在本书中用于实验的计算机应该是最新的主流版本,比如 Fedora、Ubuntu、Mint、RHEL 或 CentOS。无论您使用哪个发行版,都应该安装了 GUI 桌面并可供使用。一些实验需要在桌面上打开多个终端仿真会话。

你还需要一个 USB 拇指驱动器,在上面进行一些更危险的实验,包括读写硬盘上的数据。USB 拇指驱动器是一个合适的替代品,其工作方式与真正的硬盘驱动器完全相同,硬盘驱动器有旋转的磁盘和移动的磁头等。

我强烈建议您使用一台没有用于任何其他用途的主机,例如一个专门用于培训的系统,或者一台运行在 VirtualBox 等免费开源软件上的虚拟机来安装 Linux 并执行这些实验。这将大大降低生产计算机受损的可能性。

您应该对用于这些实验的计算机或虚拟机拥有 root 访问权限。如果您在任何计算机上都没有 root 用户,您将无法执行其中的一些实验。需要 root 访问权限的实验会通知您。

您应该使用一个帐户,如“学生”帐户来尝试这些实验中的大部分。这进一步降低了损坏您自己的文件的危险。事实上,大多数实验都假设您以非特权用户 student 的身份登录。

警告!

不要在生产系统上进行本书中介绍的实验。

如何访问命令行

所有现代主流 Linux 发行版都至少提供了三种访问命令行的方式。

如果您使用图形桌面,大多数发行版都有多个终端模拟器可供选择。我更喜欢 Krusader 和 Tilix,但是你可以使用任何你喜欢的终端模拟器。

Linux 还为多个虚拟控制台提供了功能,允许从一个键盘和显示器进行多次登录。虚拟控制台可以在没有 GUI 桌面的系统上使用,但也可以在有 GUI 桌面的系统上使用。

每个虚拟控制台被分配给对应于控制台编号的功能键。因此 vc1 将被分配给功能键 F1,依此类推。在这些会话之间切换很容易。在您的计算机上,您可以按住 Ctrl 和 Alt 键并按 F2 切换到 vc2。然后按住 Ctrl 和 Alt 键,按 F1 切换到 vc1 和图形界面。

在 Linux 计算机上访问命令行的最后一种方法是通过远程登录。安全 Shell(SSH)是最常见的远程访问方法。

如果您可以本地访问的计算机不适合运行这些实验,但是您可以访问远程计算机,也就是说,您可以 SSH 到该计算机来运行实验。对于某些实验,您需要多次登录。

我们将在第七章中详细介绍终端模拟器和控制台会话。

创建学生用户

作为 root 用户,您应该立即在您将用于这些实验的计算机上创建一个新用户,用户 ID 为“student”,不带引号。将密码设置为您能记住的任何合理安全的值。

制备 2-1

输入以下命令创建学生用户并分配密码。

[root@testvm1 ~]# useradd -c "Student User" student
[root@testvm1 ~]# passwd student
Changing password for user student.
New password: <Enter password>
Retype new password: <Enter password again>
passwd: all authentication tokens updated successfully.

准备 USB 拇指驱动器

你可以用一个没有其他用途的 u 盘安全地进行许多实验。当然,您将不得不在其上重新创建分区和文件系统,以便在我们完成时它可以再次使用。

我发现了一个旧的 USB 2.0 64MB 是的 MB-拇指驱动器,我目前没有其他用途,所以我设置它用于这些实验。你可以使用手头上任何大小的 u 盘,但是小的也可以。

准备 2-2

准备用于这些实验的 USB 设备。

  1. 在您将用于本实验的计算机上打开一个终端会话,并以 root 用户身份登录。

  2. 将 USB 设备插入 Linux 计算机上的 USB 插槽。

  3. 使用 dmesg 命令确定内核将哪个设备文件分配给了 USB 驱动器。可能会是/dev/sdb 之类的。dmesg 输出应该显示至少一个分区/dev/sdb1。在您的 Linux 计算机上,驱动器盘符(在本例中为 b)可能是不同的盘符。

    警告如果在命令中指定了错误的设备,以下步骤可能会导致生产系统上的数据完全销毁。确保使用非生产系统进行此实验。

  4. 在/mnt 上挂载驱动器的分区。

  5. 将 PWD 更改为/mnt。

  6. 删除任何先前存在的文件。

  7. 输入并运行以下命令,在驱动器上创建一些包含内容的文件。

    for I in 0 1 2 3 4 5 6 7 8 9 ; do dmesg > file$I.txt;done
    
    
  8. 验证驱动器上现在至少有 10 个名为 file0.txt 到 file9.txt 的文件。

  9. 将 PWD 更改为 root 的主目录。

  10. 卸载 USB 驱动器,并将其从计算机中移除,直到需要它为止。

USB 驱动器现在可以在我们的实验中使用了。

如果实验不成功该怎么办

这些实验是独立的,不依赖于任何设置,除了 USB 拇指驱动器或之前进行的实验的结果。某些 Linux 实用程序和工具必须存在,但这些都应该在标准 Fedora Linux 工作站安装或任何其他主流通用发行版中可用。

因此,所有这些实验都应该“正常工作”我们都知道那是怎么回事,对吧?因此,当某件事情失败时,首先要做的事情是显而易见的。

  1. 验证命令输入是否正确。这是我自己遇到的最常见的问题。

  2. 您可能会看到一条错误消息,指示找不到该命令。bash shell 显示了错误的命令;在这种情况下,我编造了 badcommand。然后给出了问题的简要描述。对于缺失和拼写错误的命令,都会显示此错误消息。多次检查命令的拼写和语法,以验证它是否正确。

    [student@testvm1 ~]$ badcommand
    bash: badcommand: command not found...
    
    
  3. 使用 man 命令查看手册页(手册页),以验证命令的语法和拼写是否正确。

  4. 确保实际上安装了所需的命令。如果尚未安装,请安装它们。

  5. 对于要求您以 root 身份登录的实验,请确保您已经这样做了。应该只有几个,但是作为非根用户执行它们是不行的。

应该不会有太多其他问题——但是如果你遇到一个你无法用这些技巧解决的问题,请通过LinuxGeek46@both.org联系我,我会尽力帮助你解决问题。

三、数据流

Linux 中的一切都围绕着数据流——尤其是文本流。

我最近在谷歌上搜索了“数据流”,大多数热门搜索都与处理单个实体中的大量流数据有关,如流视频和音频,或者金融机构处理由大量单个交易组成的流。这不是我们在这里讨论的内容,尽管概念是相同的,并且可以认为当前的应用使用 Linux 的流处理功能作为处理多种类型数据的模型。

在 Unix 和 Linux 世界中,流是源自某个源的流动文本数据;该流可以流向一个或多个程序,这些程序以某种方式对其进行转换,然后它可以存储在文件中或在终端会话中显示。作为系统管理员,您的工作与操纵这些数据流的创建和流动密切相关。在本章中,我们将探索数据流——什么是数据流,如何创建数据流,以及如何使用数据流。

文本流——通用界面

使用标准输入/输出(STDIO)进行程序输入和输出是 Linux 做事方式的关键基础。STDIO 最初是为 Unix 开发的,从那以后,它已经进入了大多数其他操作系统,包括 DOS、Windows 和 Linux。

这就是 Unix 哲学 :编写做一件事并把它做好的程序。写程序一起工作。写程序处理 文本流 ,因为那是通用接口。

—道格·麦克洛伊,Unix 基础知识 哲学 1,2

STDIO 由 Ken Thompson 3 开发,作为在早期版本的 Unix 上实现管道所需的基础设施的一部分。实现 STDIO 的程序使用标准化的文件句柄进行输入和输出,而不是存储在磁盘或其他记录媒体上的文件。STDIO 最好被描述为缓冲数据流,它的主要功能是将数据从一个程序、文件或设备的输出传输到另一个程序、文件或设备的输入。

STDIO 文件句柄

有三种 STDIO 数据流,每一种都在程序启动时作为一个文件自动打开——当然还有那些使用 STDIO 的程序。每个 STDIO 数据流都与一个文件句柄相关联,该文件句柄只是一组描述文件属性的元数据。文件句柄 0、1 和 2 被约定和长期实践分别明确定义为 STDIN、STDOUT 和 STDERR。

STDIN ,文件句柄 0,是通常从键盘输入的标准输入。STDIN 可以从任何文件重定向,包括设备文件,而不是键盘。与 STDOUT 或 STDERR 相比,重定向 STDIN 不太常见,但也可以很容易地完成。

STDOUT ,文件句柄 1,默认情况下是将数据流发送到显示器的标准输出。通常将 STDOUT 重定向到一个文件,或者通过管道将其传送到另一个程序进行进一步处理。

STDERR 与文件句柄 2 相关联。STDERR 的数据流通常也发送到显示器。

如果 STDOUT 被重定向到一个文件,STDERR 将继续显示在屏幕上。这确保了当数据流本身不在终端上显示时,STDERR 会显示出来,从而确保用户可以看到程序执行过程中产生的任何错误。STDERR 也可以重定向到同一个转换程序,或者传递到管道中的下一个转换程序。

STDIO 是作为 C 库 stdio.h 实现的,它可以包含在程序的源代码中,以便可以编译成最终的可执行文件。

生成数据流

大多数核心实用程序使用 STDIO 作为它们的输出流,而那些生成数据流的实用程序可以用来创建我们将在实验中使用的数据流,而不是以某种方式转换数据流。数据流可以短至一行甚至一个字符,也可以根据需要而定。 4

让我们尝试第一个实验,创建一个短数据流。

实验 3-1

如果您还没有这样做,请以用户“学生”的身份登录到您用于这些实验的主机如果您已经登录到 GUI 桌面会话,请启动您最喜欢的终端模拟器;如果您已经登录到一个虚拟控制台或终端模拟器,您就可以开始了。

使用下面显示的命令生成数据流。该命令以粗体显示。

[student@f26vm ~]$ ls -la
total 28
drwx------   3 student student 4096 Oct 20 01:25 .
drwxr-xr-x. 10 root    root    4096 Sep 21 10:06 ..
-rw-------   1 student student 1218 Oct 20 20:26 .bash_history
-rw-r--r--   1 student student   18 Jun 30 11:57 .bash_logout
-rw-r--r--   1 student student  193 Jun 30 11:57 .bash_profile
-rw-r--r--   1 student student  231 Jun 30 11:57 .bashrc
drwxr-xr-x   4 student student 4096 Jul  5 18:00 .mozilla

该命令的输出是一个短数据流,显示在 STDOUT(您登录的控制台或终端会话)上。

在第四章“转换数据流”中,我们将把像这样的 STDOUT 数据流通过管道传输到一些 transformer 程序的 STDIN,以便对流中的数据执行一些操作。目前,我们只是在生成数据流。

一些 GNU 核心工具是专门为产生数据流而设计的。

实验 3-2

yes命令产生一个连续的数据流,该数据流由作为参数提供的数据串的重复组成。生成的数据流将继续,直到被 Ctrl-C 中断,在屏幕上显示为^C.

输入如图所示的命令,并让它运行几秒钟。当你厌倦了看着同一串数据滚动时,按 Ctrl-C。

[student@f26vm ~]$ yes 123465789-abcdefg
123465789-abcdefg
123465789-abcdefg
123465789-abcdefg
123465789-abcdefg
123465789-abcdefg
123465789-abcdefg
123465789-abcdefg
1234^C

“这证明了什么?,“你问吧。只是有许多方法可以创建有用的数据流。例如,您可能希望自动响应来自fsck程序的似乎无休止的“y”输入请求,以修复硬盘上的问题。这种解决方案可以节省很多按“y”键的次数。

实验 3-3

要查看yes如何生成一串“y”字符,再次尝试没有字符串参数的yes命令,如实验 3-2,您将得到一串“y”字符作为输出。

[student@f26vm ~]$ yes
y
y
y
y
y
y
y

^C

现在,这是你绝对不应该尝试的事情。当以 root 用户身份运行时,rm *命令将删除当前工作目录(pwd)中的每个文件,但它会要求您为每个文件输入“y”以确认您确实想要删除该文件。这意味着更多的打字。

代码示例 3-1

我还没有谈到管道,但是作为一名系统管理员,或者想成为一名系统管理员的人,你应该已经知道如何使用它们了。下面的 CLI 程序将对 rm 命令的每个请求提供“y”响应,并将删除所有文件。

yes | rm *

警告!不要运行这个命令,因为它会删除当前工作目录中的所有文件。

当然,您也可以使用rm -f *,这也将强制删除 PWD 中的所有文件。f 表示“强制”删除。这也是你不应该做的事情。

用“是”来检验一个理论

使用 yes 命令的另一个选项是用一个包含一些任意的和非常不相关的数据的文件来填充目录,以便——嗯——填满目录。我已经使用这种技术测试了当一个特定的目录变满时,Linux 主机会发生什么。在我使用这种技术的具体例子中,我正在测试一个理论,因为一个客户遇到了问题,无法登录到他们的计算机。

注意

在这一系列实验中,我假设 USB 驱动器在/dev/sdb 上,其分区是/dev/SD B1——因为它在我的虚拟机上——确保您验证了它在您的计算机上分配的设备,因为它可能会有所不同。根据您的情况使用正确的设备文件 5

实验 3-4

这个实验应该以 root 用户身份进行。

为了防止填满您的根文件系统,这个实验将使用您应该提前准备好的 USB 设备。该实验不会影响设备上的现有文件。

你确实准备了那个 u 盘,不是吗?如果没有,那么现在就回到第一章。我会等…

准备好了吗?太好了!

  1. 现在将 USB 驱动器插入计算机上的一个 USB 插槽中。

  2. 使用dmesg命令查看 USB 驱动器的信息,并确定其分配的设备文件。应该是/dev/sdb 之类的。确保为您的设备使用正确的设备文件。

  3. 在我的系统上的/mnt 上挂载 USB 设备文件系统分区/dev/sdb1。

  4. 运行下面粗体显示的命令。由于页面宽度有限,这里显示的一些结果已经换行,但是您已经了解了。

根据你的 USB 文件系统的大小,填充它的时间可能会有所不同,但是应该会很快。

[root@testvm1 ~]# yes 123456789-abcdefgh >> /mnt/testfile.txt

yes: standard output: No space left on device
[root@testvm1 ~]# df -h /mnt
Filesystem                       Size  Used Avail Use% Mounted on

/dev/sdb1                         62M   62M  2.0K 100% /mnt
[root@testvm1 ~]# ls -l /mnt
total 62832
-rwxr-xr-x 1 root root    37001 Nov  7 08:23 file0.txt
-rwxr-xr-x 1 root root    37001 Nov  7 08:23 file1.txt
-rwxr-xr-x 1 root root    37001 Nov  7 08:23 file2.txt
-rwxr-xr-x 1 root root    37001 Nov  7 08:23 file3.txt
-rwxr-xr-x 1 root root    37001 Nov  7 08:23 file4.txt
-rwxr-xr-x 1 root root    37001 Nov  7 08:23 file5.txt
-rwxr-xr-x 1 root root    37001 Nov  7 08:23 file6.txt
-rwxr-xr-x 1 root root    37001 Nov  7 08:23 file7.txt
-rwxr-xr-x 1 root root    37001 Nov  7 08:23 file8.txt
-rwxr-xr-x 1 root root    37001 Nov  7 08:23 file9.txt
-rwxr-xr-x 1 root root 63950848 Dec  7 13:16 testfile.txt

你的结果看起来会有些不同,但肯定会和我的相似。

请务必查看从df输出指向/dev/sdb1 设备的那一行。这表明该文件系统上 100%的空间都被使用了。

现在从/mnt 中删除 testfile.txt 并卸载该文件系统。

作为测试的一部分,我在自己的一台计算机的/tmp 目录下使用了实验 3-4 中的简单测试来帮助我确定客户的问题。在/tmp 填满之后,用户不再能够登录 GUI 桌面,但是他们仍然可以使用控制台登录。这是因为登录 GUI 桌面会在/tmp 目录中创建文件,并且没有剩余空间,所以登录失败。控制台登录没有在/tmp 中创建新文件,因此它们成功了。我的客户没有尝试登录控制台,因为他们不熟悉 CLI。

在我自己的系统上进行验证之后,我使用控制台登录到客户主机,发现许多大文件占据了/tmp 目录中的所有空间。我删除了这些文件,并帮助客户确定文件是如何创建的,我们能够阻止这种情况。

探索 USB 驱动器

现在是做一些探索的时候了,为了尽可能的安全,您将使用您之前准备的 USB 拇指驱动器。在这个实验中,我们将研究一些文件系统结构。

先说简单的。您至少应该对dd命令有所了解。正式名称是“磁盘转储”,许多系统管理员称之为“磁盘破坏者”是有道理的。我们中的许多人都曾使用dd命令无意中破坏了整个硬盘或分区的内容。这就是为什么我们将使用 USB 驱动器来执行这些实验。

dd命令是一个强大的工具,它允许我们使用任何文件或设备生成数据流,如硬盘驱动器、磁盘分区、RAM 内存、虚拟控制台、终端仿真会话、STDIO 等等,作为源和目标。因为dd命令不修改这些数据流,所以它允许我们访问原始数据,这样我们就可以查看和分析它。

随着我们在这一系列实验中的进展,你会看到由dd生成的数据流可以用于许多不同的目的。这是我最喜欢的探索文件和设备的工具之一。

实验 3-5

本实验不需要安装 USB 驱动器;事实上,如果不安装设备,这个实验会给人留下更深刻的印象。如果 USB 设备当前已安装,请将其卸载。

以 root 用户身份登录到终端会话。

作为终端会话中的 root 用户,使用 dd 命令查看 USB 驱动器的引导记录,假设它被分配给/dev/sdb 设备。bs=参数不是您可能会想到的;它只是指定块大小,count=参数指定转储到 STDIO 的块数。of=参数指定数据流的来源,在本例中是 USB 设备。

[root@f26vm ~]# dd if=/dev/sdb bs=512 count=1
•>•MSWIN4.1P•} •••)L•0NO NAME    FAT16   •}•3•••{••x•vVU•"•~•N•
•••|•E••F•E••8f$|•r<•F••fFVF•PR•F•V•• •v••^
•H••F•N•ZX••••rG8-t•
V•v>•^tJNt
••F•V••S••[r•?MZu•••BJu••pPRQ••3••v••vB•••v••V$•••••••t<•t      •••••}•
•}••3••^••D•••}•}••r••HH•N          ̸
•YZXr   @uB^
•••'
Invalid system disk•
Disk I/O error•
Replace the disk,!••U•

1+0 records in
1+0 records out
512 bytes copied, 0.0116131 s, 44.1 kB/s

这将打印引导记录的文本,引导记录是磁盘上的第一个块——任何磁盘。在这种情况下,有关于文件系统和分区表的信息,尽管因为它是以二进制格式存储的,所以不可读。如果这是一个可引导设备,GRUB 的第一阶段或其他引导加载程序将位于这个扇区。我在引导记录本身后面添加了几个换行符,以便阐明扇区中数据的结尾和 dd 命令本身打印的信息。最后三行包含关于记录数和处理的字节数的数据。

现在让我们做同样的实验,但是是在第一个分区的第一条记录上。

实验 3-6

USB 设备仍应被插入和卸载,并且您仍应以 root 用户身份登录。

  1. 运行以下命令。

    [root@f26vm ~]# dd if=/dev/sdb1 bs=512 count=1
    •<•mkfs.fat•|••)•GR•NO NAME    FAT16   •[|•"•t
                                                  V•••^••2•••••This is not a bootable disk.  Please insert a bootable floppy and
    press any key to try again ...
    U•1+0 records in
    1+0 records out
    512 bytes copied, 0.0113664 s, 45.0 kB/s
    
    

这个实验显示了引导记录和分区的第一个记录之间的差异。它还显示了dd命令可以用来查看分区中的数据以及磁盘本身的数据。

让我们看看 u 盘里还有什么。根据您在这些实验中使用的 USB 设备的具体情况,您可能会得到与我不同的结果。我会告诉你我做了什么,你可以修改,如果有必要达到预期的结果。

我们正在尝试使用dd命令来定位我们在 USB 驱动器上创建的文件的目录条目,然后是一些数据。如果我们对元数据结构有足够的了解,我们可以直接解释它们来找到这些数据在驱动器上的位置,但是我们没有,所以我们将不得不硬着头皮这样做——打印出数据,直到我们找到我们想要的。

所以,让我们从我们所知道的开始,然后用一点技巧继续。我们知道我们在 USB 设备准备期间创建的数据文件在设备的第一个分区中。因此,我们不需要搜索引导记录和第一个分区之间的空间,其中包含许多空白。至少这是它应该包含的内容。

从/dev/sdb1 的开头开始,让我们一次查看几个数据块,找到我们想要的。实验 3-7 中的命令与前一个相似,只是我们指定了更多的数据块来查看。如果您的终端不够大,无法一次显示所有数据,您可能必须指定较少的块,或者您可以通过 less 实用程序传输数据,并使用它来逐页浏览数据。两种方式都可行。请记住,我们是以 root 用户的身份执行所有这些操作的,因为非 root 用户没有所需的权限。

实验 3-7

输入与上一个实验中相同的命令,但是将显示的块数增加到 10,如下所示,以便显示更多数据。

[root@f26vm ~]# dd if=/dev/sdb1 bs=512 count=10
•<•mkfs.fat•|••)•GR•NO NAME    FAT16   •[|•"•t
                                              V•••^••2•••••This is not a bootable disk.  Please insert a bootable floppy and
press any key to try again ...
U•••••

•• !"#$%&'(••*+,-./0123456789:;••=>?@ABCDEFGHIJKLMN••PQRSTUVWXYZ[\]^_`a••cdefghijklmnopqrst••vwxyz{|}~••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••10+0 records in
10+0 records out
5120 bytes (5.1 kB, 5.0 KiB) copied, 0.019035 s, 269 kB/s

这里没有太大的不同,但是让我们看得更远一点。

让我们看看dd命令的一个新选项,它给了我们更多的灵活性。

实验 3-8

我们仍然希望一次显示大约 10 个数据块,但是我们不想从分区的开始处开始,我们希望跳过我们已经查看过的数据块。

输入以下命令并添加skip=10参数,它会跳过前 10 个数据块并显示接下来的 10 个数据块。

[root@f26vm ~]# dd if=/dev/sdb1 bs=512 count=10 skip=10
10+0 records in
10+0 records out
5120 bytes (5.1 kB, 5.0 KiB) copied, 0.01786 s, 287 kB/s

我们在实验 3-8 中看到,分区的后 10 个块是空的;那就是。它们包含空值,不打印,因为它们是空的-什么也不是。我们可以在分区的开始处继续跳过越来越多的块,或者在计数和跳过参数中使用更大的增量,比如 20 和 20。但我希望能为你节省一些时间。我发现如果我跳过 250 个块,就会显示目录条目。如果您的 USB 驱动器大小不同或格式不同,这可能不适合您,但这应该是一个好的起点。

实验 3-9

现在输入 dd 命令,跳过 250 块。

[root@f26vm ~]# dd if=/dev/sdb1 bs=512 count=10 skip=250
Afile0•.txt••••••FILE0   TXT •jgKgK•jgK••Afile1•.txt••••••FILE1TXT
•jgKgK•jgK••Afile2•.txt••••••FILE2   TXT •jgKgK•jgK)••Afile3•.txt••••••FILE3   TXT •jgKgK•jgK<••Afile4•.txt••••••FILE4   TXT •jgKgK•jgKO••Afile5•.txt••••••FILE5   TXT •jgKgK•jgKb••Afile6A.txt••••••FILE6   TXT •jgKgK•jgKu••Afile7E.txt••••••FILE7   TXT •jgKgK•jgK•••Afile8•.txt••••••FILE8   TXT •jgKgK•jgK•••Afile9M.txt••••••FILE9   TXT •jgKgK•jgK•••10+0 records in
10+0 records out
5120 bytes (5.1 kB, 5.0 KiB) copied, 0.0165904 s, 309 kB/s

如果您在第一次尝试时没有看到类似于上面所示的目录,请尝试更改要跳过的块数,然后再次运行该实验。我们的技术审查人员确实找到了目录,但使用了非常不同的跳过计数。

该命令的输出显示了/dev/sdb1 分区目录中包含的数据。这表明目录只是分区上的数据,就像任何其他数据一样。

我还发现跳过 500 个块会显示其中一个文件的数据,如下面的实验 3-10 所示。

实验 3-10

这次输入dd命令,跳过计数为 5 的 500 个块,只显示 5 个块。注意,这些结果是换行的,但是dmesg中的每一行都以时间戳开始。

[root@f26vm ~]# dd if=/dev/sdb1 bs=512 count=5 skip=500
msg='unit=systemd-journald comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
[    6.430317] audit: type=1131 audit(1509824958.916:49): pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=systemd-journald comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
[    6.517686] audit: type=1305 audit(1509824959.007:50): audit_enabled=1 old=1 auid=4294967295 ses=4294967295 res=1
[    6.665314] audit: type=1130 audit(1509824959.154:51): pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=systemd-journald comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
[    6.671171] audit: type=1130 audit(1509824959.160:52): pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=kmod-static-nodes comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
[    6.755493] audit: type=1130 audit(1509824959.244:53): pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=systemd-sysctl comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
[    9.782860] RAPL PMU: hw unit of domain pp0-core 2^-0 Joules
[    9.783651] RAPL PMU: hw unit of domain package 2^-0 Joules
[    9.784427] RAPL PMU: hw unit of domain pp1-gpu 2^-0 Joules
[    9.785611] ppdev: user-space parallel port driver
[    9.948408] Adding 4177916k swap on /dev/mapper/fedora_f26vm-swap.  Priority:-1 extents:1 across:4177916k FS
[   10.082485] snd_intel8x0 0000:00:05.0: white list rate for 1028:0177 is 48000
[   10.441113] EXT4-fs (sda1): mounted filesystem with ordered data mode. Opts: (null)
[   11.456654] kauditd_printk_skb: 15 callbacks suppressed
[   11.457548] audit: type=1130 audit(1509824963.942:69): pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=lvm2-pvscan@8:2 comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
[   11.523286] audit: type=1130 audit(1509824964.012:70): pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=systemd-fsck@dev-mapper-fedora_f26vm\x2dhome co5+0 records in
5+0 records out
2560 bytes (2.6 kB, 2.5 KiB) copied, 0.0223881 s, 114 kB/s

我不知道数据来自哪个文件。如果我们真的想的话,我们可以算出来,但是这对于本书的目的来说是不必要的。请注意,在您的驱动器上,目录和文件本身的位置可能不同。你可能需要搜索一下才能找到它们,但这是它们在我的设备上的位置。

你绝对应该花些时间自己探索一下 u 盘的内容。你可能会对你的发现感到惊讶。

一连串的随机性

事实证明,随机性在计算机中是一种可取的东西。谁知道呢。系统管理员想要生成随机数据流的原因有很多。从其他来源(如文件或硬盘分区等设备)生成的数据流应该包含非随机数据,这些数据可能会被黑帽黑客用来获取私人或机密数据。使用保证是随机的数据流提供了一个更安全的选择。

随机数据流有时有助于覆盖整个分区的内容,例如/dev/sda1,甚至是/dev/sda 中的整个硬盘驱动器。

尽管删除文件似乎是永久性的,但事实并非如此。许多取证工具是可用的,并且可以被训练有素的取证专家用来容易地恢复假定已经被删除的文件。恢复被随机数据覆盖的文件要困难得多。我经常不仅需要删除硬盘上的所有数据,还需要覆盖它,使其无法恢复。我这样做是为了客户和朋友,他们把旧电脑“送给”我,让我重复使用或回收。

不管电脑最终会发生什么,我向捐赠电脑的人保证,我会清除硬盘上的所有数据。我从计算机中取出驱动器,将它们放入我的插入式硬盘驱动器扩展坞,并使用类似于实验 3-11 中的命令来覆盖所有数据,但不是像本实验中那样将随机数据输出到 STDOUT。我将其重定向到需要覆盖的硬盘驱动器的设备文件,但不要这样做。

实验 3-11

输入这个命令将无休止的随机数据流打印到 STDOUT。

[student@testvm1 ~]$ cat /dev/urandom

使用Ctrl-C中断和停止数据流。

如果你非常偏执,可以使用shred命令来覆盖单个文件以及分区和完整的驱动器。它可以根据需要多次覆盖设备,让您感到安全,多次使用随机数据以及专门设计的有序数据模式,以防止即使是最敏感的设备也无法从硬盘上恢复任何数据。与其他使用随机数据的实用程序一样,随机流是由/dev/urandom 设备提供的。

随机数据也用作程序的输入种子,这些程序生成用于科学和统计计算的随机密码、随机数据和数字。我将在第四章中更详细地介绍随机性和其他有趣的数据源:“一切都是一个文件。”

摘要

在本章中,您了解到 STDIO 只不过是数据流。这些数据几乎可以是任何内容,可以是列出目录中文件的命令输出,也可以是来自特殊设备(如/dev/urandom)的无休止的数据流,甚至可以是包含来自硬盘驱动器或分区的所有原始数据的数据流。您学习了一些不同且有趣的方法来生成不同类型的数据流,以及如何使用dd命令来探索硬盘的内容。

Linux 计算机上的任何设备都可以被视为数据流。您可以使用像ddcat这样的普通工具将设备中的数据转储到 STDIO 数据流中,该数据流可以使用其他普通的 Linux 工具进行处理。

到目前为止,除了查看这些数据流,我们还没有对它们做任何事情。但是等等,还有更多!请继续阅读。

四、转换数据流

本章介绍了使用管道将数据流从一个实用程序连接到另一个使用 STDIO 的实用程序。你将了解到这些程序的功能是以某种方式转换数据。您还将了解如何使用重定向将数据重定向到文件。

我将术语“transform”与这些程序结合使用,因为每个程序的主要任务是按照系统管理员的意图,以特定的方式转换来自 STDIN 的输入数据,并将转换后的数据发送到 STDOUT,以供另一个 transformer 程序使用或重定向到一个文件。

标准术语“过滤器”暗示了一些我不同意的东西。根据定义,过滤器是一种去除某些东西的装置或工具,例如空气过滤器可以去除空气中的污染物,这样汽车的内燃机就不会被这些微粒磨死。在我高中和大学的化学课上,滤纸被用来去除液体中的微粒。我家暖通空调系统中的空气过滤器去除了我不想呼吸的微粒。

虽然它们有时会从流中过滤掉不需要的数据,但我更喜欢“转换器”这个术语,因为这些实用程序做得更多。他们可以向数据流中添加数据,以一些令人惊讶的方式修改数据,对数据进行排序,重新排列每行中的数据,根据数据流的内容执行操作,等等。

随便你用哪个词,但我更喜欢变形金刚。

作为原材料的数据流

数据流是核心实用程序和许多其他 CLI 工具执行工作所依赖的原始资料。顾名思义,数据流是使用 STDIO 从一个文件、设备或程序传递到另一个文件、设备或程序的数据流。

可以通过使用管道将转换器插入数据流来操作数据流。SysAdmin 使用每个 transformer 程序对流中的数据执行一些操作,从而以某种方式改变其内容。然后可以在管道末端使用重定向将数据流定向到文件。如前所述,该文件可以是硬盘上的实际数据文件,也可以是设备文件,如驱动器分区、打印机、终端、伪终端或任何其他连接到计算机的设备。

使用这些小而强大的 transformer 程序操纵这些数据流的能力是 Linux 命令行界面的核心。许多核心实用程序都是 transformer 程序,并使用 STDIO。

白日梦

管道对于我们在命令行上做令人惊奇的事情至关重要,以至于我认为认识到它们是由道格拉斯·麦克洛伊 2 在 Unix 早期发明的是很重要的。谢谢道格。普林斯顿大学的网站上有一段对麦克洛伊的采访片段 3 ,其中他讨论了管道的创建和 Unix 哲学的开端。

请注意实验 4-1 中显示的简单命令行程序中管道的使用,该程序一次列出每个登录用户,不管他们有多少个活动登录。

实验 4-1

如果您尚未这样做,请打开一个终端会话,以学生用户身份登录,然后以 root 用户身份登录第二个终端会话。

在一行中输入如下所示的命令。

[student@testvm1 ~]$ w | tail -n +3 | awk '{print $1}' | sort | uniq
root
student

您还可以使用sort -u来代替uniq转换器,以确保每个登录 ID 只打印一个实例。输入下面的命令试试看。

[student@testvm1 ~]$ w | tail -n +3 | awk '{print $1}' | sort -u
root
student

这些命令的结果产生两行数据,显示用户 root 和 student 都已登录。它不显示每个用户登录的次数。

实验 4-1 中的两条命令管道产生了相同的结果。在这个实验中,至少还有一种方法可以改变命令管道,同时仍然生成相同的结果。你能找到它吗?有许多方法可以完成同样的任务。没有对错,只是不同而已。在我看来,使用第二种形式既简单又优雅。我们将在第十七章“追求优雅”和第十八章“寻找简单”中讨论这些属性。"

管道——由竖线(|)表示——是将这些命令行实用程序连接在一起的语法粘合剂、操作符。管道允许一个命令的标准输出被“管道化”,即从一个命令的标准输出流到下一个命令的标准输入。

img/462716_1_En_4_Figa_HTML.png

用管道连接的一串程序被称为管道,使用 STDIO 的程序被正式称为过滤器,但我更喜欢用转换器这个术语。

想想如果我们不能将数据流从一个命令传输到下一个命令,这个程序将如何工作。第一个命令将对数据执行任务,然后该命令的输出必须保存在一个文件中。下一个命令必须从中间文件中读取数据流,并对数据流进行修改,将自己的输出发送到一个新的临时数据文件中。第三个命令将不得不从第二个临时数据文件中取出它的数据,并执行它自己的数据流操作,然后将得到的数据流存储在另一个临时文件中。在每一步,数据文件名必须以某种方式从一个命令传递到下一个命令。

我甚至无法忍受去想它,因为它太复杂了。记住简单就是摇滚!

建设管道

当我做一些新的事情,解决一个新的问题时,我通常不会像实验 4-1 那样,从头开始输入一个完整的 bash 命令管道。我通常从管道中的一两个命令开始,然后通过添加更多命令来进一步处理数据流。这允许我在管道中的每个命令之后查看数据流的状态,并在需要时进行纠正。

在实验 4-2 中,你应该输入每行显示的命令,并按所示运行它以查看结果。这将让你感受到如何分阶段构建复杂的管道。

实验 4-2

输入每行显示的命令。当使用管道将每个新的 transformer 实用程序添加到数据流中时,观察数据流中的变化。第一次通过时,使用uniq工具。这个实验的最终结果将与实验 4-1 的结果相同。

[student@f26vm ~]$ w

[student@f26vm ~]$ w | tail -n +3

[student@f26vm ~]$ w | tail -n +3 | awk '{print $1}'

[student@f26vm ~]$ w | tail -n +3 | awk '{print $1}' | sort

[student@f26vm ~]$ w | tail -n +3 | awk '{print $1}' | sort | uniq
Now let’s also use the alternate form of this last command.

[student@f26vm ~]$ w | tail -n +3 | awk '{print $1}' | sort -n

这个实验的结果说明了由管道中的每个 transformer 实用程序执行的对数据流的改变。

可以构建非常复杂的管道,使用许多与 STDIO 一起工作的不同实用程序来转换数据流。

重寄

重定向是将程序的标准输出数据流重定向到一个文件而不是显示的默认目标的能力。“大于号”(>)字符,也称为“gt”,是重定向的语法符号。实验 4-3 展示了如何将df -h命令的输出数据流重定向到文件 diskusage.txt。

实验 4-3

重定向命令的 STDOUT 可用于创建包含该命令结果的文件。

[student@f26vm ~]$ df -h > diskusage.txt

除非出现错误,否则该命令不会向终端输出任何内容。这是因为 STDOUT 数据流被重定向到文件,而 STDERR 仍然被定向到 STDOUT 设备,即显示器。您可以使用下面的命令查看刚刚创建的文件的内容。

[student@f26vm ~]$ cat diskusage.txt
Filesystem                     Size  Used Avail Use% Mounted on
devtmpfs                       2.0G     0  2.0G   0% /dev
tmpfs                          2.0G     0  2.0G   0% /dev/shm
tmpfs                          2.0G  988K  2.0G   1% /run
tmpfs                          2.0G     0  2.0G   0% /sys/fs/cgroup
/dev/mapper/fedora_f26vm-root   49G   11G   36G  24% /
tmpfs                          2.0G     0  2.0G   0% /tmp
/dev/sda1                      976M  158M  752M  18% /boot
/dev/mapper/fedora_f26vm-home   25G   45M   24G   1% /home
tmpfs                          396M     0  396M   0% /run/user/991
tmpfs                          396M     0  396M   0% /run/user/1001

使用>符号进行重定向时,如果指定的文件尚不存在,则会创建该文件。如果它已经存在,内容将被来自命令的数据流覆盖。如实验 4-4 所示,你可以使用双大于号>>,将新的数据流附加到文件中任何现有的内容上。

实验 4-4

该命令将新数据流追加到现有文件的末尾。

[student@f26vm ~]$ df -h >> diskusage.txt

您可以使用 cat 和/或更低版本查看 diskusage.txt 文件,以验证新数据是否已附加到文件末尾。

使用重定向到标准输入的一个例子是使用od命令,如实验 4-5 所示。N 50 选项防止输出永远持续下去。如果不使用-N 选项来限制输出数据流,可以使用 Ctrl-C 来终止它。

实验 4-5

这个实验演示了使用重定向作为 STDIN 的输入。

[student@f26vm ~]$ od -c -N 50 < /dev/urandom  
0000000 331 203   _ 307   ]   { 335 337   6 257 347       $   J   Z   U
0000020 245  \0   `  \b   8 307 261 207   K   :   }   S   \ 276 344   ;
0000040 336 256 221 317 314 241 352   ` 253 333 367 003 374 264 335   4
0000060   U  \n 347   (   h 263 354 251   u   H   ] 315 376   W 205  \0
0000100 323 263 024   % 355 003 214 354 343   \   a 254   #   `   {   _
0000120   b 201 222   2 265   [ 372 215 334 253 273 250   L   c 241 233
<snip>

本实验中的字体大小已经减小,因此无需换行即可适应。理解结果的本质要容易得多。

重定向可以是管道的来源,也可以是管道的终点。因为很少需要它作为输入,重定向通常被用作管道的终止。

重定向标准错误

STDERR 被设计为打印在 STDERR 设备上——通常是与 STDOUT 相同的终端会话——以确保错误消息被显示出来,并且可以被系统管理员查看,而不是通过管道传递并可能丢失。即使 STDOUT 被重定向或通过管道传送到管道的下一级,STDERR 通常也会显示在终端上。

实验 4-6 说明了 STDERR 数据流的默认行为,然后继续展示如何创建替代行为。

实验 4-6

让我们通过在您的主目录中创建一些测试文件来开始这个实验。在一行中输入以下命令。

[student@testvm1 ~]$ for I in 0 1 2 3 4 5 6 7 8 9;do echo "This is file $I" > file$I.txt;done

现在使用 cat 命令连接其中三个文件的内容。在这一点上,我们仍然不期望任何错误,只是设置阶段。

[student@testvm1 ~]$ cat file0.txt file4.txt file7.txt > test1.txt
[student@testvm1 ~]$ cat test1.txt
This is file 0
This is file 4
This is file 7

到目前为止,一切正常。现在让我们更改命令,通过指定一个不存在的文件来生成一个简单的错误。我们指定了不存在的 filex.txt,而不是 file4.txt。

[student@testvm1 ~]$ cat file0.txt filex.txt file7.txt > test1.txt
cat: filex.txt: No such file or directory
[student@testvm1 ~]$ cat test1.txt
This is file 0
This is file 7

当数据仍然被重定向到 test1.txt 时,cat 命令生成的错误消息出现在终端上。

[student@testvm1 ~]$ cat file0.txt filex.txt file7.txt &> test1.txt
[student@testvm1 ~]$ cat test1.txt
This is file 0
cat: filex.txt: No such file or directory
This is file 7

在上面的命令中,STDOUT 和 STDERR 都被重定向到文件 test1.txt。现在让我们假设我们希望 STDOUT 继续被发送到终端,而我们并不关心错误消息。为此,我们将 STDERR 重定向到/dev/null。 4 首先我们确保 test1.txt 为空,这样就不会有任何数据存储在其中而混淆结果。

[student@testvm1 ~]$ echo "" > test1.txt
[student@testvm1 ~]$ cat test1.txt

[student@testvm1 ~]$ cat file0.txt filex.txt file7.txt 2> 
/dev

/
null

This is file 0
This is file 7
[student@testvm1 ~]$ cat test1.txt

[student@testvm1 ~]$

我们还可以将 STDERR 重定向到 test1.txt 文件,同时仍然将 STDOUT 发送到终端。

[student@testvm1 ~]$ cat file0.txt filex.txt file7.txt 2> test1.txt
This is file 0
This is file 7
[student@testvm1 ~]$ cat test1.txt
cat: filex.txt: No such file or directory
[student@testvm1 ~]$

我们可能还会发现将 STDOUT 重定向到一个文件并将 STDERR 重定向到另一个文件是很有用的。这看起来像下面的命令。

[student@testvm1 ~]$ cat file0.txt filex.txt file7.txt 1> good.txt 2> error.txt
[student@testvm1 ~]$ cat good.txt
This is file 0
This is file 7
[student@testvm1 ~]$ cat error.txt
cat: filex.txt: No such file or directory
[student@testvm1 ~]$

重定向提供的灵活性使我们有可能以一种非常优雅的方式执行一些令人惊奇的事情。例如,我有一些脚本会输出大量的输出,这使得很难确定是否发生了任何错误。通过将 STDOUT 重定向到一个日志文件,将 STDERR 重定向到另一个日志文件,我可以很容易地确定是否有任何错误,而不必搜索将近一兆字节的数据。

管道挑战

我为 Opensource.com5写了很多东西,几年前我向我们的读者提出了一个挑战,其中涉及到管道作为解决方案的必要组件。这是一个简单的问题,有一个我经常使用的解决方案。

问题

我有多台计算机配置为向我自己的电子邮件帐户发送管理电子邮件。我已经在我的邮件服务器上配置了 procmail,将这些管理邮件中的大部分移到一个文件夹中,这样就可以很容易地找到它们。在过去的几年里,我在那个文件夹里收集了超过 50,000 封电子邮件。这些电子邮件包括来自 rkhunter (Rootkit hunter)、logwatch、cron jobs 和 Fail2Ban 等的输出。

我感兴趣的消息来自 Fail2Ban,这是一个免费的开源软件,可以动态禁止试图恶意访问我自己主机的主机的 IP 地址,主要是互联网上的防火墙。Fail2Ban 通过向 IPTables 添加规则来做到这一点。每当一个 IP 地址因多次 SSH 登录尝试失败而被禁止时,Fail2Ban 就会发送一封电子邮件。

挑战的目标是创建一个命令行程序来计算来自每个 IP 地址的试图使用 SSH 访问我的主机的电子邮件数量。参赛者将下载 admin.index 文件,其中包含从我的电子邮件客户端导出的 CSV 数据,以及从电子邮件中提取的超过 50,000 个主题行。所有的主题行都包含在参赛者可用的数据中,因此部分任务是只提取与禁止的 SSH 连接相关的主题行。图 4-1 中显示了参赛者可用数据的一个小样本。请注意,图中有一些线条被包裹起来,但你明白了。

img/462716_1_En_4_Fig1_HTML.png

图 4-1

挑战中使用的 CSV 数据示例

规则规定命令行程序应该只有一行,并且必须使用管道将数据从一个命令传送到下一个命令。对于额外的学分,结果可以包括每个 IP 地址的国家的名称。

解决方案

我们收到了居住在世界各地许多国家的 Opensource.com 读者的来信。有些人提交了多个解决方案,但竞赛规则规定,只有参赛者的第一个解决方案将被考虑。因此,一些好的参赛作品不得不被取消资格,因为它们是同一个人的第二次或第三次参赛作品。

我有自己非常简单的解决方案,如图 4-2 所示。然而,即使我有资格,也不会是赢家。事实上,许多参赛作品提供了比我自己更好的解决方案。

img/462716_1_En_4_Fig2_HTML.png

图 4-2

我自己解决问题的方法

我自己的解决方案提供了一个按 IP 地址升序排序的列表,其中大多数条目的源数据来自 admin.index 文件。我的解决方案中的最后一种方法并不是赢得比赛的必要条件,但它是我喜欢做的事情,以查看大多数攻击是从哪里发出的。

我的解决方案产生了 5,377 行输出,所以大约有这个数量的唯一 IP 地址。然而,我的解决方案没有考虑到一些没有 IP 地址的异常条目。当我在思考命令行程序在这个挑战中的目标时,我决定不指定应该产生的行数,因为我觉得这可能限制太多,并且会对条目施加不必要的约束。我认为这是一个好主意,因为我们收到的许多条目产生了多少有些不同的数字。所以一个成功的解决方案不需要产生和我的解决方案一样多的数据行。

带解决方案的第一个条目

美国新泽西州汉密尔顿的迈克尔·迪多梅尼科提交了比赛的第一份参赛作品,这也是一份工作。我特别喜欢 Michael 使用 sort 命令来确保输出按照 IP 地址排序。

Michael 的输入,如图 4-3 所示,产生 5295 行输出,与我自己的结果相差不大。这也是许多其他条目产生的输出行数。

img/462716_1_En_4_Fig3_HTML.png

图 4-3

Michael DiDomenico 提交了第一个带有正确答案的条目

最短解决方案

有资格获奖的最短解决方案是由西班牙马德里的 Víctor Ochoa Rodríguez 提交的。他在图 4-4 中的 65 个字符的解决方案非常优雅,使用 egrep 仅选择包含 SSH 和 IP 地址的行,同时仅打印每行中与表达式匹配的部分。我从这个条目中了解到了-o 选项,所以感谢 Víctor 提供的这一点新知识。

img/462716_1_En_4_Fig4_HTML.png

图 4-4

维克多·奥乔亚·罗德里格斯提交了这个解决方案,这是有资格获奖的最短的一个

图 4-5 显示了另一个实际上比 Víctor 更短的条目。Teresa e Junior 提交了一个长度为 58 个字符的条目。她没有资格在竞赛中获奖,但她的解决方案至少应该在这个类别中得到非正式的认可。

img/462716_1_En_4_Fig5_HTML.png

图 4-5

小特雷莎·e·提交的这份文件是所有文件中最短的

这两种解决方案都会产生 5,295 行输出。

最有创意的解决方案

前两个类别可以根据纯粹的客观标准来判断,所以我希望有这个类别来提供一个额外的机会,以表彰那些提出更有创意的答案的人。这一类别的结果是基于我的纯主观意见,在我看来,这一类别有一个平局。

爱尔兰科克公司的 Przemo Firszt 提交了图 4-6 中的参赛作品,该作品因其对 tee 和 xargs 命令的使用而非常有趣且富有创意。它的独特之处还在于,除了使用管道之外,它还使用 tee 命令将中间数据存储在一个文件中,该命令还将数据传递给 STDOUT,最终输出被重定向到另一个文件,而不是允许输出到 STDOUT。它甚至在最后通过删除临时文件来进行清理。

img/462716_1_En_4_Fig6_HTML.png

图 4-6

Przemo Firszt 提交了这个使用 tee 和 xargs 的创意作品

这个解决方案产生 7,403 行输出。这似乎是因为许多 IP 地址有多行。因此,尽管这不是一个完美的解决方案,但只需很少的修改就可以为每个 IP 地址生成一行输出。

美国德克萨斯州弗里斯科的蒂姆·蔡斯。,是这个类别的另一个赢家。Tim 的条目如图 4-7 所示,其独特之处在于它使用 curl 命令从服务器下载文件,然后它使用 awk 命令选择文件中所需的行,并仅从每一行中选择 IP 地址。Tim 的解决方案是唯一包含执行文件下载的代码的解决方案。它产生 5,295 行输出。

img/462716_1_En_4_Fig7_HTML.png

图 4-7

Tim Chase 的解决方案在使用 curl 下载文件方面很有创意

额外积分解决方案

许多条目旨在满足额外积分解决方案的要求,为每个 IP 地址提供国家名称。我找到了两个特别引起我兴趣的条目。这两个条目都使用 GeoIP 包提供本地数据库来获取国家信息。其他几个条目使用了whois命令,但是除了其他问题之外,whois使用了远程数据库,当从单个 IP 地址访问太快时,就会被阻塞。GeoIP 包在标准 Fedora 存储库和 EPEL CentOS 存储库中提供。

来自阿根廷的 Gustavo Yzaguirre 提交了图 4-8 中的条目,我喜欢它,因为它首先给出了一个带有计数的 IP 地址的基本列表,然后列出了国家。它产生 16,419 行输出,其中许多是重复的。Gustavo 说它没有优化,但这不是要求之一。

img/462716_1_En_4_Fig8_HTML.png

图 4-8

Gustavo Yzaguirre 提交了这个条目,列出了每个 IP 地址的国家名称

塞尔维亚贝尔格莱德的 Dejan Bogdanovic 也提交了一个非常有趣的额外积分解决方案。他在图 4-9 中的条目按频率降序列出了 IP 地址和国家信息。德扬的条目产生 5,764 行输出。

img/462716_1_En_4_Fig9_HTML.png

图 4-9

这个额外学分条目是由德扬·波格丹诺维奇提交的

关于解决方案的思考

我很惊讶 Opensource.com 的读者能想出这么多不同的方法来解决这个问题。在某种程度上,我认为这是因为许多参与者对预期结果的解释有点随意,在许多情况下,添加了比原始规范中要求的更多的信息。

在所有的解决方案中也有很多创意。没有两个解决方案是相同的,这强调了每个人解决问题的方式不同的事实。即使有些解决方案看起来是从相同的角度出发的,但每个解决方案都有自己的个性和一点天赋,这只能是多样化、聪明、知识渊博且非常有创造力的系统管理员带来的独特视角的产物。

让我们把这个竞赛作为现实世界的一个隐喻。竞赛规则是这个项目的规范。每个系统管理员,甚至那些没有在竞赛中胜出的系统管理员,都采用了那些规范,并精心制作了满足需求的解决方案,这些解决方案也具有惊人的创造力。每个解决方案都说明了如何使用 transformer 程序和 STDIO 来转换数据流,最终为系统管理员提供有意义的信息。

这场竞赛也很好地说明了“没有应该”没有一种方法可以让你“应该”做任何事情。重要的是结果。你知道,这听起来很好,我应该把它作为信条之一。在我写作的这一点上,我还没有把它当作一个信条,但它确实是,所以我现在就要创造这一章。

摘要

对于系统管理员来说,只有使用管道和重定向,Linux 哲学的许多原则才有意义。管道将 STDIO 数据流从一个程序或文件传输到另一个程序或文件。在本章中,您已经了解到通过一个或多个 transformer 程序使用管道传输数据流支持对这些流中的数据进行强大而灵活的操作。

实验中展示的每个项目,以及这里展示的所有参赛项目,都很小,而且每个项目都做得很好。他们也是变形金刚,也就是说。它们接受标准输入,以某种方式进行处理,然后将输出发送到标准输出。将这些程序实现为转换器,将经过处理的数据流从它们自己的标准输出发送到其他程序的标准输入,这是对将管道实现为 Linux 工具的补充,也是必要的。

五、一切都是文件

这是使 Linux 特别灵活和强大的最重要的概念之一:一切都是文件。也就是说,一切都可以是数据流的源,数据流的目标,或者在许多情况下两者都是。在这一章中,你将探索“一切都是一个文件”的真正含义,并学习作为一名系统管理员如何利用它。

“一切都是一个文件”的全部意义在于……你可以使用普通的工具来操作不同的东西。

—电子邮件中的莱纳斯·托瓦尔兹

什么是文件?

这是给你的一个难题。下列哪些是文件?

  • 目录

  • Shell 脚本

  • 运行终端模拟器

  • 文档库

  • 串行端口

  • 内核数据结构

  • 内核调整参数

  • 硬盘-/开发/sda

  • /dev/null

  • 分区- /dev/sda1

  • 逻辑卷(LVM)-/开发/映射器/卷 1-tmp

  • 打印机

  • 套接字

对 Unix 和 Linux 来说,它们都是文件,这是计算史上最惊人的概念之一。它使一些非常简单但功能强大的方法成为可能,这些方法用于执行许多管理任务,否则这些任务可能极其困难或不可能。

Linux 几乎把所有东西都当作文件来处理。这有一些有趣和惊人的含义。这个概念使得复制整个硬盘成为可能,包括引导记录,因为整个硬盘是一个文件,就像单独的分区一样。

“一切都是一个文件”是可能的,因为所有的设备都是由 Linux 实现的,这些东西被称为设备文件。设备文件不是设备驱动程序;相反,它们是暴露给用户的设备的网关。

设备文件

设备文件在技术上被称为设备专用文件。 1 设备文件被用来提供操作系统,更重要的是,在开放的操作系统中,提供给用户一个到他们所代表的设备的接口。所有 Linux 设备文件都位于/dev 目录中,该目录是根(/)文件系统不可或缺的一部分,因为它们必须在引导过程的早期阶段(在挂载其他文件系统之前)对操作系统可用。

设备文件创建

udev 守护进程旨在简化/dev 目录中充斥着大量几乎不需要的设备的混乱局面。理解 udev 如何工作是处理设备,尤其是热插拔设备以及如何管理它们的关键。

/dev/目录一直是所有 Unix 和 Linux 操作系统中设备文件的位置。过去,设备文件是在创建操作系统时创建的。这意味着所有可能在系统中使用的设备都需要提前创建。事实上,需要创建成千上万的设备文件来处理所有的可能性。很难确定哪个设备文件实际上与特定的物理设备相关,或者某个设备是否丢失。

udev 简化

udev 旨在通过在/dev 中只为那些在引导时实际存在或者很可能实际存在于主机上的设备创建条目来简化这个问题。这大大减少了所需设备文件的总数。

此外,udev 会在设备插入系统时为其指定名称,例如 USB 存储器和打印机,以及其他非 USB 类型的设备。事实上,udev 将所有设备都视为即插即用,或者像有些人喜欢说的那样,即插即用,甚至在启动时也是如此。这使得在任何时候处理设备都是一致的,无论是在引导时还是以后热插拔时。

让我们用一个实验来看看这是如何工作的。

实验 5-1

以 root 用户身份执行此实验。

插上之前准备好的 u 盘。如果您使用的是虚拟机,您可能还必须使该设备对虚拟机可用。

输入这些命令。

[root@testvm1 dev]# cd /dev ; ls -l sd*
brw-rw---- 1 root disk 8,  0 Nov 22 03:50 sda
brw-rw---- 1 root disk 8,  1 Nov 22 03:50 sda1
brw-rw---- 1 root disk 8,  2 Nov 22 03:50 sda2
brw-rw---- 1 root disk 8, 16 Nov 28 14:02 sdb
brw-rw---- 1 root disk 8, 17 Nov 28 14:02 sdb1

查看 USB 设备上的日期和时间,在我的主机中分别是/dev/sdb 和/dev/sdb1。USB 驱动器和该驱动器上分区的设备文件的创建日期和时间应该正好是设备插入 USB 端口的时间,并且不同于其他设备上在引导时创建的时间戳。你看到的具体结果会和我的不一样。

作为系统管理员,我们不需要为要创建的设备文件做任何其他事情。Linux 内核负责一切。只有在创建了设备文件/dev/sdb1 之后,才能挂载分区以访问其内容。

udev 的创始人之一 Greg Kroah-Hartman 写了一篇论文 2 ,提供了一些关于 udev 的细节以及它应该如何工作的见解。请注意,自从本文撰写以来,udev 已经变得更加成熟,一些东西也发生了变化,比如 udev 规则的位置和结构。无论如何,本文对 udev 和当前的设备命名策略提供了一些深刻而重要的见解。

命名规则

在 Fedora 和 CentOS 的现代版本中,udev 将其默认命名规则存储在/usr/lib/udev/rules.d 目录中的文件中,将其本地规则和配置文件存储在/etc/udev/rules.d 目录中。每个文件包含一组特定设备类型的规则。CentOS 6 和更早版本将全局规则存储在/lib/udev/rules.d/中。udev 规则文件的位置在您的发行版中可能会有所不同。

在 udev 的早期版本中,创建了许多本地规则集,包括一组网络接口卡(NIC)命名规则。由于每个网卡都是由内核发现并由 udev 第一次重命名的,因此在规则集中为网络设备类型添加了一条规则。最初这样做是为了在名称从“ethX”更改为更一致的名称之前确保一致性。

规则改变蓝调

使用 udev 进行持久即插即用命名的一个主要结果是,对于普通的非技术用户来说,事情变得容易多了。从长远来看,这是一件好事;然而,存在迁移问题,许多系统管理员过去和现在都不喜欢这些变化。

随着时间的推移,规则发生了变化,网络接口卡至少有三种明显不同的命名约定。这种命名上的差异造成了大量的混乱,在这些变化期间,许多配置文件和脚本不得不被多次重写。

例如,最初为 eth0 的 NIC 名称将从 eth 0 更改为 em1 或 p1p2,最后更改为 eno1。我在我的技术网站上写了一篇文章 3 ,详细介绍了这些命名方案及其背后的原因。

现在 udev 有了多个一致的默认规则来确定设备名,尤其是对于 NIC,不再需要在本地配置文件中存储每个设备的特定规则来保持一致性。

设备数据流

让我们看一下典型命令的数据流,以直观地了解设备专用文件是如何工作的。图 5-1 显示了一个简单命令的简化数据流。从诸如 Konsole 或 xterm 之类的 GUI 终端仿真器发出cat /etc /resolv.conf命令会导致从磁盘读取 resolv.conf 文件,同时磁盘设备驱动程序处理设备特定的功能,例如在硬盘上定位文件并读取它。数据通过设备文件传递,然后从命令传递到伪终端 6 的设备文件和设备驱动程序,并在终端会话中显示。

img/462716_1_En_5_Fig1_HTML.jpg

图 5-1

使用设备专用文件简化数据流

当然,cat命令的输出可以通过下面的方式重定向到一个文件,cat /etc /resolv.conf > /etc/resolv.bak以创建该文件的备份。在这种情况下,图 5-1 左侧的数据流将保持不变,而右侧的数据流将通过/dev/sda2 设备文件、硬盘设备驱动程序,然后作为新文件 resolv.bak 返回到硬盘的/etc 目录中

这些设备专用文件使得使用标准流(STDIO)和重定向来访问 Linux 或 Unix 计算机上的任何设备变得非常容易。它们为每台设备提供一致且易于访问的界面。只需将数据流定向到设备文件,即可将数据发送到该设备。

关于这些设备专用文件,需要记住的最重要的一点是,它们不是设备驱动程序。它们被最准确地描述为设备驱动程序的入口或网关。数据从应用或操作系统传递到设备文件,然后设备文件将数据传递到设备驱动程序,设备驱动程序再将数据发送到物理设备。

通过使用这些独立于设备驱动程序的设备文件,用户和程序可以对主机上的每个设备拥有一致的接口。正如 Linus 所说,这就是如何使用通用工具对不同的事物进行操作。

设备驱动程序仍然负责处理每个物理设备的独特需求。然而,这超出了本书的范围。

设备文件分类

设备文件至少有两种分类方式。第一种也是最常用的分类是通常与设备相关的数据流类型。例如,tty 和串行设备被认为是基于字符的,因为数据流一次传输和处理一个字符或一个字节。硬盘等块类型设备以块为单位传输数据,通常是 256 字节的倍数。

让我们看看/dev/目录和其中的一些设备。

实验 5-2

这个实验应该以用户学生的身份进行。

打开一个终端会话,显示一长串/dev/目录。

[student@f26vm ~]$ ls -l /dev | less
<snip>
brw-rw----   1 root disk        8,   0 Nov  7 07:06 sda
brw-rw----   1 root disk        8,   1 Nov  7 07:06 sda1
brw-rw----   1 root disk        8,  16 Nov  7 07:06 sdb
brw-rw----   1 root disk        8,  17 Nov  7 07:06 sdb1
brw-rw----   1 root disk        8,  18 Nov  7 07:06 sdb2
<snip>
crw--w----   1 root tty         4,   0 Nov  7 07:06 tty0
crw--w----   1 root tty         4,   1 Nov  7 07:06 tty1
crw--w----   1 root tty         4,  10 Nov  7 07:06 tty10
crw--w----   1 root tty         4,  11 Nov  7 07:06 tty11
<snip>

此命令的结果太长,无法在此完整显示,但是您将看到一个设备文件列表,其中包含它们的文件权限以及它们的主要和次要标识号。

ls -l命令的大量输出通过less transformer 实用程序传输,让您可以翻阅结果;使用向上翻页、向下翻页和上下箭头键移动。键入 q 退出并退出less显示。

实验 5-1 中显示的设备文件的删减列表只是我的 Fedora 工作站上/dev/目录中的几个。它们代表磁盘和 tty 类型的设备。注意输出中每行最左边的字符。带有“b”的是块类型设备,以“c”开头的是字符设备。

识别设备文件的更详细、更明确的方法是使用设备主设备号和次设备号。磁盘设备的主编号为 8,表示它们是 SCSI 块设备。请注意,所有 PATA 和 SATA 硬盘都由 SCSI 子系统管理,因为旧的 ATA 子系统由于其代码质量差,多年前就被认为是不可维护的。因此,以前被指定为“hd[a-z]”的硬盘现在被称为“sd[a-z]”。

在上面显示的小样本中,您大概可以推断出磁盘驱动器次要编号的模式。次要编号 0、16、32 等等直到 240 都是完整的磁盘编号。因此,major/minor 8/16 表示整个磁盘/dev/sdb,8/17 是第一个分区/dev/sdb1 的设备文件。数字 8/34 应该是/dev/sdc2。

上面列表中的 tty 设备文件的编号从 tty0 到 tty63 稍微简单一些。我发现 tty 设备的数量有点不协调,因为新 udev 系统的要点是只为那些实际存在的设备创建设备文件;我不知道为什么要这样做。然而,您也可以从图 5-2 中的列表中看到,所有这些设备文件都是在 11 月 7 日 07:06 创建的,也就是主机启动的时间。主机上的设备文件也应该有一个与上次引导时间相同的时间戳。

位于 Kernel.org 的 Linux 分配设备 4 文件是设备类型和主要及次要编号分配的官方注册表。它可以帮助您了解所有当前定义的设备的主要/次要编号。

设备文件的乐趣

现在,让我们花几分钟时间来研究一下这些设备文件。我们将进行几个有趣的实验,展示 Linux 设备文件的强大功能和灵活性。

大多数 Linux 发行版都有多个虚拟控制台,从 1 到 7,可以用来通过 shell 界面登录到本地控制台会话。可以使用 Ctrl-Alt-F1 组合键访问控制台 1,Ctrl-Alt-F2 组合键访问控制台 2,依此类推。

实验 5-3

在这个实验中,我们将展示简单的命令可以用来在设备之间发送数据,在这个例子中,不同的控制台和终端设备。以学生用户的身份执行此实验。

按下 Ctrl-Alt-F2 切换到控制台 2。在某些发行版中,登录信息包括与此控制台相关的 tty (Teletype)设备,但许多发行版没有。应该是 tty2,因为你在控制台 2。如果使用虚拟机的本地实例,您可能需要使用不同的组合键。

以学生身份登录控制台 2。然后使用who am i命令——是的,就像这样,用空格——来确定哪个 tty 设备连接到这个控制台。

[student@f26vm ~]$ who am i
student  tty2        2017-10-05 13:12

此命令还显示控制台上的用户登录的日期和时间。

在我们进一步进行这个实验之前,让我们看看/dev 中的 tty2 和 tty3 设备列表。我们通过使用一个集合[23]来做到这一点,以便只列出这两个设备。

[student@f26vm ~]$ ls -l /dev/tty[23]
crw--w---- 1 root tty 4, 2 Oct  5 08:50 /dev/tty2
crw--w---- 1 root tty 4, 3 Oct  5 08:50 /dev/tty3

在引导时定义了大量的 tty 设备,但是在这个实验中我们并不关心它们中的大多数,只关心 tty2 和 tty3 设备。作为设备文件,它们没有什么特别的,它们只是简单的字符型设备;请注意结果第一列中的“c”。我们将在这个实验中使用这两台 TTY 设备。tty2 设备连接到虚拟控制台 2,tty3 设备连接到虚拟控制台 3。

按下 Ctrl-Alt-F3 切换到控制台 3,再次以学生用户身份登录。再次使用who am i命令验证您确实在控制台 3 上,然后输入 echo 命令。

[student@f26vm ~]$ who am i
student  tty3        2017-10-05 13:18
[student@f26vm ~]$ echo "Hello world" > 
/dev

/tty2

按下 Ctrl-Alt-F2 返回控制台 2。控制台 2 上应该显示字符串“Hello world”(不带引号)。

这个实验也可以用 GUI 桌面上的终端模拟器来执行。桌面上的终端会话使用/dev 树中的伪终端设备,例如/dev/pts/1,其中 pts 代表“伪终端会话”。

使用 Konsole、Tilix、Xterm 或您喜欢的其他图形终端模拟器,在 GUI 桌面上打开至少两个终端会话。如果你愿意,你可以打开几个。使用我是谁命令确定它们连接到哪个伪终端设备文件,然后选择一对终端仿真器用于本实验。使用 echo 命令将消息发送给另一个。

[student@f26vm ~]$ who am i
student  pts/9        2017-10-19 13:21 (192.168.0.1)
[student@f26vm ~]$ w
13:23:06 up 14 days,  4:32,  9 users,  load average: 0.03, 0.08, 0.09
USER     TTY        LOGIN@   IDLE   JCPU   PCPU WHAT
student  pts/1     05Oct17  4:48m  0.04s  0.04s -bash
student  pts/2     06Oct17  2:16   2.08s  2.01s screen
student  pts/3     07Oct17 12days  0.04s  0.00s less
student  pts/4     07Oct17  2:16   0.10s  0.10s /bin/bash
root     pts/5     08:35    4:08m  0.05s  0.05s /bin/bash
root     pts/6     08:35    4:47m  1:19   1:19  htop
root     pts/7     08:35    4:40m  0.05s  0.05s /bin/bash
root     pts/8     08:50    4:32m  0.03s  0.03s /bin/bash
student  pts/9     13:21    0.00s  0.04s  0.00s w
[student@f26vm ~]$ echo "Hello world" > 
/dev

/pts/4

在我的测试主机上,我将文本“Hello world”从/dev/pts/9 发送到/dev/pts/4。您的终端设备将与我在测试虚拟机上使用的不同。请确保在您的环境中使用正确的设备进行本实验。

另一个有趣的实验是使用 cat 命令将文件直接打印到打印机上。

实验 5-4

这个实验应该以学生用户的身份进行。

您可能需要确定哪个设备是您的打印机。如果你的打印机是 USB 打印机,现在几乎都是,在/dev/usb 目录中查找 lp0,它通常是默认的打印机。您也可以在该目录中找到其他打印机设备文件。

我用 LibreOffice Writer 创建了一个简短的文档,然后导出为 PDF 文件,test.pdf。任何 Linux 文字处理器都可以,只要它能导出到 PDF 格式。

我们将假设您的打印机设备是/dev/usb/lp0,并且您的打印机可以直接打印 PDF 文件,大多数打印机都可以。请务必使用 PDF 文件,并将命令中的名称 test.pdf 改为您自己的文件名。

[student@f26vm ~]$ cat test.pdf > 
/dev

/usb/lp0

此命令应该在您的打印机上打印 PDF 文件 test.pdf。

/dev 目录包含一些非常有趣的设备文件,这些文件是硬件的入口,人们通常不认为这些硬件是硬盘或显示器之类的设备。例如,系统内存——RAM——通常不被认为是“设备”,但是/dev/mem 是设备专用文件,通过它可以实现对内存的直接访问。

实验 5-5

这个实验必须以 root 用户的身份运行。因为你只是在读取内存的内容,所以这个实验没有什么危险。

注意一些测试人员报告说这个实验对他们不起作用。我没有在几个物理和虚拟主机上发现任何问题。请注意,这个实验可能会产生一个权限错误,而不是期望的输出。

如果根终端会话尚不可用,请打开终端模拟器会话,并以 root 用户身份登录。下一个命令将把第一个 200K 的 RAM 转储到 STDOUT。

[root@f26vm ~]# dd if=/dev/mem bs=2048 count=100

它可能看起来不像那么多,你所看到的将是难以理解的。为了使它更容易理解——至少以专家可以理解的适当格式显示数据——通过 od 实用程序传输前面命令的输出。

[root@f26vm ~]# dd if=/dev/mem bs=2048 count=100 | od -c

Root 用户比非 root 用户有更多的读取内存的权限,但是大多数内存是受保护的,不会被任何用户写入,包括 root 用户。

与简单地使用cat命令转储所有内存相比,dd命令提供了更多的控制,我也尝试过这样做。dd命令提供了指定从/dev/mem 中读取多少数据的能力,也允许我指定从内存中读取数据的起始点。尽管使用cat命令读取了一些内存,但内核最终还是响应了图 5-2 中的错误。

您也可以以非 root 用户学生的身份登录,并尝试此命令。您将得到一条错误消息,因为您试图访问的内存不属于您的用户。这是 Linux 的一个内存保护特性,可以防止其他用户读取或写入不属于他们的内存。

img/462716_1_En_5_Fig2_HTML.jpg

图 5-2

cat 命令试图将受保护的内存转储到 STDOUT 时,最后一行显示错误

这些内存错误意味着内核正在通过保护属于其他进程的内存来完成它的工作,这正是它应该如何工作。因此,尽管您可以使用/dev/mem 来显示存储在 RAM 内存中的数据,但是对大多数内存空间的访问是受保护的,并且会导致错误。只有内核内存管理器分配给运行dd命令的 bash shell 的虚拟内存应该是可访问的,而不会导致错误。抱歉,你不能窥探不属于你的内存,除非你发现一个漏洞利用。

许多类型的恶意软件依靠特权提升来允许它们读取它们通常无法访问的内存内容。这使得恶意软件能够找到并窃取个人数据,如账号、用户 ID 和存储的密码。幸运的是,Linux 可以防止非根用户访问内存。它还可以防止特权升级。

但是即使是 Linux 安全也不是完美的。安装安全补丁来防止允许权限提升的漏洞是很重要的。你还应该意识到人为因素,比如人们写下密码的倾向,但那都是另一回事了。 5

现在,您可以看到内存也被视为一个文件,并且可以使用内存设备文件来处理它。

随机性、零等等

/dev 中还有一些其他非常有趣的设备文件。设备专用文件 null、zero、random 和 urandom 不与任何物理设备相关联。这些设备文件提供了零、空值和随机数的来源。

空设备/dev/null 可以用作重定向 shell 命令或程序输出的目标,这样它们就不会显示在终端上。

实验 5-6

我经常在我的 bash 脚本中使用/dev/null 来防止用户看到可能让他们感到困惑的输出。输入以下命令,将输出重定向到空设备。终端上不会显示任何内容。数据只是被转储到天空中的大比特桶中。

[student@f26vm ~]$ echo "Hello world" > /dev/null

将/dev/null 作为“空”字符的来源。

[student@testvm1 ~]$ cat /dev/null
[student@testvm1 ~]$ dd if=/dev/null
0+0 records in
0+0 records out
0 bytes copied, 5.2305e-05 s, 0.0 kB/s

/dev/null 实际上没有可见的输出,因为 null 设备只是返回一个文件结束(EOF)字符。请注意,字节计数为零。空设备作为一个重定向不需要的输出以便将其从数据流中删除的地方要有用得多。

/dev/random 和/dev/urandom 设备都是有用的数据流源。顾名思义,它们都产生随机输出——不仅仅是数字,而是任何和所有的字节组合。/dev/urandom 设备产生确定性的 6 随机输出,速度非常快。

实验 5-7

使用此命令查看/dev/urandom 的典型输出。可以用 Ctrl-c 突围。

[student@f26vm ~]$ cat /dev/urandom
,3••VwM
N•g•/•l•ۑ•!••'۩'•:••|R••[•t••Z••F.:H•7•,••
••z/••|•7q•Sp•"•(l_c••π••-•••••••ś•Y•••D⁵•i8••"%•••&ŋ|C9!y•••f•5bPp;••C
••x••1•••U••3~•••
<snip>

我只展示了该命令的一部分数据流,但是它应该会让您对您的系统有所了解。

您还可以通过od命令来传输实验 5-6 的输出,使其更易于阅读。这对大多数现实世界的应用来说没有什么意义,因为它毕竟是随机数据。

od的手册页显示,它可以用来直接从文件中获取数据,以及指定要读取的数据量。

实验 5-8

在这种情况下,我使用-N 128 将输出限制为 128 字节。

[student@f26vm ~]$ od /dev/urandom -N 128
0000000 043514 022412 112660 052071 161447 057027 114243 061412
0000020 154627 105675 154470 110352 135013 127206 103057 136555
0000040 033417 011054 014334 040457 157056 165542 027255 121710
0000060 125334 065600 165447 165245 020756 101514 042377 132156
0000100 116024 027770 000537 014743 170561 011122 173454 102163
0000120 074301 104771 123476 054643 105211 151753 166617 154313
0000140 103720 147660 012644 037363 077661 076453 104161 033220
0000160 056501 001771 113557 075046 102700 043405 132046 045263
0000200

dd命令也可用于指定从[u]个随机设备获取的数据量的限制,但它不能直接格式化数据。

/dev/random 设备文件产生不确定的 7 随机输出,但它产生输出的速度较慢。该输出不是由仅依赖于先前生成的数字的算法确定的,而是响应于击键和鼠标移动而生成的。这种方法使得复制一系列特定的随机数更加困难。使用 cat 命令查看/dev/random 设备文件的一些输出。尝试移动鼠标,看看它如何影响输出。

从/dev/random 和/dev/urandom 生成的随机数据,不管是如何从这些设备中读取的,通常都被重定向到某个存储介质上的文件或另一个程序的 STDIN。系统管理员、开发人员或最终用户很少需要查看随机数据。但它确实为这个实验做了一个很好的示范。

顾名思义,/dev/zero 设备文件产生一个无休止的零字符串作为输出。请注意,这些是八进制零,而不是 ASCII 字符零(0)。

实验 5-9

使用dd命令查看/dev/zero 设备文件的一些输出。请注意,该命令的字节数不为零。

[student@f26vm ~]$ dd if=/dev/zero  bs=512 count=500 | od -c
0000000  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
*
500+0 records in
500+0 records out
256000 bytes (256 kB, 250 KiB) copied, 0.00126996 s, 202 MB/s
0764000

备份主引导记录

例如,考虑对硬盘驱动器的主引导记录(MBR)进行备份的简单任务。有时,我需要恢复或重新创建我的 MBR,尤其是分区表。从头开始重新创建它是非常困难的。从保存的文件中恢复它很容易。所以我们备份一下硬盘的开机记录。

请注意,本节中的所有实验都必须以 root 用户身份执行。

实验 5-10

我们将为您的主引导记录(MBR)创建一个备份,但不会尝试恢复它。

dd命令必须以 root 身份运行,因为出于安全原因,非 root 用户不能访问/dev 目录中的硬盘设备文件。bs 值不是你可能想的那样;它代表块大小。Count 是从源文件中读取的块数。

[root@f26vm ~]# dd if=/dev/sda of=/tmp/myMBR.bak bs=512 count=1

这个命令在/tmp 目录中创建一个文件 myMBR.bak。该文件大小为 512 字节,包含 MBR 的内容,包括引导代码和分区表。

现在看看您刚刚创建的文件的内容。

[root@testvm1 ~]# cat /tmp/myMBR.bak
•c•••••••••|••••!••8u
Z••••••}•f••d•@f•D•••••••@•••••f•f•`|fL••uNf•\|f1•f•4••1•f•t;}7•••0••••Z••p••1••r••`•••1••••••a•&Z|••}•••}•4••}•.•••GRUB GeomHard DiskRead Error
••••<u••}•••• !••( •)•••• ••U•[root@testvm1 ~]#

因为在引导扇区的结尾没有行尾字符,所以命令提示符与引导记录的结尾在同一行。

如果 MBR 损坏了,就需要启动到一个救援盘,并使用代码示例 5-1 中的命令来执行与上面的命令相反的操作。请注意,不需要像第一个命令那样指定块大小和块数,因为 dd 命令只是将备份文件复制到硬盘驱动器的第一个扇区,并在到达源文件末尾时停止。

代码示例 5-1

以下代码将备份主引导记录还原到硬盘上的第一个扇区。

[root@testvm1 ~]#  dd if=/tmp/myMBR.bak of=/dev/sda

不要运行此代码,因为如果输入不正确,它可能会损坏您的系统。

现在,您已经备份了硬盘的引导记录并验证了该备份的内容,让我们转移到一个更安全的环境,销毁引导记录,然后恢复它。

实验 5-11

这是一个相当长的实验,必须以 root 用户身份执行。您将为 USB 设备备份 MBR,损坏设备上的 MBR,尝试读取设备,然后恢复 MBR。不要安装 USB 驱动器。

确保 USB 驱动器已插入您的计算机,并验证设备文件名。在我的例子中,它仍然是/dev/sdb。

首先我们用fdisk看分区表,为后面的对比提供依据,然后我们备份 USB 设备的 MBR,验证备份文件的内容。与之前的类似实验一样,警告消息是 MBR 内容的一部分。

[root@testvm1 ~]# fdisk -l /dev/sdb
Disk /dev/sdb: 62.5 MiB, 65536000 bytes, 128000 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x73696420

Device     Boot Start    End Sectors  Size Id Type
/dev/sdb1        2048 127999  125952 61.5M  c W95 FAT32 (LBA)

[root@f26vm ~]# dd if=/dev/sdb of=/tmp/myMBR.bak bs=512 count=1
1+0 records in
1+0 records out
512 bytes copied, 0.012374 s, 41.4 kB/s
[root@f26vm ~]# cat /tmp/myMBR.bak
•>•MSWIN4.1P•} •••)L•0NO NAME    FAT16   •}•3•••{•x•vVU•"•~•N•
•••|•E••F•E••8f$|•r<•F••fFVF•PR•F•V•• •v••^
•H••F•N•ZX••••rG8-t• V•v>•^tJNt
••F•V••S••[r•?MZu•••BJu••pPRQ••3••v••vB•••v••V$•••••••t<•t    •••••}••}••3••^••D•••}•}••r••HH•N ••YZXr   @uB^
            •••'
Invalid system disk•
Disk I/O error•
Replace the disk,!••U•

现在有趣的部分来了,我们用一个 512 字节的随机数据块覆盖 USB 设备的 MBR,然后查看 MBR 的新内容以验证更改。请注意,警告消息不再存在,因为它们已被覆盖。

[root@f26vm ~]# dd if=/dev/urandom of=/dev/sdb bs=512 count=1
1+0 records in
1+0 records out
512 bytes copied, 0.0195473 s, 26.2 kB/s
[root@f26vm ~]# dd if=/dev/sdb bs=512 count=1
6••••%•w••pI!8k•••••$••Q••¯••••gO••\••AT••KQ••••• ••"5•oW-•••;•••••ٹr3••oiP•d•q••••a••%••••N••#••&F•_•••y••?•\•••)••K••?•fa••+.••••F•'F••~•H•••XbS•••BA•V•^••z[S•jy••••••=aPs:••N_[ڶ••••b••#%•;/•••,4•}9
0••7•••ٯF85••L•g••\•R4••••q••Kn|M••cy••ʗ••m•\••••yi{_o^•i•j K•nry2MMSeA•••p•^E•n•v•u2•/•A•Zb•••1••Ì•K5•3•x•K•ia•K?•Iw••••^•1f•••{3•p&E•••M••rbɠ••••••••• p••K•1+0 records in
1+0 records out
512 bytes copied, 0.0137811 s, 37.2 kB/s

在我们继续恢复这个 MBR 之前,让我们再做一些事情来测试这种状态。首先,我们使用fdisk来验证 USB 驱动器不再有分区表,这意味着 MBR 已经被覆盖。

[root@f26vm ~]# fdisk -l /dev/sdb
Disk /dev/sdb: 62.5 MiB, 65536000 bytes, 128000 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

尝试挂载原始分区将会失败。错误消息表明特殊设备不存在。这表明大多数特殊设备文件都是根据需要按需创建和删除的。

[root@f26vm ~]# mount /dev/sdb1 /mnt
mount: /mnt: special device /dev/sdb1 does not exist.

是时候恢复您之前备份的启动记录了。因为您使用 dd 命令小心地用随机数据只覆盖了包含驱动器分区表的 MBR,所以所有其他数据都保持不变。恢复 MBR 将使其再次可用。恢复 MBR,在设备上查看 MBR,然后挂载分区并列出内容。

[root@f26vm ~]# dd if=/tmp/myMBR.bak of=/dev/sdb
1+0 records in
1+0 records out
512 bytes copied, 0.0738375 s, 6.9 kB/s

[root@testvm1 ~]# fdisk -l /dev/sdb
Disk /dev/sdb: 62.5 MiB, 65536000 bytes, 128000 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x73696420

Device     Boot Start    End Sectors  Size Id Type
/dev/sdb1        2048 127999  125952 61.5M  c W95 FAT32 (LBA)

[root@f26vm ~]# mount /dev/sdb1 /mnt
[root@f26vm ~]# ls -l /mnt
total 380
-rwxr-xr-x 1 root root 37001 Nov  7 08:23 file0.txt
-rwxr-xr-x 1 root root 37001 Nov  7 08:23 file1.txt
-rwxr-xr-x 1 root root 37001 Nov  7 08:23 file2.txt
-rwxr-xr-x 1 root root 37001 Nov  7 08:23 file3.txt
-rwxr-xr-x 1 root root 37001 Nov  7 08:23 file4.txt
-rwxr-xr-x 1 root root 37001 Nov  7 08:23 file5.txt
-rwxr-xr-x 1 root root 37001 Nov  7 08:23 file6.txt
-rwxr-xr-x 1 root root 37001 Nov  7 08:23 file7.txt
-rwxr-xr-x 1 root root 37001 Nov  7 08:23 file8.txt
-rwxr-xr-x 1 root root 37001 Nov  7 08:23 file9.txt

哇——太酷了!这一系列实验旨在说明,您可以利用所有设备都可以被视为文件这一事实,从而以一些非常有趣的方式使用一些非常常见但功能强大的 CLI 工具。

没有必要用 sb=和 count=参数指定要复制的数据量,因为 dd 命令只复制可用的数据量,在本例中是一个 512 字节的扇区。

卸载 USB 设备,因为我们现在已经完成了。

一切都是一个文件的含义

“一切都是一个文件”的含义是深远的,比这里所能列出的要大得多。在前面的实验中,您已经看到了一些例子。但是这里有一个简短的列表,包含了这些以及更多。

  • 克隆硬盘。

  • 备份分区。

  • 备份主引导记录(MBR)。

  • 将 ISO 映像安装到 USB 拇指驱动器上。

  • 与其他终端上的用户进行交流。

  • 将文件打印到打印机。

  • 更改/proc 伪文件系统中某些文件的内容,以修改正在运行的内核的配置参数。

  • 用随机数据或零覆盖文件、分区或整个硬盘。

  • 将不需要的命令输出重定向到一个空设备,在那里它将永远消失。

  • 等等。等。等。

这里有太多的可能性,任何列表都只能触及表面。我确信你已经——或者将要——想出许多方法来更有创造性地运用这一哲学原则,远比我在这里讨论的要多得多。

摘要

它是文件系统的一部分。Linux 计算机上的一切都可以作为文件系统空间中的一个文件来访问。这样做的全部目的是能够使用通用工具来操作不同的东西——通用工具,如标准的 GNU/Linux 实用程序和对文件起作用的命令也将在设备上起作用——因为在 Linux 中,它们是文件。

六、使用 Linux FHS

Linux 文件系统分层标准(FHS)定义了 Linux 目录树的结构。它命名了一组标准目录并指定了它们的用途。

这个标准已经到位,以确保 Linux 的所有发行版在目录使用上是一致的。这种一致性使得系统管理员更容易编写和维护 shell 和编译程序,因为程序、它们的配置文件和它们的数据(如果有的话)应该位于标准目录中。本章介绍在目录树中的标准和推荐位置存储程序和数据,以及这样做的好处。您将学习如何参考 Linux FHS 文档,并在解决问题时使用这些知识。

定义

在我们深入探讨这个主题之前,让我们先给出“文件系统”这个词的一些定义,并尝试整理一下您可能会发现的一些术语混淆。您可能会听到人们以许多不同的、令人困惑的方式谈论文件系统。这个词本身可能有多种含义,你可能必须从讨论或文件的上下文中辨别正确的含义。

我将根据我观察到的“文件系统”一词在不同环境中的用法,尝试定义它的各种含义。请注意,虽然我试图符合标准的“官方”含义,但我的意图是基于该术语的各种用法来定义它。

  1. 从顶层(/)根目录开始的整个 Linux 目录结构。

  2. 一种特定类型的数据存储格式,如 EXT3、EXT4、BTRFS、XFS 等。Linux 支持近 100 种文件系统,包括一些非常老的文件系统,以及一些最新的文件系统。每种文件系统类型都使用自己的元数据结构来定义数据的存储和访问方式。

  3. 用特定类型的文件系统格式化的分区或逻辑卷,可以挂载在 Linux 文件系统上的指定挂载点(目录)上。

在本章的某个地方,我将在所有这些定义的上下文中使用术语“文件系统”。

标准

作为系统管理员,我们的任务包括从修复问题到编写 CLI 程序来为我们和他人执行许多任务。了解各种类型的数据应该存储在 Linux 系统的什么地方,对于解决问题和预防问题非常有帮助。

最新的文件系统层次标准(3.0) 1 在 Linux 基金会维护的文档中定义。这份文件在他们的网站上有多种格式,就像 FHS 的历史版本一样。

表 6-1 提供了标准的、众所周知的、已定义的顶级 Linux 目录及其用途的简要列表。这些目录是按字母顺序排列的。我建议您阅读整个文档,以便理解这些顶层子目录所扮演的角色。

注意表 6-1 中的第 2 列,中间一列。该列中所有带有“Yes”的目录都必须是根(/)文件系统不可分割的一部分。这些目录都不能位于单独的分区或逻辑卷上;它们必须与根文件系统位于同一个分区或逻辑卷中,因为它们是根文件系统不可分割的一部分。这些目录必须在引导过程开始时作为一个单元与根文件系统一起挂载。

第 2 列中带有“否”的目录可以创建在单独的分区或逻辑卷上——它们不必是单独的,但可以是单独的。当这些文件系统与根文件系统分离时,它们会根据/etc/fstab 文件中包含的信息在启动序列中稍后被挂载。将这些目录作为独立的文件系统安装有一些很好的理由,我将在本章后面讨论这些理由。

表 6-1

Linux 文件系统层次标准的顶层

|

目录

|

属于/

|

描述

|
| --- | --- | --- |
| /(根文件系统) | 是 | 根文件系统是文件系统的顶级目录。在挂载其他文件系统之前,它必须包含引导 Linux 系统所需的所有文件。系统启动后,所有其他文件系统都挂载在标准的、定义良好的挂载点上,作为根文件系统的子目录。 |
| /bin | 是 | /bin 目录包含用户可执行文件。 |
| /boot | 不 | 包含引导 Linux 计算机所需的静态引导加载程序、内核可执行文件和配置文件。 |
| /开发 | 是 | 该目录包含连接到系统的每个硬件设备的设备文件。这些不是设备驱动程序,而是代表计算机上每个设备并便于访问这些设备的文件。 |
| /等等 | 是 | 包含主机的各种系统配置文件。 |
| /home | 不 | 用户文件的主目录存储。每个人类用户通常在/home 中有一个子目录。一些组织可能会为用户的主目录选择其他位置。一些服务或服务器应用也可能使用不同的主目录位置。例如,Apache web 服务器使用/var/www。您可以查看/etc/passwd 文件来查看这些用户的主目录位置。使用中央文件服务器的安装也可能将这些远程主目录放在/home 以外的挂载点上。 |
| /库 | 是 | 包含引导系统所需的共享库文件。 |
| /媒体 | 不 | 安装外部可移动媒体设备(如可能连接到主机的 USB 拇指驱动器)的地方。 |
| /mnt | 不 | 常规文件系统(如不可移动介质)的临时挂载点,可在管理员修复或处理文件系统时使用。 |
| /opt | 不 | 供应商提供的应用等可选文件应位于此处。 |
| /proc | 虚拟的 | 虚拟文件系统用于公开对内部内核信息和可编辑调优参数的访问。 |
| /root | 是 | 这不是根(/)文件系统。它是根用户的主目录。 |
| /sbin | 是 | 系统二进制文件。这些是用于系统管理的可执行文件。 |
| /selinux | 虚拟的 | 这个伪文件系统仅在启用 SELinux 时使用。激活时,这个文件系统包含重要的 SELinux 工具和文件。 |
| /sys | 虚拟的 | 这个虚拟文件系统包含关于 USB 和 PCI 总线以及连接到每个总线的设备的信息。 |
| /tmp | 不 | 临时目录。由操作系统和许多程序用来存储临时文件。用户也可以在这里临时存储文件。请注意,此处存储的文件可能会在不事先通知的情况下随时删除。 |
| /usr | 不 | 这些是可共享的只读文件,包括可执行二进制文件和库、人工文件和其他类型的文档。 |
| /usr/local | 不 | 这些通常是 shell 程序或编译程序及其支持配置文件,它们在本地编写,由系统管理员或主机的其他用户使用。 |
| /var | 不 | 可变数据文件存储在这里。这可能包括日志文件、MySQL 和其他数据库文件、web 服务器数据文件、电子邮件收件箱等等。 |

/media 和/mnt 目录是临时文件系统维护或外部设备(如包含文件系统的 USB 拇指驱动器)的挂载点。

关于表 6-1 的“顶层”声明实际上有一个例外。这是/usr/local 目录。我将在本章稍后更详细地讨论这个目录。

使用定义良好的文件系统结构

遵循 Linux 文件系统分层标准有一些很好的理由。所有这些都让我们作为系统管理员的生活变得更加轻松。别担心,我不会讨论标准中定义的每个目录的功能——毕竟,你可以像我一样轻松地阅读我写的内容和更详细的在线版本。我要做的是讨论这个 FHS 的几个具体特征如何影响我的工作。

Linux FHS 的目的是提供一个定义良好的结构来存储文件,无论是可执行文件、数据还是配置文件。在文件系统分层标准(3.0)文档中定义的结构,以及前面提到的,阐述了 Linux 中文件位置的指导原则,这些原则基于可追溯到 Unix 早期的历史背景,以及新的、更新的和不断变化的标准和约定。

事实是用法确实会改变。文件系统层次标准也随着时间的推移而改变。此外,并不是所有的发行版和软件供应商都以相同的方式解释 FHS,一些软件供应商可能只是不了解这个标准。

不管这些事实如何,作为系统管理员,我们有责任在我们控制的所有方面坚持当前的标准。我们不能总是控制供应商的使用,但我们当然可以有我们的发言权。不要误解我——我在这里没有看到广泛的问题,但是如果有问题,我认为作为负责任的系统管理员,我们应该向适当的供应商报告这些问题。

当我们编写代码时,我们自己也应该遵守这些标准,即使它看起来只是 CLI 编程中的一小段无关紧要的代码。

Linux 统一目录结构

Linux 文件系统目录结构由可挂载文件系统的层次结构组成,这是本章开头列表中的第 3 项。这使得对层次结构中所有目录的访问更加容易和一致。它也提供了一些非常有用的副作用。

在一些非 Linux PC 操作系统中,如果有多个物理硬盘驱动器或多个分区,每个磁盘或分区都会分配一个驱动器号。有必要知道文件或程序位于哪个硬盘上,例如 C:或 D:。然后,您将驱动器号作为命令 D:发出,例如,切换到 D:驱动器,然后您使用cd命令切换到正确的目录,以定位所需的文件。每个硬盘都有自己独立完整的目录树。

Linux 文件系统将所有物理硬盘驱动器、分区和逻辑卷统一到一个目录结构中。这一切都从顶部开始——根(/)目录。所有其他目录及其子目录都位于单个 Linux 根目录下。这意味着只有一个单一的目录树来搜索文件和程序。

这样做的唯一原因是,可以在独立的物理硬盘驱动器、不同的分区或与/(根)文件系统不同的逻辑卷上创建文件系统,比如/home、/tmp、/var、/opt 或/usr,然后将其作为根文件系统目录树的一部分挂载到一个挂载点上。挂载点只是空目录,没有什么特别的。甚至像 USB 拇指驱动器、外部 USB 或 ESATA 硬盘这样的可移动驱动器也将被安装到根文件系统上,并成为该目录树不可分割的一部分。

在从一个版本的 Linux 发行版升级到另一个版本,或者从一个发行版换到另一个发行版的过程中,这样做的一个很好的理由是显而易见的。一般来说,除了像 Fedora 中的 dnf-upgrade 这样的升级实用程序之外,明智的做法是在升级期间偶尔重新格式化根分区和包含操作系统的其他分区,以积极地删除任何随着时间积累的 cruft。如果/home 是根文件系统的一部分,它也将被重新格式化,然后必须从备份中恢复。通过将/home 作为一个单独的文件系统,安装程序将知道它是一个单独的文件系统,并且可以跳过对该文件系统的格式化。这也适用于存储数据库、电子邮件收件箱、网站和其他可变用户和系统数据的/var,以及用于存储商业应用的/opt 文件系统。因此,没有任何数据丢失,应用不应该需要重新安装,除非供应商非常愚蠢。

将 Linux 目录树的某些部分作为单独的文件系统来维护还有其他原因。例如,很久以前,当我还没有意识到将所有必需的 Linux 目录作为/(根)文件系统的一部分的潜在问题时,我设法用大量非常大的文件填满了我的主目录。因为/home 目录和/tmp 目录都不是独立的文件系统,而只是根文件系统的子目录,所以整个根文件系统都被填满了。没有空间留给操作系统来创建临时文件或扩展现有的数据文件。起初,应用开始抱怨没有空间保存文件,然后操作系统本身开始表现得非常奇怪。引导到单用户模式并清除我的主目录中有问题的文件让我可以重新开始工作;然后,我使用一个非常标准的多文件系统设置重新安装了 Linux,并且能够防止整个系统崩溃再次发生。

我曾经遇到过这样的情况,Linux 主机继续运行,但是阻止用户使用 GUI 桌面登录。我能够使用命令行界面(CLI)在本地使用一个虚拟控制台登录,并使用 SSH 远程登录。问题是/tmp 文件系统已经满了,GUI 桌面需要的一些临时文件无法在登录时创建。因为 CLI 登录不需要在/tmp 中创建文件,所以空间不足并没有妨碍我使用 CLI 登录。在这种情况下,/tmp 目录是一个单独的文件系统,并且/tmp 逻辑卷所在的卷组中有大量的可用空间。我只是将/tmp 逻辑卷扩展到一个大小,以适应我对该主机上所需的临时文件空间量的新理解,问题就解决了。请注意,该解决方案不需要重新启动,并且一旦/tmp 文件系统扩大,用户就可以登录到桌面。

特殊文件系统

Linux 在运行时有一些特殊的文件系统,其中两个系统管理员特别感兴趣,/proc 和/sys。当 Linux 主机运行时,这些虚拟文件系统只存在于 RAM 中;它们不存在于任何物理磁盘上。因为它们只存在于 RAM 中,所以这些文件系统不像存储在硬盘上的文件系统那样是持久的。当计算机关闭时,它们会消失,每次 Linux 启动时,它们会重新创建。

在 Linux 主机中,每个特殊的文件系统都扮演着独特的角色。/proc 文件系统很可能是您作为系统管理员非常熟悉的一个文件系统,所以我们将对它进行一点探索。

/proc 文件系统

FHS 将/proc 文件系统定义为 Linux 存储有关系统、内核和主机上运行的所有进程的信息的位置。它旨在为内核提供一个暴露自身信息的地方,以便于访问系统数据。它还旨在提供对视图内核配置参数的访问,并在必要时修改其中的许多参数。

当用作进入操作系统状态及其系统和硬件视图的窗口时,它提供了对您作为系统管理员可能想要的几乎每一点信息的方便访问。

实验 6-1

为了获得最佳实验结果,必须以 root 用户身份执行。

让我们首先看看正在运行的 Linux 主机的/proc 文件系统的顶层内容。在您的主机上,您可能会看到颜色编码来区分文件和目录。

首先,看看数字条目。这些目录的名称是 PID 或进程 ID 号。这些 PID 目录中的每一个都包含关于它所代表的正在运行的进程的信息。

[root@testvm1 proc]# cd /proc ; ls
1      26533  666  828        cpuinfo        modules
10     26561  669  83         crypto         mounts
11     27     680  84         devices        mtrr
12     29356  681  85         diskstats      net
13     30     685  86         dma            pagetypeinfo
14     30234  686  87         driver         partitions
15     31     692  9          execdomains    sched_debug
16     333    694  90         fb             schedstat
17     361    695  91         filesystems    scsi
18     4      697  927        fs             self
19     401    7    928        interrupts     slabinfo
2      402    707  929        iomem          softirqs
20     412    708  934        ioports        stat
21     413    740  937        irq            swaps
22     433    741  940        kallsyms       sys
23     434    749  941        kcore          sysrq-trigger
24     517    756  942        keys           sysvipc
25     543    764  947        key-users      thread-self
26     6      765  948        kmsg           timer_list
26465  615    766  966        kpagecgroup    tty
26511  616    771  990        kpagecount     uptime
26514  636    778  acpi       kpageflags     version
26521  637    780  asound     latency_stats  vmallocinfo
26522  639    783  buddyinfo  loadavg        vmstat
26524  641    8    bus        locks          zoneinfo
26526  647    80   cgroups    mdstat
26527  661    81   cmdline    meminfo
26532  664    82   consoles   misc

/proc 目录中的每个文件都包含关于内核某个部分的信息。让我们来看看其中的两个文件,cpuinfo 和 meminfo。

cpuinfo 文件大部分是静态的。它包含所有已安装 CPU 的规格。

[root@testvm1 proc]# cat cpuinfo
processor       : 0
vendor_id       : GenuineIntel
cpu family      : 6
model           : 58
model name      : Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz
stepping        : 9
microcode       : 0x19
cpu MHz         : 3392.345
cache size      : 8192 KB
physical id     : 0
siblings        : 1
core id         : 0
cpu cores       : 1
apicid          : 0
initial apicid  : 0
fpu             : yes
fpu_exception   : yes
cpuid level     : 13
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid pni pclmulqdq monitor ssse3 cx16 sse4_1 sse4_2 popcnt aes xsave avx rdrand lahf_lm
bugs            :
bogomips        : 6784.69
clflush size    : 64
cache_alignment : 64
address sizes   : 36 bits physical, 48 bits virtual power management:

来自 cpuinfo 文件的数据包括处理器 ID 和型号、其当前速度(MHz)以及可用于确定 CPU 特性的标志。如果您运行命令ls -la cpuinfo,,您将看到文件上的时间戳在不断变化。指示文件正在更新。

现在我们来看看内存。首先对 meminfo 文件进行 cat,然后使用free命令进行比较。

[root@testvm1 proc]# cat meminfo
MemTotal:        4044740 kB
MemFree:         2936368 kB
MemAvailable:    3484704 kB
Buffers:          108740 kB
Cached:           615616 kB
SwapCached:            0 kB
Active:           676432 kB
Inactive:         310016 kB
Active(anon):     266916 kB
Inactive(anon):      316 kB
Active(file):     409516 kB
Inactive(file):   309700 kB
Unevictable:        8100 kB
Mlocked:            8100 kB
SwapTotal:       4182012 kB
SwapFree:        4182012 kB
Dirty:                 0 kB
Writeback:             0 kB
AnonPages:        270212 kB
Mapped:           148088 kB
Shmem:               988 kB
Slab:              80128 kB
SReclaimable:      64500 kB
SUnreclaim:        15628 kB
KernelStack:        2272 kB
PageTables:        11300 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:     6204380 kB
Committed_AS:     753260 kB
VmallocTotal:     34359738367 kB
VmallocUsed:           0 kB
VmallocChunk:          0 kB
HardwareCorrupted:     0 kB
AnonHugePages:         0 kB
ShmemHugePages:        0 kB
ShmemPmdMapped:        0 kB
CmaTotal:              0 kB
CmaFree:               0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
DirectMap4k:       73664 kB
DirectMap2M:     4120576 kB
[root@testvm1 proc]# free
       total        used        free      shared  buff/cache   available
Mem:   4044740      304492     2935748       988      804500   3484100
Swap:  4182012           0     4182012

/proc/meminfo 文件中有很多信息。像 free command 这样的程序会使用其中的一些数据。如果您想了解内存使用的完整情况,请查看/proc/meminfo。free 命令像许多其他核心实用程序一样,从/proc 文件系统中获取数据。

因为/proc 中的数据几乎是 Linux 内核和计算机硬件状态的即时图像,所以数据可能会快速变化。连续几次查看中断文件。

我建议您花一点时间将/proc/meminfo 文件中的数据与您在使用像freetop这样的命令时获得的信息进行比较。您认为这些实用工具和许多其他工具是从哪里获得信息的?就在/proc 文件系统中,这就是。

让我们更深入地看看 PID 1。像所有进程目录一样,它包含关于具有该 ID 的进程的信息。让我们来看看这些信息。

实验 6-2

让我们进入并查看/proc/1 目录的内容。然后使用cat命令查看 cmdline 文件的内容。

[root@testvm1 proc]# cd 1 ; cat cmdline
/usr/lib/systemd/systemd--switched-root--system--deserialize24

我们可以从 cmdline 的内容中看出,这是 systemd,所有程序之母。在所有旧版本和一些当前版本的 Linux 上,PID 1 将是 init 程序。花些时间研究一下这个过程中其他一些文件和目录的内容。

还需要花一些时间来研究一些其他的 PID 目录。

/proc 文件系统中有大量可用的信息,可以很好地用来解决问题。事实上,对正在运行的内核进行动态更改而无需重启是一个强大的工具,它允许您对 Linux 内核进行即时更改,以解决问题、启用功能或调整性能。让我们看一个例子。

Linux 非常灵活,可以做很多有趣的事情。其中一件很酷的事情是,任何带有多个网络接口卡(NIC)的 Linux 主机都可以充当路由器。只需要一点知识、一个简单的命令和对 iptables 防火墙的一些修改。

路由是由内核管理的任务。所以打开(或关闭)它需要我们改变一个内核配置参数。幸运的是,我们不需要重新编译内核,这是在/proc 文件系统中公开内核配置的好处之一。我们将打开 IP 转发,它提供了内核的基本路由功能。

实验 6-3

这个小命令行程序将/proc/sys/net/ipv4 目录作为 PWD,打印应该为零(0)的 ip_forward 文件的当前状态;将其设置为“1”;然后打印它的新状态,应该是 1。路由现已打开。确保在一行中输入命令。

[root@testvm1 ipv4]# cd /proc/sys/net/ipv4 ; cat ip_forward ; echo 1 > ip_forward ; cat ip_forward
0
1

恭喜你!您已经改变了正在运行的内核的配置。

为了完成 Linux 主机作为路由器的全部功能的配置,需要对 iptables 防火墙或您可能使用的任何防火墙软件以及路由表进行额外的更改。这些改变将定义路由的细节,例如哪些分组被路由到哪里。虽然超出了本书的范围,但我已经写了一篇文章 3 详细介绍了如何配置路由表,如果你想了解更多信息,可以参考这篇文章。我还写了一篇文章 4 ,简要介绍了将 Linux 主机转变为路由器所需的所有步骤,包括在重启后保持 IP 转发。

当您在/proc 文件系统中时,请多看看——跟随您自己的好奇心去探索这个重要文件系统的不同领域。

警告

我有意选择修改一个我熟悉的内核参数,它不会对您的 Linux 主机造成任何伤害。当您探索/proc 文件系统时,您不应该做任何进一步的更改。

/sys 文件系统

/sys 目录是另一个虚拟文件系统,Linux 使用它来维护供内核和系统管理员使用的特定数据。/sys 目录为计算机硬件中的每种总线类型分级维护硬件列表。

快速浏览一下/sys 文件系统就可以看到它的基本结构。

实验 6-4

在这个实验中,我们简要地看一下/sys 目录的内容,然后是它的一个子目录/sys/block。

[root@testvm1 sys]# cd /sys ; ls

block  bus  class  dev  devices  firmware  fs  hypervisor  kernel  module  power
[root@testvm1 sys]# ls block
dm-0  dm-1  sda  sr0

/sys/block 中有不同类型的磁盘(块)设备,sda 设备是其中之一。这通常是该虚拟机中的第一个硬盘,在这种情况下也是唯一的硬盘。让我们快速看一下 sda 目录的一些内容。

[root@testvm1 sys]# ls block/sda
alignment_offset   events_async       queue      slaves
bdi                events_poll_msecs  range      stat
capability         ext_range          removable  subsystem
dev                holders            ro         trace
device             inflight           sda1       uevent
discard_alignment  integrity          sda2
events             power              size
[root@testvm1 sys]# cat block/sda/dev
8:0
[root@testvm1 sys]# ls block/sda/device
block                               ncq_prio_enable
bsg                                 power
delete                              queue_depth
device_blocked                      queue_ramp_up_period
device_busy                         queue_type
dh_state                            rescan
driver                              rev
eh_timeout                          scsi_device
evt_capacity_change_reported        scsi_disk
evt_inquiry_change_reported         scsi_generic
evt_lun_change_reported             scsi_level
evt_media_change                    state
evt_mode_parameter_change_reported  subsystem
evt_soft_threshold_reached          sw_activity
generic                             timeout
inquiry                             type
iocounterbits                       uevent
iodone_cnt                          unload_heads
ioerr_cnt                           vendor
iorequest_cnt                       vpd_pg80
modalias                            vpd_pg83
model                               wwid
[root@testvm1 sys]# cat block/sda/device/model
VBOX HARDDISK

为了从这最后一个命令中获得更真实的信息,我还在我自己的物理硬盘上执行了这个操作,而不是在我在这些实验中使用的虚拟机上,看起来像这样。

[root@david proc]# cat /sys/block/sda/device/model
ST320DM000-1BD14

这些信息更像是您在自己的硬件主机上看到的,而不是在虚拟机上看到的。现在让我们使用smartctl命令来显示同样的信息和更多的信息。由于更真实的数据,我使用了我的物理主机。我还削减了结果末尾的大量输出。

[root@david proc]# smartctl -x /dev/sda
smartctl 6.5 2016-05-07 r4318 [x86_64-linux-4.13.16-302.fc27.x86_64] (local build)
Copyright (C) 2002-16, Bruce Allen, Christian Franke, www.smartmontools.org

=== START OF INFORMATION SECTION ===
Model Family:     Seagate Barracuda 7200.14 (AF)
Device Model:     ST320DM000-1BD14C
Serial Number:    Z3TT43ZK
LU WWN Device Id: 5 000c50 065371517
Firmware Version: KC48
User Capacity:    320,072,933,376 bytes [320 GB]
Sector Sizes:     512 bytes logical, 4096 bytes physical
Rotation Rate:    7200 rpm
Device is:        In smartctl database [for details use: -P show]
ATA Version is:   ATA8-ACS T13/1699-D revision 4
SATA Version is:  SATA 3.0, 6.0 Gb/s (current: 6.0 Gb/s)
Local Time is:    Wed Dec 13 13:31:36 2017 EST
SMART support is: Available - device has SMART capability.
SMART support is: Enabled
AAM level is:     208 (intermediate), recommended: 208
APM feature is:   Unavailable
Rd look-ahead is: Enabled
Write cache is:   Enabled
ATA Security is:  Disabled, frozen [SEC2]
Wt Cache Reorder: Enabled

=== START OF READ SMART DATA SECTION ===
SMART overall-health self-assessment test result: PASSED

General SMART Values:
<snip>

如果我没有切断最后一个命令的结果,它还会显示故障指示器和温度历史记录等信息,这有助于确定硬盘问题的来源。

smartctl实用程序从/sys 文件系统获得它所使用的数据,就像其他实用程序从/proc 文件系统获得它们的数据一样。

如您所见,该目录中的数据包含大量关于设备的信息。

/sys 文件系统包含关于 PCI 和 USB 系统总线硬件以及任何附加设备的数据。例如,内核可以使用这个信息来确定使用哪个设备驱动程序。

实验 6-5

让我们来看看关于计算机上的一种总线 USB 总线的一些信息。我将直接跳到/sys 文件系统中设备的位置;你可能需要自己做一些探索来找到你感兴趣的项目。

[root@testvm1 ~]# ls /sys/bus/usb/devices/usb2
2-0:1.0             bMaxPacketSize0    driver                  quirks
authorized          bMaxPower          ep_00                   removable
authorized_default  bNumConfigurations idProduct               remove
avoid_reset_quirk   bNumInterfaces     idVendor                serial
bcdDevice           busnum             interface_authorized    speed_default
bConfigurationValue configuration      ltm_capable             subsystem
bDeviceClass        descriptors        manufacturer            uevent
bDeviceProtocol     dev                maxchild                urbnum
bDeviceSubClass     devnum             power                   version
bmAttributes        devpath            product

上面的结果显示了提供有关该特定设备的数据的一些文件和目录。但是有一种更简单的方法,那就是使用核心实用程序,这样我们就不必自己去做所有的探索了。

[root@david ~]# lsusb
Bus 002 Device 005: ID 1058:070a Western Digital Technologies, Inc. My Passport Essential (WDBAAA), My Passport for Mac (WDBAAB), My Passport Essential SE (WDBABM), My Passport SE for Mac (WDBABW
Bus 002 Device 004: ID 05e3:0745 Genesys Logic, Inc. Logilink CR0012
Bus 002 Device 003: ID 1a40:0201 Terminus Technology Inc. FE 2.1 7-port Hub
Bus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 006 Device 005: ID 0bc2:ab1e Seagate RSS LLC Backup Plus Portable Drive
Bus 006 Device 003: ID 2109:0812 VIA Labs, Inc. VL812 Hub
Bus 006 Device 002: ID 2109:0812 VIA Labs, Inc. VL812 Hub
Bus 006 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 005 Device 007: ID 2109:2812 VIA Labs, Inc. VL812 Hub
Bus 005 Device 004: ID 2109:2812 VIA Labs, Inc. VL812 Hub
Bus 005 Device 006: ID 04f9:0042 Brother Industries, Ltd HL-2270DW Laser Printer
Bus 005 Device 005: ID 04f9:02b0 Brother Industries, Ltd MFC-9340CDW
Bus 005 Device 003: ID 050d:0234 Belkin Components F5U234 USB 2.0 4-Port Hub
Bus 005 Device 002: ID 2109:3431 VIA Labs, Inc. Hub
Bus 005 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 005: ID 046d:c52b Logitech, Inc. Unifying Receiver
Bus 001 Device 006: ID 17f6:0822 Unicomp, Inc
Bus 001 Device 003: ID 051d:0002 American Power Conversion Uninterruptible Power Supply
Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 003 Device 010: ID 0424:4063 Standard Microsystems Corp.
Bus 003 Device 009: ID 0424:2640 Standard Microsystems Corp. USB 2.0 Hub
Bus 003 Device 008: ID 0424:2514 Standard Microsystems Corp. USB 2.0 Hub
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

我再次在我自己的物理主机上运行最后一个命令,因为它会产生更有趣的结果。

除 PCI 总线外,lspci命令执行与lsusb相同的功能。继续,自己尝试一下lspci命令。

我有时发现查找特定的硬件设备很有帮助,尤其是新添加的设备。与/proc 目录一样,有一些核心实用程序,如lsusblspci,使我们可以轻松查看连接到主机的设备的信息。

防火墙

selinux 伪文件系统类似于其他伪文件系统,比如/proc。它可以位于/selinux 或/sys/fs/selinux。只有在启用 SELinux 时,才会创建和显示这个文件系统。

当/selinux 文件系统存在时,它包含与内核密切相关的文件,就像/proc 中的文件一样。当 SELinux 被启用时,这个文件系统提供了一个窗口来查看正在运行的内核的安全功能。

Fedora 和其他与 Red Hat 相关的发行版默认在目标模式下启用 SELinux。您的发行版可能已经关闭了它,或者像包括我在内的许多系统管理员一样已经关闭了它。下一个实验帮助我们探索 selinux 文件系统,但是我们首先需要了解一个已知的状态。

实验 6-6

注意仅在指定用于培训目的的主机或虚拟机上执行本实验。在任何情况下,都不要在生产主机上执行此实验。

如果您的主机启用了 SELinux,我们将在继续之前禁用它。首先让我们看看它是否被禁用。

[root@testvm1 ~]# sestatus
SELinux status:                 enabled
SELinuxfs mount:                /sys/fs/selinux
SELinux root directory:         /etc/selinux
Loaded policy name:             targeted
Current mode:                   enforcing
Mode from config file:          enforcing
Policy MLS status:              enabled
Policy deny_unknown status:     allowed
Memory protection checking:     actual (secure)
Max kernel policy version:      31

SELinux 在我的 Fedora 主机上以目标模式启用。如果您的主机是这种情况,请记下为 SELinuxfs 挂载点指定的位置。还要记下当前的模式,应该是强制模式还是许可模式。

禁用 SELinux。用您喜欢的编辑器打开/etc/sysconfig/selinux 文件。将 SELINUX=行更改为 disabled。完成后,该文件应该如下所示。

# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
#     enforcing - SELinux security policy is enforced.
#     permissive - SELinux prints warnings instead of enforcing.
#     disabled - No SELinux policy is loaded.
SELINUX=disabled
# SELINUXTYPE= can take one of these three values:
#     targeted - Targeted processes are protected,
#     minimum - Modification of targeted policy. Only selected processes are protected.
#     mls - Multi Level Security protection.
SELINUXTYPE=targeted

现在重新启动主机。重新启动需要一些时间,因为 SELinux 必须从它所保护的文件系统的文件中删除它的标签。移除这些标签后,主机将再次重启。

主机重新启动后,以 root 用户身份登录。您可以在其中一个虚拟控制台中完成这项工作,因为实验的这一部分不需要 GUI。

每个人都应该执行这个实验的剩余部分,不管 SELinux 是启用还是禁用。

尝试在上面提到的位置找到 selinux 文件系统。它不应该出现。

[root@testvm1 ~]# ls -l /sys/fs/selinux
ls: cannot access '/sys/fs/selinux': No such file or directory

现在重新启用 SELINUX,方法是使用您的编辑器将 SELinux 行改回 enforcing 或 permissive,也就是我们第一次更改它之前的值。然后重新启动系统,并等待它完成第二次重新启动。

以 root 用户身份登录到虚拟控制台,或者登录到您喜欢的桌面。如果您登录到桌面,请以 root 用户身份打开终端仿真程序窗口。现在尝试查看 selinux 目录。

[root@testvm1 ~]# ls -l /sys/fs/selinux/
total 0
-rw-rw-rw-.  1 root root    0 Feb  3  2018 access
dr-xr-xr-x.  2 root root    0 Feb  3  2018 avc
dr-xr-xr-x.  2 root root    0 Feb  3  2018 booleans
-rw-r--r--.  1 root root    0 Feb  3  2018 checkreqprot
dr-xr-xr-x. 99 root root    0 Feb  3  2018 class
--w-------.  1 root root    0 Feb  3  2018 commit_pending_bools
-rw-rw-rw-.  1 root root    0 Feb  3  2018 context
-rw-rw-rw-.  1 root root    0 Feb  3  2018 create
-r--r--r--.  1 root root    0 Feb  3  2018 deny_unknown
--w-------.  1 root root    0 Feb  3  2018 disable
-rw-r--r--.  1 root root    0 Feb  3  2018 enforce
dr-xr-xr-x.  2 root root    0 Feb  3  2018 initial_contexts
-rw-------.  1 root root    0 Feb  3  2018 load
-rw-rw-rw-.  1 root root    0 Feb  3  2018 member
-r--r--r--.  1 root root    0 Feb  3  2018 mls
crw-rw-rw-.  1 root root 1, 3 Feb  3  2018 null
-r--r--r--.  1 root root    0 Feb  3  2018 policy
dr-xr-xr-x.  2 root root    0 Feb  3  2018 policy_capabilities
-r--r--r--.  1 root root    0 Feb  3  2018 policyvers
-r--r--r--.  1 root root    0 Feb  3  2018 reject_unknown
-rw-rw-rw-.  1 root root    0 Feb  3  2018 relabel
-r--r--r--.  1 root root    0 Feb  3  2018 status
-rw-rw-rw-.  1 root root    0 Feb  3  2018 user
--w--w--w-.  1 root root    0 Feb  3  2018 validatetrans

如果看不到 selinux 目录的内容,请验证位置是否正确,然后重试。

问题解决

我能想到的坚持 Linux FHS 的最好的理由之一是尽可能地使解决问题变得容易。使用 Linux 文件系统分层标准促进了一致性和简单性,这使得问题的解决更加容易。知道在 Linux 文件系统目录结构中的什么地方可以找到东西,这让我避免了多次无休止的摸索。

我发现我所使用的发行版中提供的大多数核心实用程序、Linux 服务和服务器在使用/etc 目录及其子目录存储配置文件时是一致的。这意味着为发行版提供的行为不当的程序或服务找到配置文件应该很容易。

我通常使用/etc 中的许多 ASCII 文本文件来配置 SendMail、Apache、DHCP、NFS、NTP、DNS 等等。我总是知道在哪里可以找到我需要为这些服务修改的文件,它们都是开放和可访问的,因为它们是 ASCII 文本,这使得它们对计算机和人类都是可读的。

注意

这似乎与 BIND DNS 不一致,因为它的 zone、reverse 和根提示文件 named.ca 都位于/var/named 中。这并不矛盾,因为那些不是配置文件,它们是数据库文件,正如你在表 6-1 中看到的,是/var 的功能之一。此外,那些“可变”文件可能被外部服务器修改,例如当主名称服务器更新辅助名称服务器的数据库时。让这些外部服务器远离我们计算机上的主配置目录/etc 是一个非常好的主意。

绑定数据库文件的位置与 FHS 一致。但我确实花了一段时间才弄明白为什么会这样,更不用说对 FHS 的广泛研究了。有时我的好奇心会带我走很长的弯路,但我总是从这些旅程中学到很多东西,这对后来很有用。

不正确地使用文件系统

当我在 Research Triangle Park 的一家大型技术公司担任实验室管理员时,发生了一个涉及文件系统使用不当的情况。我们的一个开发人员在错误的位置安装了一个应用。应用崩溃是因为/var 文件系统已满,而存储在该文件系统的/var/log 中的日志文件由于/var 中缺少空间而无法附加新消息来指示/var 文件系统已满。然而,由于关键的/ (root)和/tmp 文件系统没有填满,系统仍然保持正常运行。删除有问题的应用,并在/opt 文件系统中重新安装它,这样就解决了这个问题。我还和最初安装的开发人员讨论了一下。

电子邮件收件箱

有很多次,我需要解决电子邮件收件箱的问题。我发现一些垃圾邮件不符合适当的电子邮件标准,至少一些电子邮件客户端在查看和管理这些垃圾邮件以及电子邮件收件箱文件中的一些垃圾邮件时存在问题。

你知道电子邮件收件箱在电子邮件服务器上的什么位置吗?它在/var/spool/mail 中,那里的每个收件箱文件都有电子邮件用户 ID 的名称。凭借一点点运气和良好的研究,我能够通过删除令人讨厌的垃圾邮件来修复收件箱。

即使我从来不需要对特定服务的配置文件进行修改,我也知道它几乎总是可以在/etc 目录中找到。这大大减少了我需要做的搜索量。

坚持标准

那么,作为系统管理员,我们如何坚持 Linux FHS 呢?这其实很简单,在表 6-1 中有一个提示。/usr/local 目录是存储本地创建的可执行文件及其配置文件的地方。

对于本地程序,FHS 是指那些我们作为系统管理员自己创建的程序,以使我们的工作或其他用户的工作更容易。这包括我们编写的所有那些强大和通用的 Shell 程序。

这些程序应该位于/usr/local/bin 中,配置文件(如果有)应该位于/usr/local/etc 中。还有一个/var/local 目录,本地程序可以在其中存储自己的数据库文件。

这些年来,我写了相当多的 shell 程序,至少花了五年时间,我才明白在主机上安装我自己的软件的合适位置。在某些情况下,我甚至忘记了我把它们安装在哪里。在其他情况下,我将配置文件安装在/etc 而不是/usr/local/etc 中,并且我的文件在升级期间被覆盖。第一次发生的时候花了几个小时才追踪到。

通过在编写 shell 程序时遵守这些标准,我更容易记住我将它们安装在了哪里。对于其他系统管理员来说,通过只搜索我们作为系统管理员已经安装了这些程序及其文件的目录,也更容易找到东西。

________________________________
/ I have trouble remembering \
| where to put files, too.   |
\ The FHS can help.          /
 ----------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

这个文件放在哪里?

我过去安装简单的 Bash 程序时,只是简单地将文件复制到我工作的主机上的适当位置。有时我会忘记他们应该去哪里。随着程序数量的增加,我需要花费更多的时间来执行所有的任务,以安装越来越多的我自己的省时工具。

当我安装一台新计算机时,我发现了一个很好的方法来方便我的 shell 程序的安装,以及当它们需要被传播时的升级。我创建了一个 RPM,其中包含我的程序及其所有配置和其他辅助文件,以及放置每个文件的位置说明。RPM 还包含一个小的 Bash 脚本,它在安装后运行,以便执行某些配置任务,安装最新的更新,并安装一些我一直希望在我的 Linux 主机上安装的应用和实用程序,但安装程序通常不会安装它们。

从某种意义上说,创建这个 RPM 是懒惰的系统管理员的行为,自动安装大量的程序、字体、配置文件等等。有一段时间,我有太多的事情需要手动完成——通过终端上的单个命令——以至于我要花三四个小时的时间来完成这些任务。创建 RPM 后,现在需要几分钟运行 dnf 来安装 RPM。然后花一分钟时间输入命令来运行我编写的一个大型 Bash 程序,以执行我以前手工执行的所有其他安装、修改等等。shell 程序可能需要 20 分钟到一个小时左右才能运行完成,但是我不再需要监视每一个命令,所以我可以准备好手动运行下一个命令。我不需要悬停在计算机上;当自动化为我工作时,我可以去做其他更有效率的事情。

摘要

本章探讨了 Linux 文件系统。您已经了解到,分层目录结构具有适用于该结构中目录使用的标准。遵循由 Linux 基金会维护的 Linux 文件系统分层标准中概述的标准使用约定,为系统管理员提供了一些重要的好处。当目录树中包含数据的部分被创建为独立的文件系统并被单独挂载时,尤其如此。

Linux 文件系统不仅仅是存储程序和数据的地方。它是一个可以找到并充分利用有关操作系统、运行程序甚至硬件的数据和统计信息的地方。Linux FHS 定义了可以找到这些信息的目录,所以我们知道当我们需要时,这些信息会一直存在。

了解 Linux 文件系统中包含的内容及其位置是执行问题确定的一个不可或缺的工具。

七、拥抱 CLI

力量来自 Linux,力量来自命令行界面 CLI。Linux CLI 的强大之处在于它完全没有限制。在这一章中,我们将开始探索命令行的方式,这将阐明它实际上就在你的指尖的力量。

有许多访问命令行的选项,例如虚拟控制台、许多不同的终端模拟器和其他可以提高灵活性和生产率的相关软件。所有这些可能性都将在这一章中讨论,还有一些具体的例子说明命令行是如何执行看似不可能的任务的——或者只是满足尖头发老板。

在我们进一步讨论命令行之前,我们需要做一些准备工作。

准备

并非所有的发行版都安装了我们在本章中需要的几个软件包,所以我们现在就安装它们。如果已经安装了这些软件包中的一个或多个,将会显示一条消息指出这一点,但其余的软件包仍将正确安装。将安装一些额外的软件包来满足我们正在安装的软件包的先决条件。

我的包管理器是 dnf,但是你应该使用你的发行版提供的包管理器。以 root 用户身份执行此操作。

[root@testvm1 ~]# dnf -y install konsole 
tilix

screen 
ksh

tcsh 
zsh

在我的测试中,已经安装了 VM Konsole 和 screen,但是命令安装了 ksh、csh、zsh、tilix 和其他三个包来满足依赖关系。

定义命令行

命令行是一种在用户和操作系统之间提供文本模式界面的工具。命令行允许用户将命令输入计算机进行处理并查看结果。

Linux 命令行接口是用 bash(Bourne shell)、csh (C shell)和 ksh (Korn shell)这样的 shell 实现的,这里仅举三个例子。任何 shell 的功能都是将用户输入的命令传递给操作系统,操作系统执行命令并将结果返回给 shell。

对命令行的访问是通过某种终端接口进行的。现代 Linux 计算机中常见的终端接口主要有三种类型,但术语可能会令人混淆。所以,请允许我详细地定义这些术语以及其他一些与命令行相关的术语。

CLI 术语

有几个与命令行相关的术语经常互换使用。当我第一次开始使用 Unix 和 Linux 时,这种对术语的不加区别的使用给我造成了很大的困惑。我认为对系统管理员来说,理解控制台、虚拟控制台、终端、终端仿真器、终端会话和 shell 这些术语之间的区别是很重要的。

当然,只要你能表达你的观点,你可以使用任何对你有用的术语。在本书中,我将尽可能做到精确,因为现实情况是,这些术语的含义存在重大差异,有时这很重要。

命令提示符

命令提示符是一串像这样的字符,它有一个闪烁的光标,等待——提示——你输入命令。

[student@testvm1 ~]$ ◾

现代 Linux 安装中典型的命令提示符由用户名组成;主机名;和当前工作目录(PWD),也称为“当前”目录,都用方括号括起来。波浪号(~)字符表示主目录。

命令行

命令行是终端上的一行,包含命令提示和您输入的任何命令。

命令行界面

命令行界面是 Linux 操作系统的文本模式用户界面,允许用户键入命令并以文本输出的形式查看结果。

末端的

终端是一种旧的硬件,它提供了与大型机或 Unix 计算机主机交互的手段。终端不是电脑;终端仅仅连接到大型机和 Unix 系统。终端——硬件类型——通常通过一条长的串行电缆连接到主机。如图 7-1 所示的 DEC VT100 终端通常被称为“哑终端”,以区别于连接到大型机或 Unix 主机时作为终端的 PC 或其他小型计算机。哑终端有足够的逻辑来显示来自主机的数据,并将击键传送回主机。所有的处理和计算都在终端所连接的主机上进行。

img/462716_1_En_7_Fig1_HTML.png

图 7-1

DEC VT100 哑终端。本文件根据知识共享署名 2.0 通用许可协议进行许可。作者:杰森·斯科特img/462716_1_En_7_Figa_HTML.jpg

甚至更古老的终端,如机械电传打字机(TTY),比阴极射线管显示器的普遍使用还早。他们使用新闻纸质量的纸卷来记录命令的输入和结果。我上的第一堂大学计算机编程课使用了这些 TTY 设备,它们通过电话线以每秒 300 比特的速度连接到几百英里外的阿格(是的,通用电气)分时计算机。那时我们大学买不起一台自己的电脑。

许多与命令行相关的术语都源于这两种类型的哑终端的历史使用。例如,术语 TTY 仍然被广泛使用,但是我已经很多年没见过真正的 TTY 设备了。再次查看您的 Linux 或 Unix 计算机的/dev 目录。你会发现大量的 TTY 设备文件。

注意

我们在第五章中讨论了设备文件。

终端设计的唯一目的是允许用户通过输入命令和在纸卷或屏幕上查看结果来与他们所连接的计算机进行交互。术语“终端”倾向于暗示一个独立于计算机的硬件设备,同时用于与计算机通信和交互(图 7-2 )。

img/462716_1_En_7_Fig2_HTML.jpg

图 7-2

Unix 开发者 Ken Thompson 和 Dennis Ritchie。汤普森正坐在一台用于与 Unix 计算机接口的电传打字终端前。彼得·哈默——由马格努斯·曼斯克上传img/462716_1_En_7_Figb_HTML.jpg

安慰

控制台是一种特殊的终端,因为它是连接到主机的主要终端。它是系统操作员用来输入命令和执行任务的终端,这些任务在与主机相连的其它终端上是不允许的。当出现问题时,控制台也是主机显示系统级错误消息的唯一终端。

可以有许多终端连接到大型机和 Unix 主机,但只有一个终端是或可以充当控制台。在大多数大型机和 Unix 主机上,控制台通过专门为控制台设计的专用连接进行连接。

与 Unix 一样,Linux 也有运行级别,一些运行级别(如运行级别 1、单用户模式和恢复模式)仅用于维护。在这些运行级别中,只有控制台可以允许系统管理员与系统交互并执行维护。

注意

KVM 代表键盘、视频和鼠标,这三种设备是大多数人用来与他们的计算机进行交互的。

在 PC 上,物理控制台通常是直接连接到计算机的键盘、显示器,有时还有鼠标(KVM)。这些是用于在 BIOS 引导序列期间与 BIOS 进行交互的物理设备,可以在 Linux 引导过程的早期阶段用于与 GRUB 进行交互,并选择不同的内核进行引导或修改引导命令以引导到不同的运行级别。

由于 KVM 设备与计算机的紧密物理连接,系统管理员必须在引导过程中亲自出现在该控制台上,以便与计算机进行交互。在引导过程中,系统管理员无法进行远程访问,只有当 SSHD 服务启动并运行时,远程访问才可用。

虚拟控制台

运行 Linux 的现代个人电脑和服务器通常没有可以用作控制台的哑终端。Linux 通常为多个虚拟控制台提供功能,允许从单个键盘和显示器进行多次登录。Red Hat Linux、CentOS 和 Fedora Linux 通常为文本模式登录提供六到七个虚拟控制台。如果使用图形界面,第一个虚拟控制台 vc1 将成为 X Window 系统(X)启动后的第一个图形(GUI)会话,而 vc7 将成为第二个 GUI 会话。见图 7-3 。

img/462716_1_En_7_Fig3_HTML.jpg

图 7-3

虚拟控制台 2 的登录提示

每个虚拟控制台被分配给对应于控制台编号的功能键。因此 vc1 将被分配给功能键 F1,依此类推。在这些会话之间切换很容易。在你的电脑上你可以按住 Ctrl-Alt 键,按下 F2 切换到 vc2。然后按住 Ctrl-Alt 键,按 F1 切换到 vc1 和通常的图形桌面界面。如果没有 GUI 运行,vc1 将只是另一个文本控制台。

虚拟控制台提供了一种使用单个物理系统控制台、键盘、视频显示器和鼠标(KVM)访问多个控制台的方法。这为管理员执行系统维护和解决问题提供了更大的灵活性。还有一些其他方法来增加灵活性,但如果您可以物理访问系统或直接连接的 KVM 设备或一些逻辑 KVM 扩展(如 Integrated Lights Out 或 iLO ),虚拟控制台总是可用的。在某些环境中,screen 命令等其他方法可能不可用,GUI 可能在大多数服务器上也不可用。

img/462716_1_En_7_Fig4_HTML.png

图 7-4

打开了两个选项卡的 Konsole 终端仿真程序窗口

终端仿真程序

终端仿真器是一种模拟硬件终端(如 VT100)的软件程序。目前大多数终端仿真器可以仿真几种不同类型的硬件终端(图 7-4 )。大多数终端模拟器都是运行在任何 Linux 图形桌面环境下的图形程序,比如 KDE、Cinnamon、LXDE、GNOME 等等。Linux 控制台1 是 Linux 虚拟控制台的终端仿真器。

第一个终端仿真器是 Xterm,2T3,最初是由 Thomas Dickey 在 1984 年开发的。 3 Xterm 仍然被维护,并被打包成许多现代 Linux 发行版的一部分。

其他终端模拟器还包括 Konsole、 4 Tilix、 5 (图 7-5 )、rxvt、6gnome-terminal、 7 终结者、 8 等等。每个终端模拟器都有一组吸引特定用户群的有趣特性。一些具有在单个窗口中打开多个标签或终端的能力。其他的仅提供执行其功能所需的最小特征集,并且通常在要求小尺寸和效率时使用。

img/462716_1_En_7_Fig5_HTML.jpg

图 7-5

打开了几个会话的 Tilix 实例

我最喜欢的终端模拟器是 Konsole 和 Tilix,因为它们提供了在一个窗口中拥有多个终端模拟器会话的能力。Konsole 使用多个选项卡来实现这一点,我可以在这些选项卡之间切换。

Tilix 提供了在一个窗口会话中平铺多个仿真器会话以及提供多个会话的能力。图 7-5 显示了 Tilix 的一个实例,在左侧边栏中显示了两个会话。可见会话虽然部分被侧边栏覆盖,但有三个终端在运行。侧边栏允许在会话之间切换。

其他终端模拟器软件提供了这些功能,但不如 Konsole 和 Tilix 灵活和无缝。

伪终端

伪终端是一个 Linux 设备文件,为了与操作系统交互,终端模拟器被逻辑地附加到该文件上。伪终端的设备文件位于/dev/pts 目录中,仅在启动新的终端模拟器会话时创建。它可以是一个新的终端模拟器窗口,也可以是一个终端模拟器(如 Konsole)的现有窗口中的一个新选项卡或面板,它支持在单个窗口中进行多个会话。

/dev/pts 中的设备文件只是每个打开的模拟器会话的一个数字。例如,第一个模拟器是/dev/pts/1。

会议

会话是那些可以应用于不同事物的术语中的另一个,然而它基本上保持相同的含义。

最基本的应用是终端会话。这是一个连接到单个用户登录和 shell 的单个终端模拟器。因此,从最基本的意义上来说,会话是登录到本地或远程主机的单个窗口或虚拟控制台,其中运行着命令行 shell。

Tilix 终端仿真器使用术语会话来表示其中有一个或多个终端打开的窗格。在这种情况下,窗格是会话,每个子窗口都是终端。你可以在图 7-5 中看到这一点。

Shell 是操作系统的命令解释器。Linux 可用的许多 shells 中的每一个都将用户或系统管理员输入的命令解释成操作系统可用的形式。当结果返回到 shell 程序时,它会在终端上显示它们。

大多数 Linux 发行版的默认 shell 是 bash shell。bash 代表 Bourne Again Shell,因为 bash shell 基于较早的 Bourne shell,它是由 Steven Bourne 在 1977 年编写的。还有许多其他的 Shell。我在这里列出的四个是我最常遇到的,但是还有很多其他的。 9

  • csh——面向喜欢 C 语言语法的程序员的 C shell。

  • ksh——Korn shell,由 David Korn 编写,受到 Unix 用户的欢迎。

  • tcsh——csh 的一个版本,具有更易于使用的特性。

  • zsh——它结合了其他流行 shells 的许多特性。

所有 shells 都有一些内置的命令,可以补充或替换核心实用程序提供的命令。打开 bash 的手册页,找到“SHELL BUILTIN COMMANDS”一节,查看 SHELL 本身提供的命令列表。

我用过 C shell、Korn shell 和 Z shell。与我尝试过的其他 shell 相比,我仍然更喜欢 bash shell。每个 shell 都有自己的个性和语法。有些对你来说会更好,有些则不那么好。使用最适合你的方法,但这可能需要你至少尝试其他方法。

可以轻松换壳。

实验 7-1

因为大多数 Linux 发行版默认使用 bash shell,所以我假设您一直在使用 bash shell,并且它是您的默认 shell。在为本章做准备时,我们安装了另外三个 shells,ksh、tcsh 和 zsh。

以用户学生的身份做这个实验。首先,查看您的命令提示符,应该是这样的:

[student@testvm1 ~]$

这是非根用户的标准 bash 提示符。现在我们把这个改成 ksh shell。只需输入 Shell 的名称。

[student@testvm1 ~]$ ksh
$

您可以通过提示的不同来判断这是一个不同的 shell。运行几个简单的命令,比如lsfree,看看这些命令的工作方式有什么不同。这是因为除了内置命令之外,大多数命令都是独立于 shell 的。

尝试向上滚动以获得类似 bash 的命令历史。它不起作用。

$ zsh
This is the Z Shell configuration function for new users,
zsh-newuser-install.
You are seeing this message because you have no zsh startup files
(the files .zshenv, .zprofile, .zshrc, .zlogin in the directory
~).  This function can help you with a few settings that should
make your use of the shell easier.

You can:

(q)  Quit and do nothing. The function will be run again next time.

(0)  Exit, creating the file ~/.zshrc containing just a comment.
     That will prevent this function being run again.

(1)  Continue to the main menu.

--- Type one of the keys in parentheses ---

如果继续,您将看到一系列菜单,这些菜单将帮助您配置 Z shell 以满足您的需求——这是您在此阶段最了解的。我选择“Q ”,只是为了进入与 bash 提示符略有不同的提示符。

[student@testvm1]~%

在 Z shell 中运行几个简单的命令。然后键入 exit 两次,回到最初的 bash shell。

[student@testvm1]~% w
 14:30:25 up 3 days,  6:12,  3 users,  load average: 0.00, 0.00, 0.02
USER     TTY        LOGIN@   IDLE   JCPU   PCPU WHAT
student  pts/0     Tue08    0.00s  0.07s  0.00s w
root     pts/1     Wed06   18:48   0.26s  0.26s -bash
student  pts/2     08:14    6:16m  0.03s  0.03s -bash
[student@testvm1]~% exit
$ exit
[student@testvm1 ~]$

如果您已经在一个 bash shell 中启动了一个 bash shell,您认为会发生什么?

[student@testvm1 ~]$ bash
[student@testvm1 ~]$ ls
Desktop Documents Downloads Music Pictures Public Templates Videos
[student@testvm1 ~]$ exit
exit
[student@testvm1 ~]$

你只是进入了另一个狂欢派对。

这比表面上看起来更能说明问题。首先,每个壳都是一层。启动一个新的 shell 并不会终止以前的 shell。当您从 bash 启动 tcsh 时,bash shell 仍然存在;当您从 tcsh 退出时,您又回到了等待的 bash shell。

事实证明,这正是从 shell 中运行任何命令或进程时所发生的情况。该命令在它自己的会话中运行,父 shell(process)会一直等待,直到该子命令返回并且控制权返回给它,然后才能继续处理进一步的命令。

因此,如果您有一个运行其他命令的脚本——这是脚本的目的——脚本运行每个命令,等待它完成,然后继续运行下一个命令。

可以通过在命令末尾附加一个&符号来修改这种行为,这将调用的命令放在后台,并允许用户继续与 shell 交互,或者让脚本继续处理更多的命令。您只希望使用不需要进一步人工交互或输出到 STDOUT 的命令来实现这一点。当稍后运行的其他命令需要该命令的结果时,您也不希望在后台运行该命令,但可能是在后台任务完成之前。

您可以使用chsh命令更改您的 shell,这样它将在您每次登录和启动新的终端会话时保持不变。

安全 Shell(SSH)

宋承宪其实不是一个壳。ssh命令在作为客户端的自身和另一台运行 SSHD 服务器的主机之间启动安全通信链路。服务器端使用的实际命令 shell 是服务器端为该帐户设置的默认 shell,比如 bash shell。

屏幕

您可能首先认为“屏幕”是显示 Linux 桌面的设备。这是一个意思。

对于像我们这样的极客来说,screen 是一个程序,一个屏幕管理器,它增强了命令行的能力。screen 实用程序允许在单个终端会话中启动多个 shell,并提供了在运行的 shell 之间导航的方法。

还记得当你有一个运行程序的远程会话,而通信链接失败的时候吗?我已经经历过很多次了。当发生这种情况时,正在运行的程序也被终止,我不得不从头重新启动它。这可能会非常令人沮丧。

筛选程序可以防止这种情况。即使由于网络连接失败而导致与远程主机的连接中断,屏幕会话也将继续运行。它还允许从终端会话断开屏幕会话,并在以后从同一台或不同的计算机重新连接。在屏幕终端会话中运行的所有 CLI 程序将继续在远程主机上运行。这意味着一旦通信重新建立,用户可以重新登录到远程主机,并在远程命令行使用screen -r命令将屏幕会话重新连接到终端。

所以我可以在屏幕上启动一些终端会话,使用 Ctrl-a + d 从屏幕上断开连接,然后注销。然后,我可以转到另一个位置,登录到一个主机,SSH 到主机运行屏幕,登录,并使用screen -r命令重新连接到屏幕会话,所有终端会话及其各自的程序仍将运行。

screen 命令在某些环境中很有用,在这些环境中,对硬件控制台的物理访问不可用于提供对虚拟控制台的访问,但是需要多个 shells 的灵活性。你可能会发现使用 screen 程序很方便,在某些情况下,为了快速有效地工作,这样做是必要的。

实验 7-2

在这个实验中,我们探索屏幕程序的使用。以学生用户的身份在终端会话中执行本实验。

在我们开始之前,让我们讨论如何向屏幕程序本身发送命令,以便做一些事情,比如打开一个新的终端和在运行的终端会话之间切换。

在这个实验中,我提供了一些指令,比如“按下 Ctrl-a + c ”来打开一个新的终端。这意味着你应该在按“a”键的同时按住 Control 键;此时你可以释放控制键和“a”键,因为你已经提醒屏幕程序下一次击键是针对它的;现在按“c”键。这一系列的击键看起来有点复杂,但是我很快就学会了肌肉记忆,现在已经很自然了。我相信你也一样。

对于显示该屏幕会话中所有打开终端列表的序列 Ctrl-a + " (双引号)序列,执行 Ctrl-a ,释放这些键,然后按下 shift + "

我发现这个过程的唯一例外是 Ctrl-a + a 序列,它在最后两个终端会话之间切换。在松开 Ctrl 键之前,您必须继续按住 Control 键并连续按下“a”键两次。

  1. 输入screen命令,该命令将清除显示并离开命令提示符。您现在处于屏幕显示管理器中,一个终端会话打开并显示在窗口中。

  2. 键入任何命令,例如ls,除了命令提示符之外,还会在终端会话中显示一些内容。

  3. 按下 Ctrl-a + c 在屏幕会话中打开一个新的 shell。

  4. 在这个新终端中输入不同的命令,例如df –h

  5. 键入 Ctrl-a + a 在端子之间切换。

  6. 输入 Ctrl-a + c 打开第三个端子。

  7. 键入 Ctrl-a + " 列出打开的终端。使用向上/向下箭头键选择除最后一个以外的任何一个,并点击 Enter 键切换到该终端。

  8. 要关闭一个终端,键入 exit 并按下 Enter 键。

  9. 键入命令 Ctrl-a + " 以验证终端是否已消失。请注意,具有您选择关闭的编号的端子不再存在,其他端子也没有重新编号。

  10. 使用 Ctrl-a + c 重新打开一个新的终端。

  11. 键入 Ctrl-a + " 以验证新终端已经创建。请注意,它已在之前关闭的终端位置打开。

  12. 要从屏幕会话和所有打开的终端断开,按下 Ctrl-a + d 。请注意,这将使所有终端和其中的程序保持完整并仍在运行。

  13. 在命令行输入命令screen -list命令,列出所有当前屏幕会话。如果有多个屏幕会话,这有助于确保重新连接到正确的屏幕会话。

  14. 使用命令屏幕****–r重新连接到活动屏幕会话。如果打开了多个活动屏幕会话,则会显示一个列表,您可以选择想要连接的会话;您必须输入想要连接的屏幕会话的名称。

我建议您不要在现有的屏幕会话中打开新的屏幕会话。很难在终端之间进行切换,因为屏幕程序并不总是理解向哪个嵌入式会话发送命令。

我一直使用屏幕程序。它是一个强大的工具,为我在命令行上工作提供了极大的灵活性。

GUI 和 CLI

您可能喜欢并使用许多图形用户界面中的任何一种,即桌面,几乎所有的 Linux 发行版都提供了这种界面;你甚至可以在它们之间切换,因为你会发现某个特定的桌面比如 KDE 更适合某些任务,而另一个比如 GNOME 更适合其他任务。但是您还会发现,管理 Linux 计算机所需的大多数图形工具只是实际执行这些功能的底层 CLI 命令的包装。

图形界面无法达到 CLI 的强大功能,因为 GUI 固有地局限于程序员决定您应该访问的那些功能。这就是 Windows 和其他限制性操作系统的工作方式。他们只允许你使用他们认为你应该拥有的功能和权力。这可能是因为他们认为你真的想屏蔽掉计算机的全部能量,或者是因为他们认为你没有能力处理这种水平的能量。

仅仅因为 GUI 在某些方面有局限性,并不意味着优秀的系统管理员不能利用它来简化他们的工作。我发现我可以更灵活地利用 GUI 来完成命令行任务。通过在桌面上允许多个终端窗口,或者通过使用高级终端仿真程序,比如为 GUI 环境设计的 Tilix 和 Konsole,我可以提高我的生产率。在桌面上打开多个终端使我能够同时登录多台计算机。我也可以多次登录任何一台计算机,使用我自己的用户 ID 打开多个终端会话,并以 root 用户身份打开更多的终端会话。

对我来说,让多个终端会话以多种方式随时可用,就是 GUI 的全部意义。GUI 还可以让我访问像 LibreOffice 这样的程序,我正在用它来写这本书,图形电子邮件和网络浏览应用等等。但是系统管理员的真正权力在命令行中。

Linux 使用由理查德·M·斯托曼、 10 又名 RMS 和许多其他贡献者编写的 GNU 核心实用程序,作为任何免费版本的 Unix 或类似 Unix 的操作系统所需的免费开源实用程序。GNU 核心实用程序是任何 GNU 操作系统(如 GNU/Linux)的基本文件、Shell 和文本操作实用程序,任何系统管理员都可以依赖它在每个版本的 Linux 上运行。此外,每个 Linux 发行版都有一组扩展的实用程序,可以提供更多的功能。

您可以输入命令info coreutils来查看 GNU 核心实用程序的列表,并选择单个命令来了解更多信息。您还可以使用 man 来查看这些命令的 man 页面,以及其他数百个 Linux 命令,这些命令也是每个发行版的标准。

非限制性接口

Linux CLI 是非限制性界面,因为它对您如何使用它没有任何限制。

根据定义,GUI 是一个非常严格的界面。你只能以规定的方式执行你被允许的任务,所有这些都是由程序员选择的。你不能超越编写代码的程序员的想象力的极限,或者——更有可能的是——头发尖尖的老板对程序员的限制。

在我看来,任何图形界面最大的缺点就是抑制了自动化的可能性。没有一个 GUI 提供真正自动化任务的能力。取而代之的是,只需反复点击鼠标,就可以对略有不同的数据多次执行相同或相似的操作。

另一方面,CLI 允许在执行任务时有很大的灵活性。这是因为每个 Linux 命令,不仅仅是 GNU 核心实用程序,而且是绝大多数 Linux 命令,都是使用 Linux 哲学的原则编写的,例如,“一切都是一个文件”,“总是使用 STDIO”,“每个程序应该做好一件事”,“避免强制用户界面”,等等。你明白了,我将在本书的后面讨论这些原则,所以如果你还不明白它们的意思,不要太担心。

SysAdmin 的底线是,当开发人员遵循这些原则时,命令行的力量可以被充分利用。

邮件列表

此示例突出了 CLI 自动化常见任务的能力的强大和灵活性。

在我的职业生涯中,我管理过几个 listservs,现在仍然如此。人们给我发电子邮件地址列表,让我添加到这些列表中。有一次,我收到了一个 Word 文档中的姓名和电子邮件地址列表,这些信息将被添加到我的一个列表中。

列表本身并不是很长,但是它的格式非常不一致。图 7-6 显示了该列表的缩略版本,其中包含名称和域名的更改。原始列表有多余的行、需要删除的方括号和圆括号之类的字符以及一些空行。将这些电子邮件添加到列表所需的格式是first last <email@example.com>

img/462716_1_En_7_Fig6_HTML.png

图 7-6

要添加到 listserv 的电子邮件地址原始文档的部分列表

很明显,我需要处理数据,以便将它转换成可接受的格式,输入到列表中。可以使用文本编辑器或文字处理器(如 LibreOffice Writer)对这个小文件进行必要的更改。然而,人们经常给我发这样的文件,所以用文字处理器来做这些修改就成了一件苦差事。尽管 Writer 有很好的搜索和替换功能,但每个字符或字符串都必须单独替换,并且没有办法保存以前的搜索。Writer 确实有一个非常强大的宏功能,但是我对它的两种语言 LibreOffice Basic 和 Python 都不熟悉。我确实知道 bash shell 编程。

我做了一个系统管理员应该做的事情——自动完成任务。我做的第一件事是将地址数据复制到一个名为 addresses.txt 的文本文件中,这样我就可以使用命令行工具来处理它。经过几分钟的工作,我开发了 bash 命令行程序,如图 7-7 所示,它产生了所需的输出,即 addresses2.txt 文件。换行是可以接受的,但是在命令完全输入之前不要按回车键。

img/462716_1_En_7_Fig7_HTML.png

图 7-7

这个 bash 命令行程序清理图 7-6 中的电子邮件地址数据,如果保存为可执行的 shell 脚本,可以多次重用

我将 bash 程序保存在一个可执行文件中,现在我可以在收到新列表的任何时候运行这个程序。有些列表相当短,如图 7-6 所示,但其他列表相当长,有时包含数百个地址和许多行不包含要添加到列表中的地址的“内容”。

认识到我的解决方案不是唯一的,这一点非常重要。bash 中有不同的方法来产生相同的输出,也可以使用其他语言,如 Python 和 Perl。当然,还有 LibréOffice Writer 宏。但是我总是可以把 bash 作为任何 Linux 发行版的一部分。我可以在任何 Linux 计算机上使用 bash 程序执行这些任务,甚至是没有 GUI 桌面和没有安装 LibréOffice 的计算机。

解决方案原则

为解决此类问题的程序使用 bash shell 有助于确保解决方案符合其他哲学原则。例如,bash shell 程序可以移植到其他 Linux 和 Unix 环境中。这里列出了这种特殊解决方案所满足的原则。

  • 拥抱 CLI

  • 做一个懒惰的系统管理员

  • 使用 STDIO 和数据流

  • 自动化一切

  • 总是使用 Shell 程序

  • 将数据存储在平面文本文件中

  • 使程序可移植

  • 力求优雅

  • 找到简单

  • 沉默是金

  • 测试一切

用大数据迷惑他们

一个程序的价值与其产出的重量成正比。

—计算机编程法则

多年前在一个程序员办公室的海报上看到这句话。对于那些太年轻而不记得那些“美好旧时光”的人来说,它指的是一个时代,那时几乎所有的计算机输出都是在宽大的折纸上打印报告的形式。一些程序会从 IBM 1403 打印机中倾倒大量 11 英寸 x15 英寸的扇形折叠连续格式纸 11 。你在公司层级中的地位可以由你办公室里有多少叠电脑文件以及它们有多高来决定。

尽管那些日子已经成为过去,但大量的数据仍然可能是某种迹象。在我的例子中,这是一种应对对基本上无意义的数据的连续请求的方法。

这是另一个使用命令行的有趣例子。1999 年年中的某个时候,我在北卡罗莱纳州工作,一个 PHB 要求我为安全人员创建一个名单。他们想知道我的“非标准”个人电脑上的每一个软件,以及它的功能。当时我使用的是 Red Hat Linux 6,而不是“标准”的 Windows。

我的困境是弄清楚他们到底想要什么。他们只是想要一个列表吗,比如 Red Hat Linux 6.1,OpenOffice,Mozilla?还是他们想要更多。无论我如何要求澄清,他们只是说他们想要一份非标准的“所有东西”的清单。我了解安全部门的人,我觉得越多越好。

他们说他们想要一份我的 Linux 电脑上所有软件的清单,以及它们的功能,所以我满足了他们。我编写了一个 bash 程序,该程序确定安装在相关计算机上的每个 RPM 包,按字母顺序对它们进行排序,然后使用 RPM 数据库获得软件的基本描述。实验 7-3 展示了我为完成这个任务而编写的小程序。在你自己的电脑上运行,看看结果。

确保使用所示的反勾号(rpm -qa | sort),否则该实验将无法进行。将代码括在反勾号()中是一种在评估语句中其余代码之前执行该段代码的方法。因此,首先评估包含的代码,然后将其用作for`命令的输入列表。这就像数学问题中的圆括号一样,比如 X=a3+2(6-3) 。括号改变了表达式的求值顺序。

实验 7-3

以 root 用户身份执行此实验。

[root@testvm1 ~]# for I in `rpm -qa | sort`;do echo $I; rpm -qi $I | grep Summary;done

这个简单的命令行程序为安装在您计算机上的每个 RPM 包生成两行数据。在我的 testvm1 虚拟机上安装了一个相当普通的程序,结果是 4,630 行代码。

再一次,我可以在程序末尾使用mailx命令,通过电子邮件将数据直接发送给发出请求的 PHB。

注意

我这样做已经超过 15 年了,我没有那个 bash 程序的副本。在写这一章的时候,我花了大约 5 分钟来重新创建它。

最终结果是几十页的数据,这正是他们所要求的。我知道大部分对他们来说是没有意义的,但这无关紧要,因为我给了他们他们想要的。它只是比他们预期的要多得多,而且大部分都是晦涩难懂的描述——除非你非常熟悉 Linux 的精髓。我想他们只是希望用一页纸列出电子邮件、浏览器和办公软件等东西。

然而,尽管我曾经为 PHB 提供他们所要求的东西很有趣,这个实验确实说明了命令行可以以一些令人惊奇和强大的方式使用。让我们再次列出我们的“非标准”软件,但是增加一个命令。

实验 7-4

以 root 用户身份执行此实验。

[root@testvm1 ~]# for I in `rpm -qa | sort`;do echo $I; rpm -qi $I | grep Summary;done | text2pdf -o /tmp/non-std-software.pdf

管道中的最后一个命令text2pdf将 ASCII 文本数据流直接转换为文本文件。

CLI 电源

我希望您能从这些简单的例子中看到 SysAdmin 在使用命令行时所拥有的巨大能力。

在这一章中,你已经发现 Linux 提供了大量的方法来访问命令行和执行你作为系统管理员的工作。您可以使用虚拟控制台和许多不同的终端模拟器。您可以将它们与 screen 程序结合使用,以进一步增强命令行的灵活性。

本章中的例子本身就很有启发性,但是 CLI 的真正力量来自于我也使用 CLI“自动化一切”这一事实,这是该哲学的另一个原则。有经验的系统管理员都知道,如果某件事需要做一次,就需要再做一次,通常是多次。所以为了方便起见,我将这些简单的 bash 代码放在文本文件中,并使这些文件可执行。每当我再次被要求提供同样的信息时,我所要做的就是运行适当的 bash 脚本。

八、做一个懒惰的系统管理员

尽管我们的父母、老师、老板、善意的权威人士告诉了我们一切,而且我用谷歌搜索找到了数百条关于努力工作的引文,但按时完成工作并不等同于努力工作。一个不一定意味着另一个。

我是一个懒惰的系统管理员。我也是一个非常高效的系统管理员。这两种看似矛盾的说法并不相互排斥;相反,它们以非常积极的方式互补。效率是实现这一目标的唯一途径。

这一章是关于在正确的任务上努力工作来优化我们自己的效率。其中一部分是关于自动化的,我将在这里提到,并在第九章详细讨论。但是这一章的大部分是关于寻找无数种使用已经内置在 Linux 中的快捷方式的方法。

准备

我们需要安装 logwatch 包,为其中一个实验做准备。

准备

我们需要为本章中的一个实验安装 logwatch 包才能正常工作。

注意一定要为你的发行版使用正确的软件包管理器。我用dnf代表 Fedora。

[root@testvm1 ~]# dnf -y install logwatch

如果已经安装了 logwatch,前面的命令将打印一条消息。

真实生产率

每天整天敲键盘来完成工作要求的任务可能是任何系统管理员效率最低的。一个系统管理员在思考时效率最高——思考如何解决现有的问题,以及如何避免未来的问题;思考如何监控 Linux 计算机,以便找到预测和预示未来问题的线索;思考如何让她的工作更有效率;思考如何自动化所有那些需要每天或每年执行一次的任务。

那些不是系统管理员的人不太了解或理解系统管理员工作的这种沉思的一面——包括许多管理系统管理员的人,那些头发尖尖的老板。系统管理员都以不同的方式处理他们工作中需要思考的部分。我认识的一些系统管理员在海滩、骑自行车、参加马拉松或攀岩时发现了他们最好的想法。其他人安静地坐着或听音乐时思考效果最好。还有一些人在阅读小说、学习不相关的学科,甚至在学习更多关于 Linux 的知识时思考得最好。关键是,我们都以不同的方式激发我们的创造力,许多创造力助推器并不涉及在键盘上敲一个键。系统管理员周围的人可能完全看不到我们真正的生产力。

许多 PHB 完全不知道如何衡量系统管理员的工作效率——或者其他人的工作效率——喜欢听到敲击键盘的声音。大量的键盘噪音对他们来说就是音乐。这是衡量系统管理员工作效率的最糟糕的方法。

一些 PHB 甚至在员工的电脑上安装按键和鼠标移动监控软件,以此来衡量他们的工作效率。谷歌一下,自己看看有多少程序执行这种类型的击键计数。击键和点击鼠标越多,用户的工作效率就越高,对吗? 不对! 也许这对于会计来说是令人兴奋的事情,但这是一种可怕的方式来衡量一个系统管理员或任何其他人的生产力。

预防性保养

我想起了一个有趣的经历。这发生在我作为客户工程师(ce)在 IBM 工作的时候。我被分配去修理坏了的单位记录设备,比如打孔机、卡片分类机、整理机以及其他使用现在已经过时的穿孔卡的设备。

作为镇上的新人,我被分配了一些最古老和最不可靠的机械设备,作为我的领域的一部分。因为我所替代的那个人已经离开了一段时间,这些设备中的大部分都是刚刚好解决眼前的问题,但不足以防止下一个即将到来的问题。IBM 要求对这些设备进行预防性维护(PM)的规定已经被忽视了好几个月,机器正在磨损。

减少长期工作量的唯一方法是执行所需的预防性维护,这将减少每个设备上的呼叫频率。因此,在我修好每台坏机器后,我花了几分钟来执行当时要求的所有预防性维护。这包括清洁、润滑和更换尚未失效但很快就会失效的磨损部件。通过执行这种预防性维护,我减少了这些设备上的故障呼叫数量,节省了我自己以后的工作,并为 IBM 节省了我或我的一个同事不得不出去解决一个本来可以通过执行预防性维护来避免的问题的成本。

许多人会说我的工作是修理电脑设备。我在 IBM 的经理们明白这只是冰山一角;他们和我都知道我的工作是让客户满意。虽然这通常意味着修复损坏的硬件,但也意味着减少硬件损坏的次数。这对客户来说是好事,因为当他们的机器工作时,他们的生产效率更高。这对我来说很好,因为我接到的来自那些更开心的顾客的电话要少得多。我也睡得更多了,因为下班后的紧急呼叫减少了。我是个懒惰的人。通过预先做额外的工作,从长远来看,我需要做的工作要少得多。

同样的原则已经成为系统管理员的 Linux 哲学的功能原则之一。作为系统管理员,我们的时间最好花在那些能最小化未来工作负载的任务上。

现在让我们来看看一些偷懒的方法。请记住,这些策略只是众多策略中的一部分,可以用来减少你的工作量,提高工作效率,尽可能地用最少的努力完成所有的工作。我认识的每个系统管理员都有自己的策略。这些只是我的一部分。

最小化键入

作为一个懒惰的系统管理员,一部分是采用减少打字的策略。打字需要时间,节省时间很重要。

我是一个糟糕的打字员。我上高中的时候,男孩子还不学打字。那是给那些即将成为秘书的女性的。当我终于开始通过真正的键盘而不是穿孔卡片来使用计算机时,我设法自学了足够的知识,用每只手的几个手指以相当快的速度打字。它对我来说是有效的,但是我必须做很多修改。在 CLI 程序中输入命令时出错是一件坏事。所以减少打字量是很重要的。

别名

减少打字量的一种方法是使用别名。别名是一种将长命令替换为短命令的方法,因为短命令的字符较少,所以更容易键入。别名是减少输入的一种常用方法,它通过将我们经常使用的长选项包含在别名中,使我们不必输入长选项。

实验 8-1

作为学生用户,输入alias命令查看当前的别名列表。

[student@testvm1 ~]$ alias
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias glances='glances -t1'
alias grep='grep --color=auto'
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'
alias ls='ls --color=auto'
alias lsn='ls --color=no'
alias mc='. /usr/libexec/mc/mc-wrapper.sh'
alias vi="vim"
alias vim='vim -c "colorscheme desert" '
alias which='(alias; declare -f) | /usr/bin/which --tty-only --read-alias --read-functions --show-tilde --show-dot'
alias xzegrep='xzegrep --color=auto'
alias xzfgrep='xzfgrep --color=auto'
alias xzgrep='xzgrep --color=auto'
alias zegrep='zegrep --color=auto'
alias zfgrep='zfgrep --color=auto'
alias zgrep='zgrep --color=auto'

你的结果应该看起来和我的相似,但是我添加了一些我自己的别名。一个是 glances 实用程序,它不是大多数发行版的一部分。另一种是 vim 使用“沙漠”配色方案。

实验 8-1 中显示的别名主要用于设置默认行为,如颜色和一些标准选项。我特别喜欢ll别名,因为我喜欢目录内容的长列表,我可以只输入ll而不是输入ls -l。我经常使用ll命令,每次使用它都可以节省输入三个字符的时间。对于像我这样的慢打字员来说,那可能意味着很多时间。

我强烈建议您不要像某些人所做的那样,使用别名将 Linux 命令命名为您在另一个操作系统中使用的命令。这样你永远也学不会 Linux。

在实验 8-1 中,vim 编辑器的别名设置了一个颜色方案,这不是默认的。我碰巧更喜欢 desert 配色方案,所以将 vim 命令别名化为更长的命令,该命令也指定了我最喜欢的配色方案,这是用更少的输入就能得到我想要的结果的一种方法。

您可以使用 alias 命令将自己的新别名添加到~/中。bashrc 文件,使它们在重新启动和注销/进入之间永久存在。要使主机上的所有用户都可以使用别名,请将它们添加到/etc/bashrc 文件中。两种情况下的语法都与命令行中的语法相同。

其他键入快捷键

其他减少打字的方法包括使用程序的简称。大多数核心实用程序的名称都很短——许多只有两三个字符长。这本身就减少了我们必须做的打字量。我对自己创建的 Bash shell 程序使用了简短的名称,以使它们简单、易于记忆和输入。

文件命名

我用自己的惯例命名文件。一般来说,短名字是好的,但是在列表中容易看到的有意义的名字更好。

例如,对于名称相似但创建日期不同的文件,我的命名策略是 YYYYMMDD-filename.pdf 格式。我从网上下载了许多财务文件,它们的名字像 statement.pdf,当下载到一个目录中时,我用我自己的格式重新命名它们,这样它们在目录中更容易辨认,例如20170617-visa-statement.pdf.将日期放在 YYYYMMDD 或 YYYY-MM-DD 格式的第一位,使它们在目录列表中自动按正确的日期顺序排序,这样就很容易找到一个特定的文件。

这种类型的命名确实需要一些额外的输入,但是它可以节省以后查找特定文件的大量时间。

BASH 效率

Bash 只是 Linux 可用的众多 shells 之一。像所有的 shells 一样,Bash 有许多方法可以帮助您变得更高效。我们已经看到了可以在。bashrc 文件。

现在让我们看看 Bash shell 提供的更多有趣的命令行特性。

完井设备

Bash 提供了一种工具来完成部分类型化的程序和主机名、文件名和目录名。键入部分命令或文件名作为命令的参数,并按下 Tab 键。如果主机、文件、目录或程序存在,并且名称的其余部分是惟一的,Bash 将完成名称的输入。因为 Tab 键用于启动补全,所以该功能有时被称为“Tab 补全”

制表符补全是可编程的,并且可以进行配置以满足许多不同的需求。但是,除非您有 Linux、核心实用程序和其他 CLI 应用提供的标准配置无法满足的特定需求,否则永远没有理由更改默认值。

注意

Bash 手册页对“可编程完成”有一个详细的、几乎难以理解的解释《开始 Linux 命令行》一书有一个更简短、更易读的描述 2 ,维基百科 3 有更多的信息、示例和动画 GIF 来帮助理解这个特性。

如果你还不熟悉命令补全,实验 8-2 提供了一个非常简短的介绍。

实验 8-2

以学生用户的身份执行此实验。对于这个实验,您的主目录应该有一个名为 Documents 的子目录。大多数 Linux 发行版都为每个用户创建一个 Documents 子目录。

我们使用补全来转换到~/Documents 目录。确保您的主目录是 PWD。在终端中键入以下部分命令。

[student@testvm1 ~]$ cd D<Tab>

<Tab>表示按一次 Tab 键。什么也没有发生,因为有三个目录以“d”开头。您可以通过快速连续按 Tab 键两次来查看,这将列出与您已经键入的内容相匹配的所有目录。

[student@testvm1 ~]$ cd D<tab><Tab>
Desktop/   Documents/ Downloads/
[student@testvm1 ~]$ cd D

现在将“o”添加到命令中,然后再按两次 Tab 键。

[student@testvm1 ~]$ cd Do<tab><Tab>
Documents/ Downloads/
[student@testvm1 ~]$ cd Do

您应该会看到以“Do”开头的两个目录的列表现在将“c”添加到命令中,并按一次 Tab 键。

[student@testvm1 ~]$ cd Doc<Tab>
[student@testvm1 ~]$ cd Documents/

因此,如果您键入cd Doc<Tab>,目录名的其余部分将在命令中完成。

让我们快速看一下命令的完成情况。在这种情况下,命令相对较短,但大多数都是如此。假设我们想要确定主机当前的正常运行时间。

[student@testvm1 ~]$ up<Tab><Tab>
update-alternatives   updatedb                update-mime-database     upower
update-ca-trust       update-desktop-database update-pciids            uptime
update-crypto-policies   update-gtk-immodules    update-smart-drivedb     
[student@testvm1 ~]$ up

我们可以看到几个以“up”开头的命令,我们还可以看到再键入一个字母“t”将完成足够的 uptime 命令,剩下的将是唯一的。

[student@testvm1 ~]$ upt<Tab>ime
 07:55:05 up 1 day, 10:01,  7 users,  load average: 0.00, 0.00, 0.00

当需要的剩余文本字符串是明确唯一的时,补全工具只补全命令、目录或文件名。

制表符补全适用于命令、某些子命令、文件名和目录名。我发现补全对于补全目录和文件名最有用,它们往往更长,还有一些更长的命令和一些子命令。

大多数 Linux 命令都很短,使用补全功能实际上不如键入命令有效。简短的 Linux 命令名非常适合懒惰的系统管理员。所以这只是取决于你是否发现在短命令上使用补全更有效或者更一致。一旦您了解了哪些命令对于制表符补全是有价值的,以及您需要键入多少,您就可以使用那些您认为有帮助的命令。

命令行调用和编辑

命令行调用和编辑是减少我们打字总量的其他方法。命令行调用和命令行编辑这两个功能共同提高了工作效率。我经常使用这些特性,无法想象使用一个没有这些特性的 shell。如果没有 Bash history 特性,这些特性是不可能实现的,所以我们将从这里开始。

历史

命令行回调使用 Bash 历史特性来维护以前输入的 shell 命令的列表。此功能允许我们使用命令历史来调用以前的命令,以便重用。在按下回车键之前,可以编辑调用的命令。让我们从查看我们的主机的历史开始,这样我们可以看到它是什么样子的。

实验 8-3

以学生用户的身份执行此实验。输入history命令并查看结果。

[student@testvm1 ~]$ history
    1  poweroff
    2  w
    3  who
    4  cd /proc
    5  ls -l
    6  ls
    7  cd 1 ; ls
    8  cd
    9  ls
   10  exit
   11  ls -la
   12  exit
   13  man screen
   14  ls -la
   15  badcommand
   16  clear
   17  ls -l /usr/local/bin
   18  clear
   19  screenfetch
   20  zsh
   21  ksh
   22  bash
   23  man chgsh
   24  man chsh
   25  screen
   26  history
[student@testvm1 ~]$

您的结果将与我的不同,但您至少应该看到一些您在之前的实验中输入的命令。

Bash 命令历史记录保存在~/中。bash_history 文件。其他 shells 将它们的历史保存在不同的文件中。Korn shell 将其历史保存在。比如 sh_history。至少对于 Bash 来说,缓冲区中的历史不会写入。bash_history 文件,直到退出 shell。

每个打开的终端都有自己的历史记录,因此您可能在列表中看不到所需的命令。如果没有,请尝试另一个终端会话。屏幕程序还在内存中为在其下打开的每个终端维护自己的历史缓冲区。shell 历史记录维护了指定的行数,Fedora 缺省值为 1,000。

利用历史

现在让我们来看看如何利用这段历史。有两种方法可以访问历史记录的内容,以便重用它们。我们可以使用行号,也可以使用回滚。实验 8-4 探索了这两种方法。

实验 8-4

首先清除现有的历史,然后运行几个命令向历史文件添加一些新数据,并再次查看它。通过清除历史文件,您应该得到与我在这个实验中所做的相同的条目和结果。

[student@testvm1 ~]$ history -c
[student@testvm1 ~]$ history
    1  history
[student@testvm1 ~]$ echo "This is a way to create a new file using the echo command and 
redirection

. It can also be used to append text to a file" 
>>

newfile1.txt

请注意,我故意让这个命令有点长。现在看看结果。只需键入文件名的第一部分,然后按 Tab 键完成。

[student@testvm1 ~]$ cat new<Tab>file1.txt
This is a way to create a new file using the echo

command and redirection

. It can also be used to append text to a file

现在按一下向上箭头() 键。您应该会看到刚刚输入的命令。再次按下向上箭头键,查看之前的命令。您现在应该看到了echo命令。按下 Enter 键重复该命令,然后使用向上箭头键返回到cat命令查看结果。

↑
[student@testvm1 ~]$ cat newfile1.txt Do not press Enter here!
↑
[student@testvm1 ~]$ echo "This is a way to create a new file using the echo command and 
redirection

. It can also be used to append text to a file" 
>>

newfile1.txt                Do press Enter here!
↑↑
[student@testvm1 ~]$ cat newfile1.txt
This is a way to create a new file using the echo command and redirection. It can also be used to append text to a file
This is a way to create a new file using the echo command and redirection. It can also be used to append text to a file
[student@testvm1 ~]$

现在文件中有两行文本。现在看看历史。

[student@testvm1 ~]$ history
    1  history
    2  echo "This is a way to create a new file using the echo command and redirection. It can also be used to append text to a file" >> newfile1.txt
    3  cat newfile1.txt
    4  echo "This is a way to create a new file using the echo command and redirection. It can also be used to append text to a file" >> newfile1.txt
    5  cat newfile1.txt
    6  history
[student@testvm1 ~]$

在这一点上你的历史应该和我的一样。如果不是,您可以按以下顺序调整命令编号。

除了使用箭头键滚动 Bash 历史记录之外,我们还可以简单地使用想要重用的条目的编号。让我们使用历史文件第 4 行上的命令向现有文件添加另一行。

[student@testvm1 ~]$ !4
echo "This is a way to create a new file using the echo

command and redirection. It can also be used to append text to a file" >> newfile1.txt
[student@testvm1 ~]$

请注意,行号前面有一个惊叹号,它从历史记录中的第 4 行重新运行命令。按下 Enter 键后,Bash 还会显示正在执行的命令。但是,当你按下回车键后,就没有办法收回了。

注意确保在历史缓冲区变满后使用正确的行号。默认值为 1,000 行,在达到该条目数之前,行号保持不变。此后,每次运行新命令时,历史命令的行号都会改变。

现在我们将做一点非常简单的命令行编辑。使用向上箭头键,滚动回以下命令,但不要按 Enter 键。

[student@testvm1 ~]$ echo "This is a way to create a new file using the echo command and redirection. It can also be used to append text to a file" >> newfile1.txt

按左箭头键(←)直到光标位于文件名中的句点上。然后按退格键清除“1”键入“2”创建新文件名“newfile2.txt ”,并按下 Enter 键。

列出以“new”开头的文件,以查看上一个命令的结果。

[student@testvm1 ~]$ ls -l new*
-rw-rw-r-- 1 student student 360 Dec 21 13:18 newfile1.txt
-rw-rw-r-- 1 student student 120 Dec 21 17:18 newfile2.txt

对于系统管理员来说,命令行历史、回忆和是非常有用和节省时间的工具。我喜欢 Bash shell 的一个原因是,它拥有我尝试过的所有 shell 中最有用的历史和回忆特性。Bash 是大多数 Linux 发行版的默认 shell,所以它可能也是您的安装的 shell。

默认情况下,Bash shell 可以访问 GNU emacs 模式来编辑命令行。标准 emacs 命令可用于移动和编辑命令内容。我更喜欢 vi 模式,因为我更熟悉那些编辑按键。

要在 Bash 命令行上设置 vi 编辑模式,请将下面一行添加到/etc/bashrc 配置文件中。

set -o vi

通过将它放在那里,它变成了系统范围的,包括根用户和所有其他用户。当前打开的 shell 不受影响,但是进行此更改后打开的所有 shell 都将设置 vi 模式进行编辑。您还可以在命令行中输入该命令,以便在 Bash shell 的特定实例中设置 vi 模式。

要在命令行上进入 vi 命令模式,按下 Esc 键,就像在 vi 中一样。然后,您可以使用标准的 vi 命令来移动和编辑命令。

实验 8-5

以学生用户的身份执行此实验。首先,如果终端会话尚未打开,您应该打开它。然后查看$SHELLOPTS 环境变量,验证当前是否设置了 emacs 选项。然后设置 vi 编辑模式,并验证是否已设置。

[student@testvm1 ~]$ echo $SHELLOPTS
braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor
[student@testvm1 ~]$ set -o vi
[student@testvm1 ~]$ echo $SHELLOPTS
braceexpand:hashall:histexpand:history:interactive-comments:monitor:vi
[student@testvm1 ~]$

SHELLOPTS 环境变量包含当前对该 shell 实例有效的所有选项。现在让我们在 vi 模式下做一些事情。

  1. 滚动回到我们在实验 8-4 中使用的长echo命令。

  2. 按下 Esc 键一次,进入 vi 命令模式。

  3. 键入 23b 返回 23 个单词。

  4. 键入 d18w 删除 18 个单词。

  5. 按一次左箭头键,将光标放在单词“file”末尾的空白处

  6. r 进入单字替换模式。

  7. 按句点键替换空格。

  8. 按下 ^ (用 shift 键)移动到行首。这里什么也不做,只是让你看到光标移动到行首。

  9. 按下 $ 将光标移动到行尾。

  10. 这是我偶然发现的东西。按 Esc 然后按 :w <回车> 保存历史中的线。该行在未执行的情况下被保存,命令提示符现在为空。

  11. 现在,滚动回最后一个命令,它应该类似于下面的行。不要按回车。

```sh
[student@testvm1 ~]$ echo "This is a way to create a new file." >> newfile2.txt

```
  1. 使用左箭头键将光标移回“2”

  2. 按下 r 进入替换模式,然后按下 3 将“2”替换为“3”您的命令行现在应该是这样的。

```sh
[student@testvm1 ~]$ echo "This is a way to create a new file." >> newfile3.txt

```
  1. 现在按下键进入

验证新文件是否存在及其内容。

如果你已经熟悉了 vi,实验 8-5 中的编辑命令应该已经很熟悉了。在线 Bash 参考手册 4 有一章是关于 Bash 命令行编辑以及如何设置和使用 emacs 和 vi 编辑模式的。

如果您不是 vi 用户,那么您刚刚上了第一课。但是因为 emacs 编辑是默认的,您只需按下 Esc 键就可以使用这种命令行编辑模式。

我不会假装对 emacs 编辑有足够的了解,能够为您创建一个涵盖 emacs 模式下命令行编辑的实验。我在网上找到了一个很好的信息来源,Peter Krumins 的博客,上面有更多关于 Bash 历史、 5 Bash emacs 编辑、 6 和 Bash vi 编辑的信息和可下载的备忘单。 7

许多专门的工具也为它们的命令行界面提供制表符补全。这些工具的名称和它们识别的条目保存在/etc/bash_completion.d 目录中。

原木是你的朋友

使用日志文件来帮助确定问题和性能问题的根源。它们包含大量数据,可用于跟踪许多类型的问题。我在排除故障时最常犯的错误是没有尽快查看日志文件。

几乎所有的日志文件都位于/var/log 中,可以直接或通过简单的命令进行访问。每种类型的日志文件的最新名称中没有日期,而较旧的日志文件名称中有日期来区分它们。通常,默认情况下,日志文件会保留一个月,每个日志文件最多包含一周的数据。如果文件中的数据量超过了预配置的阈值,则文件可能会在达到该阈值时被循环,而不是等待整整七天的时间。

logrotate 工具管理日志循环和删除。

特别行政区

我长期以来最喜欢的是系统活动报告,或 SAR。SAR 是开始寻找关于 Linux 计算机性能信息的一个很好的地方。

SAR 有一个后台运行的守护进程收集数据。每隔 10 分钟,收集的数据存储在/var/log/sa 目录中。这些日志是二进制格式的,不能直接读取。sar命令用于查看这些记录。

SAR 的优势之一是它可以报告长达 30 天的历史数据。这使我们能够及时回到过去,看看我们是否能够找到一个或多个资源上的负载非常高的模式或特定时期。大多数发行版可用的其他性能监控工具都不提供这种类型的历史数据。像 top、iostat、vmstat 等命令都只提供它们监视的数据的即时读数。

注意

某些发行版上没有安装或启用 SAR。Fedora 的最新版本确实安装并启用了 SAR,但是旧版本甚至不安装它。

实验 8-6 的准备

如果尚未安装 SAR,请以 root 用户身份执行此准备部分以安装 SAR。我们需要安装的包是 sysstat。对基于 RPM 的发行版使用 dnf 或 yum,或者对您的特定发行版使用软件包管理器。

[root@testvm1 ~]# dnf -y install sysstat

如果您必须安装 sysstat 包,您可能还需要启用并启动它。

[root@testvm1 log]# systemctl enable sysstat
Created symlink /etc/systemd/system/multi-user.target.wants/sysstat.service → /usr/lib/systemd/system/sysstat.service.
Created symlink /etc/systemd/system/sysstat.service.wants/sysstat-collect.timer → /usr/lib/systemd/system/sysstat-collect.timer.
Created symlink /etc/systemd/system/sysstat.service.wants/sysstat-summary.timer → /usr/lib/systemd/system/sysstat-summary.timer.
[root@testvm1 log]# systemctl start sysstat

SAR 现在已经安装,系统数据收集过程已经开始。

在下一个 10 分钟的时间增量之前,不会聚合任何数据,例如在整点、10 分钟后、20 分钟后等等。如果您必须安装 sysstat 包,我建议您等待一个小时左右,以便积累一些数据。您可以检查/var/log/sa 的内容,以验证正在收集数据。您还可以检查消息文件,寻找与 sysstat 相关的条目。

现在您已经安装了 sysstat 包,并且已经等待收集数据,让我们继续进行实验。

实验 8-6

最简单的形式是,sar命令显示自午夜以来以 10 分钟为增量的 CPU 统计信息。该任务可以作为学生用户执行。

[student@testvm1 ~]# sar | head -25
Linux 4.14.5-300.fc27.x86_64 (testvm1)  12/23/2017  _x86_64_        (1 CPU)

12:00:02 AM   CPU     %user     %nice   %system   %iowait    %steal     %idle
12:10:21 AM   all      1.09      0.02      0.70      1.72      0.00     96.48
12:20:21 AM   all      1.07      0.00      0.51      0.03      0.00     98.39
12:30:21 AM   all      1.03      0.00      0.51      0.02      0.00     98.44
12:40:21 AM   all      1.12      0.00      0.54      0.02      0.00     98.32
12:50:21 AM   all      0.99      0.00      0.52      0.01      0.00     98.48
01:00:21 AM   all      1.00      0.00      0.48      0.02      0.00     98.49
01:10:21 AM   all      0.90      0.00      0.51      0.11      0.00     98.48
01:20:21 AM   all      0.92      0.01      0.54      0.19      0.00     98.33
01:30:21 AM   all      0.98      0.00      0.54      0.09      0.00     98.39
01:40:21 AM   all      1.00      0.00      0.50      0.23      0.00     98.26
01:50:21 AM   all      0.92      0.00      0.46      0.02      0.00     98.60
02:00:21 AM   all      0.90      0.00      0.47      0.05      0.00     98.58
02:10:21 AM   all      0.97      0.00      0.44      0.23      0.00     98.36
02:20:21 AM   all      0.92      0.04      0.51      0.05      0.00     98.48
02:30:21 AM   all      0.91      0.00      0.49      0.11      0.00     98.49
02:40:21 AM   all      0.88      0.00      0.46      0.11      0.00     98.56
02:50:21 AM   all      0.98      0.00      0.48      0.02      0.00     98.53
03:00:21 AM   all      0.93      0.00      0.47      0.02      0.00     98.58
03:10:21 AM   all      0.94      0.00      0.47      0.08      0.00     98.51
03:20:21 AM   all      0.91      0.02      0.45      0.07      0.00     98.55
03:30:21 AM   all      1.39      2.19      7.21      5.89      0.00     83.32
03:40:21 AM   all      0.94      0.06      0.71      0.07      0.00     98.22

在这个实验中,我使用了head实用程序来截断 25 行之后的输出。输出中的每一行都显示了在每 10 分钟内收集的所有数据的平均值。因此,在 03:10:21 结束的时间段内,CPU 的空闲时间为 98.51%。

现在使用-A 选项运行sar命令,显示 SAR 收集的所有数据类型。通过 less 实用程序运行它,这样您就可以逐页浏览数据,这对于我来说太长了,无法在此重复。

[student@testvm1 ~]$ sar -A | less

默认情况下,sar 命令显示今天到当前时间收集的数据。过去一个月内的数据可以位于/var/log/sa 目录下的文件中。这些文件被命名为 saXX,其中 XX 是一个月中的某一天。要查看前一天的数据,请使用以下命令。请确保使用您自己的 sa 目录中存在的文件名。

[root@testvm1 sa]# sar -A -f sa07 | less

前面的命令显示该月第 7 天的所有数据,并通过管道将其传递给less命令。

SAR 产生的大量数据可能难以解释,但我发现它在定位各种类型的问题时非常有用。

许多发行版仍然将 sysstat 脚本放在/etc/cron.d 中,以指定的 10 分钟间隔运行数据聚合程序 sa1。在 Fedora 的当前版本中,数据聚合由 systemd 管理,几个控制文件位于/usr/lib/systemd/system 目录中。

我建议你定期花些时间浏览 SAR 结果。这将为您提供一些当您的系统正确运行时应该是什么样子的知识。这将使问题发生时更容易发现。

SAR 手册页提供了大量关于收集的数据以及如何显示特定类型的数据(如磁盘、CPU、网络等)的信息。尽管如此,搜救报告中的许多标题一开始可能很难理解。很多谷歌搜索都没有找到关于 SAR 报告列标题的解码键,但我确实找到了一个网站,那里有最好的描述。 8 在我自己的 Linux 参考资料集中,我找到的最好的一本书是Unix和 Linux 系统管理手册。大多数其他介绍 SAR 的书籍都坚持使用 CPU 统计数据,但是 SAR 提供的数据远不止这些,这本书至少介绍了其中的一部分。

*### 邮件日志

我运行自己的个人邮件服务器,并经常使用日志来解决问题。就电子邮件而言,问题往往与邮件无法送达或阻止垃圾邮件和其他不受欢迎的电子邮件有关。

我在/var/log/maillog 文件中找到了日志条目,它们告诉我电子邮件是否被传递,有时还提供了足够的信息来告诉我为什么它没有被传递。如果您运行邮件服务器,您应该非常熟悉邮件日志文件。

信息

/var/log/messages 日志文件包含各种类型的内核和其他系统级消息。这是我经常用来帮助我确定问题的另一个文件。来自内核、systemd 和许多正在运行的服务的条目都记录在这里。每个日志条目都以日期和时间开始,以便于确定事件的顺序和定位日志文件中特定时间的条目。

因为它非常重要,所以让我们快速看一下消息文件。

实验 8-7

以 root 用户身份执行此实验。制作/var/log PWD。使用less命令查看消息日志文件。

[root@testvm1 log]# less messages

因为显示了大量数据,所以我没有包括来自我的测试虚拟机的任何输出。浏览消息文件的内容,了解您通常会遇到的消息类型。使用 Ctrl-C 来终止less

消息日志文件充满了有趣和有用的信息。

  • 合成孔径雷达数据收集

  • DHCP 客户端对网络配置的请求

  • 产生的 DHCP 配置信息

  • systemd 在启动和关闭期间记录的数据

  • 插入 USB 存储设备时的内核数据

  • USB 集线器信息

  • 还有更多

在处理非性能问题时,消息文件通常是我首先查看的地方。它对于性能问题也很有用,但是我从 SAR 开始。

dmesg-一般信息

dmesg不是文件,是命令。曾经有一个名为 dmesg 的日志文件,它包含了内核在引导期间生成的所有消息以及启动期间生成的大多数消息。启动过程在引导过程结束时开始,此时 init 或 systemd 控制了主机。

dmesg命令显示内核生成的所有消息,包括在引导过程中发现的大量硬件数据。在查找启动问题和硬件问题时,我总是从这个命令开始。

注意

在来自dmesg的输出中发现的许多硬件数据可以在/proc 文件系统中找到。

让我们看一下dmesg命令的输出。

实验 8-8

这个实验可以作为根用户或学生用户来执行。

[root@testvm1 log]# dmesg | less
[    0.000000] Linux version 4.14.5-300.fc27.x86_64 (mockbuild@bkernel01.phx2.fedoraproject.org) (gcc version 7.2.1 20170915 (Red Hat 7.2.1-2) (GCC)) #1 SMP Mon Dec 11 16:00:36 UTC 2017
[    0.000000] Command line: BOOT_IMAGE=/vmlinuz-4.14.5-300.fc27.x86_64 root=/dev/mapper/fedora_testvm1-root ro rd.lvm.lv=fedora_testvm1/root rd.lvm.lv=fedora_testvm1/swap
[    0.000000] x86/fpu: Supporting XSAVE feature 0x001: 'x87 floating point registers'
[    0.000000] x86/fpu: Supporting XSAVE feature 0x002: 'SSE registers'
[    0.000000] x86/fpu: Supporting XSAVE feature 0x004: 'AVX registers'
[    0.000000] x86/fpu: xstate_offset[2]:  576, xstate_sizes[2]:  256
[    0.000000] x86/fpu: Enabled xstate features 0x7, context size is 832 bytes, using 'standard' format.
[    0.000000] e820: BIOS-provided physical RAM map:
[    0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable
[    0.000000] BIOS-e820: [mem 0x000000000009fc00-0x000000000009ffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000000f0000-0x00000000000fffff] reserved
[    0.000000] BIOS-e820: [mem 0x0000000000100000-0x00000000dffeffff] usable
[    0.000000] BIOS-e820: [mem 0x00000000dfff0000-0x00000000dfffffff] ACPI data
[    0.000000] BIOS-e820: [mem 0x00000000fec00000-0x00000000fec00fff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fee00000-0x00000000fee00fff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fffc0000-0x00000000ffffffff] reserved
[    0.000000] BIOS-e820: [mem 0x0000000100000000-0x000000011fffffff] usable

示例数据中的大多数行都是换行的,这使得阅读起来有点困难。每一行数据都以精确到微秒的时间戳开始。时间戳表示内核启动后的时间。

滚动浏览数据,熟悉这里可以找到的许多不同类型的数据。

dmesg命令显示的数据位于 RAM 中,而不是硬盘上。无论主机中有多少 RAM 内存,分配给 dmesg 缓冲区的空间都是有限的。当它填满时,随着新数据的添加,最旧的数据将被丢弃。

安全的

/var /log/secure 日志文件包含与安全相关的条目。这包括成功和不成功登录系统的信息。让我们看看您可能会在该文件中看到的一些条目。

实验 8-9

这个实验必须以 root 用户身份进行。使用less命令查看安全日志文件的内容。

[root@testvm1 log]# less secure
Dec 24 13:44:25 testvm1 sshd[1001]: pam_systemd(sshd:session): Failed to release session: Interrupted system call
Dec 24 13:44:25 testvm1 sshd[1001]: pam_unix(sshd:session): session closed for user student
Dec 24 13:44:25 testvm1 systemd[929]: pam_unix(systemd-user:session): session closed for user sddm
Dec 24 13:44:25 testvm1 sshd[937]: pam_systemd(sshd:session): Failed to release session: Interrupted system call
Dec 24 13:44:25 testvm1 sshd[937]: pam_unix(sshd:session): session closed for user root
Dec 24 13:44:25 testvm1 sshd[770]: Received signal 15; terminating.
Dec 24 13:44:25 testvm1 systemd[940]: pam_unix(systemd-user:session): session closed for user root
Dec 24 13:44:25 testvm1 systemd[1004]: pam_unix(systemd-user:session): session closed for user student
Dec 24 13:45:03 testvm1 polkitd[756]: Loading rules from directory /etc/polkit-1/rules.d
Dec 24 13:45:03 testvm1 polkitd[756]: Loading rules from directory /usr/share/polkit-1/rules.d
Dec 24 13:45:04 testvm1 polkitd[756]: Finished loading, compiling and executing 9 rules
Dec 24 13:45:04 testvm1 polkitd[756]: Acquired the name org.freedesktop.PolicyKit1 on the system bus
Dec 24 13:45:04 testvm1 sshd[785]: Server listening on 0.0.0.0 port 22.
Dec 24 13:45:04 testvm1 sshd[785]: Server listening on :: port 22.
Dec 24 13:45:09 testvm1 sddm-helper[938]: PAM unable to dlopen(/usr/lib64/security/pam_elogind.so): /usr/lib64/security/pam_elogind.so: cannot open shared object file: No such file or directory
Dec 24 13:45:09 testvm1 sddm-helper[938]: PAM adding faulty module: /usr/lib64/security/pam_elogind.so
Dec 24 13:45:09 testvm1 sddm-helper[938]: pam_unix(sddm-greeter:session): session opened for user sddm by (uid=0)
Dec 24 13:45:09 testvm1 systemd[939]: pam_unix(systemd-user:session): session opened for user sddm by (uid=0)
Dec 24 13:46:18 testvm1 sshd[961]: Accepted publickey for root from 192.168.0.1 port 46764 ssh2: RSA SHA256:4UDdGg3FP5sITB8ydfCb5JDg2QCIrsW4cfoNgFxhC5A
Dec 24 13:46:18 testvm1 systemd[963]: pam_unix(systemd-user:session): session opened for user root by (uid=0)
Dec 24 13:46:18 testvm1 sshd[961]: pam_unix(sshd:session): session opened for user root by (uid=0)
Dec 24 15:37:02 testvm1 sshd[1155]: Accepted password for student from 192.168.0.1 port 56530 ssh2
Dec 24 15:37:02 testvm1 systemd[1157]: pam_unix(systemd-user:session): session opened for user student by (uid=0)
Dec 24 15:37:03 testvm1 sshd[1155]: pam_unix(sshd:session): session opened for user student by (uid=0)
########################## <snip> ###########################
Dec 26 13:02:39 testvm1 sshd[31135]: Invalid user hacker from 192.168.0.1 port 46046
Dec 26 13:04:21 testvm1 sshd[31135]: pam_unix(sshd:auth): check pass; user unknown
Dec 26 13:04:21 testvm1 sshd[31135]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.0.1
Dec 26 13:04:24 testvm1 sshd[31135]: Failed password for invalid user hacker from 192.168.0.1 port 46046 ssh2
Dec 26 13:04:27 testvm1 sshd[31135]: pam_unix(sshd:auth): check pass; user unknown
Dec 26 13:04:29 testvm1 sshd[31135]: Failed password for invalid user hacker from 192.168.0.1 port 46046 ssh2
Dec 26 13:04:30 testvm1 sshd[31135]: pam_unix(sshd:auth): check pass; user unknown
Dec 26 13:04:32 testvm1 sshd[31135]: Failed password for invalid user hacker from 192.168.0.1 port 46046 ssh2
Dec 26 13:04:32 testvm1 sshd[31135]: Connection closed by invalid user hacker 192.168.0.1 port 46046 [preauth]
Dec 26 13:04:32 testvm1 sshd[31135]: PAM 2 more authentication failures; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.0.1

/var/log/secure 中的大多数数据都与用户登录和注销的记录以及关于是否使用密码或公钥进行身份验证的信息有关。

该日志还包含失败的密码尝试,如我在该文件中截取的一些数据行下面的数据所示。

我使用安全日志文件的主要目的是识别黑客的入侵企图。但是我甚至不这样做——我也使用自动化工具。在这种情况下,日志监视工具。

以下日志文件

即使使用像grep这样的工具来帮助隔离所需的行,搜索日志文件也可能是一项耗时且麻烦的任务。在进行故障排除时,连续查看文本格式日志文件的内容,尤其是查看到达的最新条目,往往会有所帮助。使用catgrep查看日志文件,显示输入命令时的内容。

我喜欢使用tail命令来查看文件的结尾,但是重新运行 tail 命令来查看新行会很耗时,而且会破坏我的问题确定过程。使用tail -f启用 tail 命令“跟随”文件,并在添加新数据时立即显示新的数据行。

实验 8-10

您应该使用很少或没有活动的非生产主机。这对于执行大多数实验来说是完美的,但是这个实验需要一些活动,以便您可以在添加新的日志条目时观察它们。

以 root 用户身份执行此实验。我们需要两个 root 登录的终端会话。这些终端会话应该在不同的窗口中,并进行排列,以便您可以同时看到它们。在一个根终端会话中,创建/var/log PWD,然后遵循消息文件。

[root@testvm1 ~]# cd /var/log
[root@testvm1 log]# tail -f messages
Dec 24 09:30:21 testvm1 audit[1]: SERVICE_STOP pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=sysstat-collect comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
<snip>
Dec 24 09:37:58 testvm1 systemd[1]: Starting dnf makecache...
Dec 24 09:37:59 testvm1 dnf[29405]: Metadata cache refreshed recently.
Dec 24 09:37:59 testvm1 systemd[1]: Started dnf makecache.
Dec 24 09:40:21 testvm1 audit[1]: SERVICE_STOP pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=sysstat-collect comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'

Tail 显示日志文件的最后 10 行,然后等待追加更多的数据。为了简洁起见,我删除了其中的一些行。

让我们显示一些日志条目。有几种方法可以做到这一点,但最简单的是使用logger命令。在第二个窗口中,以 root 用户身份输入以下命令,在消息文件中记录一个新条目。

[root@testvm1 ~]# logger "This is test message 1."

下面一行应该出现在另一个终端的消息日志文件的末尾。

Dec 24 13:51:46 testvm1 root[1048]: This is test message 1.

我们也可以为此使用 STDIO。

[root@testvm1 ~]# echo "This is test message 2." | logger

结果是相同的——消息出现在消息日志文件中。

Dec 24 13:56:41 testvm1 root[1057]: This is test message 2.

使用 Ctrl-C 终止跟踪日志文件。

系统日志

SystemV 启动脚本相对较新的替代品 systemd 有自己的一组日志,其中许多日志取代了/var/log 目录中的传统 ASCII 文本文件。journald 守护进程为 systemd 管理的服务收集和管理消息。系统管理员使用journalctl命令来查看和操作 systemd 日志。

使用 systemd 管理日志的目的是为 Linux 主机中所有生成日志的实体提供一个中心控制点。

让我们探索一下使用 journalctl 的一些基础知识。

实验 8-11

这个实验必须以 root 用户身份运行。首先让我们看看没有选项时得到的输出。默认情况下,结果通过less实用程序传输。

[root@testvm1 ~]# journalctl
-- Logs begin at Sat 2017-04-29 18:10:23 EDT, end at Wed 2017-12-27 11:30:07 EST. --
Apr 29 18:10:23 testvm1 systemd-journald[160]: Runtime journal (/run/log/journal/) is 8.0M, max 197.6M,
Apr 29 18:10:23 testvm1 kernel: Linux version 4.8.6-300.fc25.x86_64 (mockbuild@bkernel02.phx2.fedorapro
Apr 29 18:10:23 testvm1 kernel: Command line: BOOT_IMAGE=/vmlinuz-4.8.6-300.fc25.x86_64 root=/dev/mappe
Apr 29 18:10:23 testvm1 kernel: x86/fpu: Supporting XSAVE feature 0x001: 'x87 floating point registers'
Apr 29 18:10:23 testvm1 kernel: x86/fpu: Supporting XSAVE feature 0x002: 'SSE registers'
Apr 29 18:10:23 testvm1 kernel: x86/fpu: Supporting XSAVE feature 0x004: 'AVX registers'

我只展示了 journalctl 命令输出的一小部分。它应该看起来很熟悉,因为它就是。这几乎与 dmesg 命令提供的信息相同。主要区别在于,dmesg 的时间戳是以启动后的秒为单位,而 journalctl 的时间戳是标准的日期和时间格式。

花些时间浏览结果,并探索其中的日志条目类型。

我在研究这个实验时了解到的一个特性是能够定义一个特定的时间范围来搜索日志条目。这里显示了一个例子。

[root@testvm1 ~]# journalctl --since 2017-12-20 --until 2017-12-24

还可以指定一天中的时间,并使用模糊时间(如“昨天”)和用户名来进一步定义结果。

[root@testvm1 ~]# journalctl --since yesterday -u NetworkManager
-- Logs begin at Sat 2017-04-29 18:10:23 EDT, end at Wed 2017-12-27 11:50:07 EST. --
Dec 26 00:09:23 testvm1 dhclient[856]: DHCPREQUEST on enp0s3 to 192.168.0.51 port 67 (xid=0xaa5aef49)
Dec 26 00:09:23 testvm1 dhclient[856]: DHCPACK from 192.168.0.51 (xid=0xaa5aef49)
Dec 26 00:09:23 testvm1 NetworkManager[731]: <info>  [1514264963.5813] dhcp4 (enp0s3):   address 192.168.0.101
Dec 26 00:09:23 testvm1 NetworkManager[731]: <info>  [1514264963.5819] dhcp4 (enp0s3):   plen 24 (255.255.255.0)
Dec 26 00:09:23 testvm1 NetworkManager[731]: <info>  [1514264963.5821] dhcp4 (enp0s3):   gateway 192.168.0.254
Dec 26 00:09:23 testvm1 NetworkManager[731]: <info>  [1514264963.5823] dhcp4 (enp0s3):   lease time 21600
Dec 26 00:09:23 testvm1 NetworkManager[731]: <info>  [1514264963.5825] dhcp4 (enp0s3):   nameserver '192.168.0.51'
Dec 26 00:09:23 testvm1 NetworkManager[731]: <info>  [1514264963.5826] dhcp4 (enp0s3):   nameserver '8.8.8.8'
Dec 26 00:09:23 testvm1 NetworkManager[731]: <info>  [1514264963.5828] dhcp4 (enp0s3):   nameserver '8.8.4.4'
Dec 26 00:09:23 testvm1 NetworkManager[731]: <info>  [1514264963.5830] dhcp4 (enp0s3):   domain name 'both.org'
Dec 26 00:09:23 testvm1 NetworkManager[731]: <info>  [1514264963.5831] dhcp4 (enp0s3): state changed bound -> bound
Dec 26 00:09:23 testvm1 dhclient[856]: bound to 192.168.0.101 -- renewal in 9790 seconds.
Dec 26 02:52:33 testvm1 dhclient[856]: DHCPREQUEST on enp0s3 to 192.168.0.51 port 67 (xid=0xaa5aef49)
Dec 26 02:52:33 testvm1 dhclient[856]: DHCPACK from 192.168.0.51 (xid=0xaa5aef49)
Dec 26 02:52:33 testvm1 NetworkManager[731]: <info>  [1514274753.4249] dhcp4 (enp0s3):   address 192.168.0.101
Dec 26 02:52:33 testvm1 NetworkManager[731]: <info>  [1514274753.4253] dhcp4 (enp0s3):   plen 24 (255.255.255.0)
Dec 26 02:52:33 testvm1 NetworkManager[731]: <info>  [1514274753.4255] dhcp4 (enp0s3):   gateway 192.168.0.254
Dec 26 02:52:33 testvm1 NetworkManager[731]: <info>  [1514274753.4256] dhcp4 (enp0s3):   lease time 21600
Dec 26 02:52:33 testvm1 NetworkManager[731]: <info>  [1514274753.4258] dhcp4 (enp0s3):   nameserver '192.168.0.51'
Dec 26 02:52:33 testvm1 NetworkManager[731]: <info>  [1514274753.4260] dhcp4 (enp0s3):   nameserver '8.8.8.8'
Dec 26 02:52:33 testvm1 NetworkManager[731]: <info>  [1514274753.4262] dhcp4 (enp0s3):   nameserver '8.8.4.4'
Dec 26 02:52:33 testvm1 NetworkManager[731]: <info>  [1514274753.4263] dhcp4 (enp0s3):   domain name 'both.org'

可以列出系统以前的引导,并仅查看当前或以前引导的日志条目。

[root@testvm1 ~]# journalctl --list-boots
-24 f5c1c24249df4d589ca8acb07d2edcf8 Sat 2017-04-29 18:10:23 EDT—Sun 2017-04-30 07:21:53 EDT
-23 ca4f8a71782246b292920e92bbdf968e Sun 2017-04-30 07:22:13 EDT—Sun 2017-04-30 08:41:23 EDT
-22 ca8203a3d32046e9a96e301b4c4b270a Sun 2017-04-30 08:41:38 EDT—Sun 2017-04-30 09:21:47 EDT
-21 1e5d609d89a543708a12f91b3e94350f Tue 2017-05-02 04:32:32 EDT—Tue 2017-05-02 08:51:42 EDT
-20 74b2554da751454f9f75c541d9390fc0 Sun 2017-05-07 05:35:44 EDT—Sun 2017-05-07 09:43:27 EDT
-19 4a6d9f2f34aa49a7bfba31368ce489e5 Fri 2017-05-12 06:11:48 EDT—Fri 2017-05-12 10:14:34 EDT
-18 bf8d02a57d0f4e9b849405ede1ffc80b Sat 2017-05-13 05:42:07 EDT—Sat 2017-05-13 12:20:36 EDT
-17 2463e2f48dd04bbfa03b72df90367990 Wed 2017-11-15 07:41:42 EST—Wed 2017-11-15 12:43:14 EST
-16 7882d4c7ff5c43a7b9404bb5aded31f1 Wed 2017-11-15 07:43:28 EST—Wed 2017-11-15 15:39:07 EST
-15 b19061d077634733b3ef5d54a8034665 Wed 2017-11-15 15:39:25 EST—Wed 2017-11-15 16:44:25 EST
-14 3c3c73161a0540d6b02ac14a3fe96fd2 Wed 2017-11-15 16:44:43 EST—Wed 2017-11-15 18:24:38 EST
-13 5807bb2932794fd18bb5bf74345e6586 Thu 2017-11-16 09:06:49 EST—Thu 2017-11-16 21:46:54 EST
-12 1df2c5a7500844a18c692a00ad834a5e Thu 2017-11-16 21:51:47 EST—Tue 2017-11-21 17:00:22 EST
-11 fe65766e48484d6bb45e450a1e46d257 Wed 2017-11-22 03:50:03 EST—Fri 2017-12-01 06:50:03 EST
-10 d84cf9eb31dc4d0886e1e474e21f7e45 Sat 2017-12-02 11:45:45 EST—Mon 2017-12-04 17:01:53 EST
 -9 d8234499519e4f4689acc326035b5b77 Thu 2017-12-07 07:52:08 EST—Mon 2017-12-11 06:40:44 EST
 -8 ec50e23f7ffb49b0af06fb0a415931c2 Tue 2017-12-12 03:17:36 EST—Fri 2017-12-15 21:42:09 EST
 -7 de72447d9eea4bbe9bdf29df4e4ae79c Sun 2017-12-17 11:13:43 EST—Sun 2017-12-17 21:30:54 EST
 -6 a8781fdba6cc417dbde3c35ed1a11cc0 Sun 2017-12-17 21:31:11 EST—Tue 2017-12-19 21:57:23 EST
 -5 6ed3997fc5bf4a99bbab3cc0d3a35d80 Wed 2017-12-20 16:54:01 EST—Fri 2017-12-22 10:48:30 EST
 -4 c96aa6518d6d40df902fb85f0b5a9d5b Fri 2017-12-22 10:48:39 EST—Sun 2017-12-24 13:44:28 EST
 -3 ad6217b027f34b3db6215e9d9eeb9d0b Sun 2017-12-24 13:44:44 EST—Mon 2017-12-25 15:26:28 EST
 -2 aca68c1bae4741de8d38de9a9d28a72e Mon 2017-12-25 15:26:44 EST—Mon 2017-12-25 15:29:39 EST
 -1 23169c91452645418a22c553cc387f99 Mon 2017-12-25 15:29:54 EST—Mon 2017-12-25 15:31:40 EST
  0 3335b2cb0d124ee0a93d2ac64537aa54 Mon 2017-12-25 15:31:55 EST—Wed 2017-12-27 11:50:07 EST

[root@testvm1 ~]# journalctl -b ec50e23f7ffb49b0af06fb0a415931c2

该命令列出的引导标识符来自引导列表中的第 8 行。对于最后一个命令,请确保使用您自己系统中的标识符。

我没有显示最后一个命令的任何输出,因为它很长。请务必花一些时间查看最后一个命令中的数据。

正如你在实验 8-11 中看到的,systemd 日志记录工具从启动过程开始到关机结束收集数据。所有类型的日志都位于日记数据库中。您可以使用less实用程序的搜索功能来定位特定条目,或者您可以使用journalctl本身的可用选项。

如果您有兴趣了解有关管理 systemd 日志的更多信息,可以从 journalctl 的手册页开始。数字海洋有精彩讨论journalctl. 10

日志监控

在处理问题时,使用像greptail这样的工具来查看日志文件中的几行内容是没问题的。但是,如果您需要搜索大量日志文件,该怎么办呢?即使使用这些工具,这也可能是乏味的。

Logwatch 是一个工具,可以分析系统日志文件并检测系统管理员应该查看的异常条目。它会在每晚午夜左右生成一份报告。每日报告由/etc/cron.daily 中的文件触发。

缺省配置是让 Logwatch 通过电子邮件向 root 用户发送它在日志文件中发现的内容的报告。有多种方法可以确保电子邮件被发送到本地主机上的某个人和某个地方,而不是 root 用户。一种方法是在/etc/logwatch 目录的配置文件中调用 set mailto 地址。

Logwatch 也可以从命令行运行,数据被发送到 STDOUT。听起来探索起来很有趣。

实验 8-12

这个实验必须以 root 用户身份进行。我们的目标是从命令行运行 Logwatch 并查看结果。

[root@david ~]# logwatch | less
 ################### Logwatch 7.4.3 (04/27/16) ####################
        Processing Initiated: Wed Dec 27 09:43:13 2017
        Date Range Processed: yesterday
                              ( 2017-Dec-26 )
                              Period is day.
        Detail Level of Output: 10
        Type of Output/Format: stdout / text
        Logfiles for Host: david
 ##################################################################
--------------------- Disk Space Begin ------------------------

 Filesystem                      Size  Used Avail Use% Mounted on
 devtmpfs                         32G     0   32G   0% /dev
 /dev/mapper/david1-root         9.1G  444M  8.2G   6% /
 /dev/mapper/david1-usr           46G   14G   31G  31% /usr
 /dev/sdc1                       1.9G  400M  1.4G  24% /boot
 /dev/mapper/vg_david2-stuff     128G  107G   16G  88% /stuff
 /dev/mapper/david1-var           19G  5.4G   12G  32% /var
 /dev/mapper/david1-tmp           29G   12G   15G  44% /tmp
 /dev/mapper/vg_david2-home       50G   27G   20G  58% /home
 /dev/mapper/vg_david2-Pictures   74G   18G   53G  25% /home/dboth/Pictures
 /dev/mapper/vg_david2-Virtual   581G  402G  153G  73% /Virtual
 /dev/mapper/vg_Backups-Backups  3.6T  2.9T  597G  83% /media/Backups
 /dev/sdd1                       3.6T  1.6T  1.9T  45% /media/4T-Backup

---------------------- Disk Space End -------------------------

--------------------- Fortune Begin ------------------------
 If we do not change our direction we are likely to end up where we are headed.
---------------------- Fortune End -------------------------
--------------------- lm_sensors output Begin ------------------------
 coretemp-isa-0000
 Adapter: ISA adapter
 Package id 0:  +50.0 C  (high = +95.0 C, crit = +105.0 C)
 Core 0:        +46.0 C  (high = +95.0 C, crit = +105.0 C)
 Core 1:        +49.0 C  (high = +95.0 C, crit = +105.0 C)
 Core 2:        +45.0 C  (high = +95.0 C, crit = +105.0 C)
 Core 3:        +50.0 C  (high = +95.0 C, crit = +105.0 C)
 Core 4:        +48.0 C  (high = +95.0 C, crit = +105.0 C)
 Core 5:        +46.0 C  (high = +95.0 C, crit = +105.0 C)
 Core 6:        +44.0 C  (high = +95.0 C, crit = +105.0 C)
 Core 7:        +46.0 C  (high = +95.0 C, crit = +105.0 C)
 Core 8:        +50.0 C  (high = +95.0 C, crit = +105.0 C)
 Core 9:        +49.0 C  (high = +95.0 C, crit = +105.0 C)
 Core 10:       +50.0 C  (high = +95.0 C, crit = +105.0 C)
 Core 11:       +45.0 C  (high = +95.0 C, crit = +105.0 C)
 Core 12:       +47.0 C  (high = +95.0 C, crit = +105.0 C)
 Core 13:       +45.0 C  (high = +95.0 C, crit = +105.0 C)
 Core 14:       +45.0 C  (high = +95.0 C, crit = +105.0 C)
 Core 15:       +47.0 C  (high = +95.0 C, crit = +105.0 C)

 radeon-pci-6500
 Adapter: PCI adapter
 temp1:        +39.0 C  (crit = +120.0 C, hyst = +90.0 C)

 asus-isa-0000
 Adapter: ISA adapter
 cpu_fan:        0 RPM
 ---------------------- lm_sensors output End -------------------------

浏览 Logwatch 生成的数据,并确保查找内核、Cron、磁盘空间和 Systemd 部分。如果您有一个运行这个实验的物理主机,并且安装了 lm_sensors 包,那么您可能还会看到一个显示硬件各个部分的温度的部分,包括每个 CPU 的温度。

我只包括了输出的几个部分,因为 Logwatch 在我的工作站上生成了 1,400 多行。

日志观察器输出中出现的部分取决于您在 Linux 计算机上安装的软件包。因此,如果您正在查看基本安装的 Logwatch 的输出,而不是主工作站甚至服务器的输出,您将会看到少得多的条目。

从 2014 年开始,Logwatch 确实具备了在 journald 数据库中搜索日志条目的能力。 11 与 systemd 日志记录工具的兼容性确保了日志条目的主要来源不会被忽略。

懒惰的系统管理员的成功

到现在为止,你已经明白了这一章并不是关于通常意义上的懒惰。成功的懒惰系统管理员并不懒惰——只是高效。正如我在 IBM 担任 CE 时一样,通过预测问题并做必要的工作来防止问题发生,以确保它们不会发生或能够被有效地解决,从长远来看是有好处的。

我在这里讨论的策略并不是唯一可以用来提高我们自身效率的策略。我确信你已经找到了许多你自己更聪明地工作的方法。

有一种方法可以极大地利用我们的技能和知识,虽然我提到过很多次,但我还没有详细讨论过。在第九章中,我们将探索“自动化一切”的宗旨以及它真正的含义。

*

九、自动化一切

我的问题是,“计算机的功能是什么?”正确的答案是,“将日常任务自动化,以便让我们人类能够专注于计算机还不能完成的任务。”对于系统管理员,也就是我们这些最密切地运行和管理计算机的人来说,我们可以直接使用工具来帮助我们更有效地工作。我们应该最大限度地利用这些工具。

在这一章中,我们将探索如何使用自动化来使我们作为系统管理员的生活变得更加轻松。

我为什么使用脚本

在第八章“做一个懒惰的系统管理员”中,我说,“一个系统管理员在思考的时候最有效率——思考如何解决现有的问题和如何避免未来的问题;思考如何监控 Linux 计算机,以便找到预测和预示未来问题的线索;思考如何让她的工作更有效率;思考如何自动化所有需要每天或每年执行一次的任务。”

当创建 Shell 程序时,系统管理员是下一个最有效率的,这些 Shell 程序将他们设想的解决方案自动化,而看起来却没有效率。我们的自动化程度越高,我们就有越多的时间来解决出现的实际问题,并考虑如何实现比我们现有的自动化程度更高的自动化。

我认识到,至少对我来说,编写 shell 程序——也称为脚本——提供了利用我的时间的最佳策略。一旦编写了一个 shell 程序,就可以根据需要多次重新运行。

我可以根据需要更新我的 shell 脚本,以补偿从一个 Linux 版本到下一个版本的变化。可能需要进行这些更改的其他因素包括安装新的硬件和软件、更改我希望或需要用脚本完成的内容、添加新功能、删除不再需要的功能,以及修复脚本中不常见的错误。对于任何类型的代码来说,这些变化只是维护周期的一部分。

在终端会话中,通过键盘输入和执行 shell 命令来执行的每项任务都可以而且应该是自动化的。系统管理员应该自动化我们被要求做的或者我们自己决定需要做的所有事情。很多时候,我发现预先做好自动化工作可以节省时间。

一个 bash 脚本可以包含几个到几千个命令。事实上,我编写的 bash 脚本中只有一两个命令。我写的另一个脚本包含 2700 多行,其中一半以上是注释。

我是如何来到这里的

我是如何走到“自动化一切”这一步的?

您是否曾经在命令行执行一个漫长而复杂的任务时想,“很高兴完成了——我再也不用担心它了。”?我有——非常频繁。我最终发现,几乎所有我需要在电脑上做的事情,无论是我的,还是属于我的雇主或我的一个咨询客户的,都需要在未来的某个时候再做一次。

当然,我总是认为我会记得我是如何完成这个任务的。但是下一次我需要去做的时候已经很遥远了,以至于我有时甚至忘记了我曾经做过这件事,更不用说如何去做了。对于我以前做的一些任务,我开始在一张纸上写下需要的步骤。我想,“我真笨!”于是我把这些涂鸦转移到我电脑上的一个简单的记事本类型的应用中。突然有一天我又想:“我真笨!”如果我要将这些数据存储在我的计算机上,我不妨创建一个 shell 脚本并将其存储在一个标准位置,这样我只需键入 shell 程序的名称,它就可以完成我以前手动完成的所有任务。

我个人认为一切自动化的主要原因是,任何必须执行一次的任务肯定需要重新执行。通过将执行任务所需的命令收集到一个文件中用作 shell 程序,以后运行完全相同的命令集就变得容易了。

对我来说,自动化还意味着我不必为了再次做这件事而去记忆或重新创建我是如何完成这项任务的细节。记住如何做事情需要时间,输入所有命令也需要时间。对于需要键入大量长命令的任务来说,这可能会成为一个很大的时间陷阱。通过创建 shell 脚本来实现任务自动化减少了执行日常任务所需的输入。

Shell 程序对于新的系统管理员来说也是一个重要的帮助,使他们能够在高级系统管理员外出度假或生病时保持工作。因为 shell 程序天生就对查看和更改开放,所以对于经验较少的系统管理员来说,当他们需要负责这些任务时,它们可以成为学习如何执行这些任务的细节的重要工具。

编写重复性任务的脚本

我总是同时拥有几台计算机,有时多达 14 或 15 台,尽管我目前只剩下 8 或 9 台,以及类似数量的虚拟机用于测试。我也在客户系统上安装 Linux。因此,我经常安装 Linux。有时一天好几个。这导致需要进行快速、可重复的安装。

例如,我有一组最喜欢的配置,比如 Midnight Commander (mc),一个具有文本模式用户界面的强大文件管理器,以及其他可配置工具。我也有一些我喜欢安装的字体,它们不是大多数默认安装的一部分。我可以用 DNF 手动安装每种字体,每次安装的时候我可以手动修改 Midnight Commander 的配置,但是这要花很多时间,而且非常乏味和无聊。

当我手动做这些事情的时候,我会忘记一些事情,所以我开始记录要做的事情,但是这仍然非常耗时。所以这些年来,我开发了一个过程,确保安装快速可靠地完成,并且我不会忘记安装或配置任何东西。

我以前的流程是先做一个非常基本的安装。我会按照自己的方式配置磁盘分区和逻辑卷。我没有浏览可用软件包或组的完整列表,并试图记住我想要安装的软件包或组,以便在我的计算机上获得我想要的工具。浏览安装程序提供的选项并选择我想要的选项非常麻烦,而且花费了很多时间。

让它变得更容易

我开发了一个非常简单的 bash 脚本,运行它来配置和安装我想要的其他 RPM 包。在执行了基本安装之后,我将以 root 用户身份登录到终端会话并运行我的脚本。

随着时间的推移,这个简单的脚本演变为包括命令行选项,允许我根据不同的需求(无论是台式机、服务器、客户系统还是教室系统)来定制标准安装。当我了解到我发现有用的工具时,我将它们添加到要安装的软件包列表中。

我创建了需要安装的各种配置文件,并确定最好的方法是创建一个包含这些文件的 RPM 包。其中一些文件是我多年来创建的用于执行各种其他重复性任务的更多脚本,以及我的安装后脚本。

RPM 包本身就是一种自动化的形式,因为它让我不再需要记住要安装哪些文件以及要安装在哪里。RPM 包现在安装了大约十几个我自己创建的文件,并确保从各种 Fedora 和 CentOS 存储库中安装了某些必备的 RPM 包。大约 10 年来,我一直在改进安装后脚本,它有超过 1500 行代码,超过 1100 行注释,总共超过 2600 行。

即使使用 RPM 和安装后脚本,仍然需要一个多小时才能完成所有的工作,才能让我安装的每一台计算机都符合我的标准。我当然不会怀念手动输入所有这些指令,等待每一个指令完成后再输入下一个指令的日子。我现在需要做的就是安装 RPM,然后键入一个命令来安装所有其他的包并对它们进行配置。

从理想到必然

一切都进行得很顺利,虽然我可以手动完成所有这些工作,但使用自动化要容易得多。当 Fedora 21 出现在舞台上时,我多年来创造的自动化成了一种必需品。

对于那些不熟悉 Fedora 21 的人来说,该版本的安装程序发生了巨大的变化。不是单一的 ISO 映像,而是三个独立的安装 ISO 映像:桌面、服务器和云。

我使用过台式机和服务器 ISOs 进行安装,我非常不喜欢它们。我认为新的安装对于大多数 Fedora 用户来说是非常有限的。没有简单的安装映像。桌面 ISO 是一个实时映像。在安装过程中,除了实时映像 ISO 中的软件包之外,没有安装任何软件包的选项。没有。如果我想安装 KDE——或任何其他——桌面,而不是 GNOME(我这样做了),我必须在初始安装后下载 KDE 旋转或安装 KDE。我无法从主安装介质(实时映像)中完成此操作。

我甚至不能选择安装 LibreOffice。在安装过程中没有办法做到这一点。在初始安装之后,我必须安装那个和许多其他的东西。在我看来,这对许多潜在的 Linux 用户来说是一个巨大的绊脚石,尤其是 noobs。当然,我总是在执行新的 Fedora 安装后立即安装更新,因为总是有更新。

幸运的是,我的后安装 RPM 和后安装脚本允许我毫不费力地完成所有这些事情。是的,我不得不对我的脚本做一些调整——正如我对每个新版本所做的那样——以适应不同版本之间的一些变化。

我对遵循系统管理员的 Linux 理念的嗜好给我带来了非常好的回报。因为我花时间“自动化一切”,我个人很少经历由于 Fedora Linux 处理安装方式的重大改变而造成的中断。

这就是我通过自动化安装所获得的收获。

  • 节省每次安装的时间。

  • 安装是一致的。

  • 始终安装更新。

  • 对配电装置进行重大变更时,中断最小或没有中断。

  • 易于创建相同的安装。

还有其他方法可以实现 Linux 安装和配置的自动化,并且有许多工具可以应用于这项任务,比如 Kickstart、Puppet、Satellite Server 等等。我广泛使用了 Kickstart。参见我和一位同事为 Linux 杂志写的文章“完整的 Kickstart”——我在自己的网站上保留了一份。 1

我的脚本在我当前的环境下非常适合我,并且满足了我的需求——这就是 Linux 游戏的名字。

更新

我经常做的另一项任务是在我所有的计算机上安装更新。事实上,今天早上我一直在更新。这是一个只需要做几个决定的任务,并且很容易自动化。"但是这么简单,为什么要自动化一个只需要一两个命令的任务呢?"原来更新不是这么简单的。让我们思考一下这个问题。

首先,我必须确定是否有可用的更新。然后我需要确定需要重启的包是否正在更新,比如内核或 glibc。此时,我可以安装更新。在重新启动之前,假设需要重新启动,我运行mandb实用程序来更新手册页;如果不这样做,新的和替换的手册页将无法访问,被删除的旧手册页将会出现在那里,尽管它们并不存在。图 9-1 显示了今天早上更新完 man 数据库后的部分结果。然后,如果内核已经更新,我需要重新构建 grub 引导加载程序配置文件,以便它包含每个已安装内核的恢复选项。最后,如果需要重启,我会这样做。

img/462716_1_En_9_Fig1_HTML.png

图 9-1

执行升级后运行 mandb 的部分结果

这是一组需要一些决策的非平凡的单个任务和命令。手动执行这些任务需要注意力和干预,以便在前一个命令完成时输入新的命令。因为需要在等待输入下一个命令的时候照看孩子,这将花费我大量的时间来监控每台计算机,因为它经历了这些过程。当我在主机上输入错误的命令时,偶尔会有人提醒我,这是有出错的空间的。

img/462716_1_En_9_Figa_HTML.png

使用我在上面创建的需求陈述,因为这就是那个段落的真实内容,很容易自动消除所有这些问题。我写了一个叫做 doUpdates 的小脚本。它的长度超过 400 行,提供了帮助、详细模式、打印当前版本号等选项,以及只有在内核或 glibc 更新后才重新启动的选项。

这个程序中超过一半的行都是注释,所以下次我需要修改它或者增加一些功能的时候,我可以记得这个程序是如何工作的。大部分基本功能都是从一个模板文件中复制的,该模板文件维护了我在编写的每个脚本中使用的所有标准组件。因为新脚本的框架总是在那里,所以很容易开始新的脚本。

图 9-2 是 doUpdates bash 脚本的列表。为了防止大部分较长的行换行,我将字体大小设置得比平时小一点。然而,一些很长的行被包装。如果太小,读起来不舒服,我道歉。我还删除了一些空白行和空的注释行,以尽可能缩短列表,但它仍然有大约 8 页长。

img/462716_1_En_9_Fig2a_HTML.pngimg/462716_1_En_9_Fig2b_HTML.pngimg/462716_1_En_9_Fig2c_HTML.pngimg/462716_1_En_9_Fig2d_HTML.pngimg/462716_1_En_9_Fig2e_HTML.pngimg/462716_1_En_9_Fig2f_HTML.pngimg/462716_1_En_9_Fig2g_HTML.pngimg/462716_1_En_9_Fig2h_HTML.pngimg/462716_1_En_9_Fig2i_HTML.png

图 9-2

doUpdates 脚本的列表

根据 Linux FHS,doUpdates 脚本应该位于/usr/local/bin 中。它可以使用命令doUpdates -r运行,该命令将导致它仅在满足其中一个或两个条件时重新启动主机。

我不会为你解构整个 doUpdates 程序,但有些事情我想引起你的注意。先注意评论数量;这些帮助我记住每个部分应该做什么。she-bang(!/bin/bash)包含程序的名称、其功能的简短描述以及维护或更改历史。第一部分基于我在 IBM 工作时学到并遵循的一些实践。其他意见描述了各种程序和主要部分,并提供了每个简短的描述。最后,嵌入在代码中的较短注释描述了较短代码的功能或目标,如流控制结构。

我在脚本的开头有大量的程序。这是他们狂欢的地方。这些过程来自我的模板脚本,我尽可能在新脚本中使用它们,以节省重写它们的工作量。

过程名和变量名是有意义的,有些用大写字母表示一两个字符。这使得阅读更容易,并帮助程序员(我)和任何未来的维护者(也包括我)理解过程和变量的功能。是的,这似乎与哲学的另一个原则相违背,但是从长远来看,使代码更具可读性可以节省更多的时间。我从自己和他人的代码中了解到这一点。

我为一个组织做了一些咨询工作,这个组织让我开始修复一些脚本中的错误。我看了一眼脚本,知道要修复实际的错误需要做很多工作,因为我首先要修复脚本的可读性。我从给脚本添加注释开始,因为没有注释。然后,我开始重命名变量和过程,以便更容易理解这些变量的用途和它们所保存的数据的性质。只有在做出这些改变之后,我才能开始理解他们所经历的问题的本质。

我们将在第十八章中看到更多关于这个组织的内容。他们真的对那些剧本有很多问题。

doUpdates 脚本可以从 Apress 网站下载。

https://github.com/Apress/linux-philo-sysadmins/tree/master/Ch09

额外的自动化水平

现在我有了这个令人难以置信的精彩而有用的脚本。我已经把它复制到我所有电脑上的/usr/local/bin。我现在要做的就是在适当的时候在我的每台 Linux 主机上运行它来进行更新。我可以通过使用 SSH 登录到每台主机并运行程序来做到这一点。

但是等等!还有呢!我告诉过你宋承宪有多酷吗?

ssh命令是一个安全的终端模拟器,允许用户登录到远程计算机来访问远程 shell 会话并运行命令。所以我可以登录到远程计算机,并在远程计算机上运行doUpdates命令。结果显示在我的本地主机上的 ssh 终端模拟器窗口中。该命令的标准输出(STDOUT)显示在我的终端窗口上。

那部分是琐碎的,每个人都这样做。但是下一步更有趣一些。与其在远程计算机上维护终端会话,我可以简单地在本地计算机上使用如图 9-3 所示的命令,在远程计算机上运行相同的命令,结果显示在本地主机上。这里假设 SSH 公共/私有密钥对 2 (PPKP)正在使用中,并且我不必在每次向远程主机发出命令时都输入密码。

img/462716_1_En_9_Fig3_HTML.png

图 9-3

此命令使用公钥/私钥对进行身份验证,在远程主机上运行 doUpdates 程序

现在,我在本地主机上运行一个命令,通过 SSH 隧道向远程主机发送一个命令。好的,这很好,但是这意味着什么呢?

这意味着我能在一台电脑上做的事,我也能在几台甚至几百台电脑上做。图 9-4 中的 bash 命令行程序展示了我现在拥有的能力。

img/462716_1_En_9_Fig4_HTML.png

图 9-4

这个 bash 命令行程序在三台远程主机上运行 doUpdates 程序

以为我们完事了吗?不,我们不是!下一步是为这个 CLI 程序创建一个简短的 bash 脚本,这样我们就不必在每次想要在主机上安装更新时重新键入它。这不一定要花里胡哨;脚本可以像图 9-5 中的脚本一样简单。

img/462716_1_En_9_Fig5_HTML.png

图 9-5

这个 bash 脚本包含在三台远程主机上运行 doUpdates 程序的命令行程序

这个脚本可以被命名为“updates”或其他名称,这取决于您喜欢如何命名脚本以及您认为它的最终功能是什么。我认为我们应该把这个脚本叫做“doit .”现在我们只需输入一个命令,就可以在for语句列表中的所有主机上运行智能更新程序。我们的脚本应该位于/usr/local/bin 目录中,这样就可以很容易地从命令行运行它。

我们的小doit脚本看起来可以作为更广泛应用的基础。我们可以向 doit 添加更多的代码,使它能够接受参数或选项,比如在列表中的所有主机上运行的命令的名称。这使我们能够在主机列表上运行我们想要的任何命令,我们安装更新的命令可能是doit doUpdates -rdoit myprogram以在每台主机上运行“myprogram”。

下一步可能是从程序本身中取出主机列表,并将它们放在位于/usr/local/etc 中的 doit.conf 文件中——同样符合 Linux FHS。对于简单的doit脚本,该命令看起来如图 9-6 所示。注意后面的 tics( ),它根据cat命令的结果创建了一个由for`结构使用的列表。

img/462716_1_En_9_Fig6_HTML.png

图 9-6

我们现在添加了一个简单的外部列表,其中包含脚本将在其上运行指定命令的主机名

通过保持主机列表独立,我们可以允许非根用户修改主机列表,同时保护程序本身不被修改。给doit程序添加一个-f 选项也很容易,这样用户就可以指定一个文件的名称,该文件包含他们自己的运行指定程序的主机列表。

最后,我们可能希望将它设置为一个 cron 作业,这样我们就不必记得按照我们想要的时间表来运行它。设置 cron 作业值得在本章中单独讨论,所以接下来将会讨论。

使用 cron 实现及时自动化

有许多任务需要在没有人使用计算机的非工作时间执行,或者更重要的是,在特定的时间定期执行。我不想不得不在凌晨起床开始备份或重大更新,所以我使用 cron 服务重复地安排任务,比如每天、每周或每月。让我们看看 cron 服务以及如何使用它。

我使用 cron 服务安排一些显而易见的事情,比如每天凌晨 2:00 进行定期备份。我还做一些不太明显的事情。我所有的电脑都有自己的系统时间,也就是操作系统时间,使用网络时间协议 NTP 来设置。NTP 设置系统时间;它不设置硬件时间,硬件时间可能会漂移并变得不准确。我使用 cron 通过系统时间来设置硬件时间。我还有一个每天早上运行的 bash 程序,它会在每台计算机上创建一个新的“每日消息”(MOTD),其中包含磁盘使用情况等信息,这些信息应该是最新的,以便于使用。许多系统进程也使用 cron 来调度任务。像 logwatch、logrotate 和 rkhunter 这样的服务每天都使用 cron 服务来运行程序。

crond 守护进程是支持 cron 功能的后台服务。

cron 服务检查/var/spool/cron 和/etc/cron.d 目录中的文件以及/etc/anacrontab 文件。这些文件的内容定义了将在不同时间间隔运行的 cron 作业。单个用户 cron 文件位于/var/spool/cron 中,系统服务和应用通常会在/etc/cron.d 目录中添加 cron 作业文件。/etc/anacrontab 是一个特例,我们将在后面介绍。

例行性工作排程

包括 root 在内的每个用户都可以拥有一个 cron 文件。默认情况下没有文件存在,但是使用如图 9-7 所示的 crontab -e 命令编辑 cron 文件会在/var/spool/cron 目录下创建它们。我强烈建议您不要使用标准的编辑器,比如 vi、vim、emacs、nano 或任何其他可用的编辑器。使用crontab命令不仅允许您编辑该命令,还可以在您保存并退出编辑器时重启 crond 守护进程。crontab 命令使用 vi 作为其底层编辑器,因为 vi 总是出现在最基本的安装中。

img/462716_1_En_9_Fig7_HTML.png

图 9-7

crontab 命令用于查看或编辑 cron 文件

第一次编辑时,所有 cron 文件都是空的,因此必须从头开始创建。我将图 9-7 中的作业定义示例添加到我自己的 cron 文件中,只是作为快速参考。可以随意复制自己用。

在图 9-7 中,前三行设置了一个默认环境。需要将环境设置为给定用户所必需的,因为 cron 不提供任何类型的环境。shell 变量指定执行命令时使用的 SHELL。在本例中,它指定了 bash shell。MAILTO 变量设置 cron 作业结果将被发送到的电子邮件地址。这些电子邮件可以提供备份、更新或任何其他内容的状态,并且包括程序的输出,如果您从命令行手动运行它们,您将会看到这些输出。这三行中的最后一行设置了这个环境的路径。然而,不管这里设置了什么路径,我总是喜欢在每个可执行文件前面加上完全限定的路径。

有几个注释行详细说明了定义 cron 作业所需的语法。我认为它们基本上是不言自明的,所以我将使用图 9-7 中的条目作为例子,然后再添加一些条目,向您展示 crontab 文件的一些更高级的功能。

图 9-8 中显示的行运行我的另一个 bash shell 脚本rsbu,来执行我所有系统的备份。这项工作在每天凌晨 1 点后 1 分钟开始。时间规范的位置 3、4 和 5 中的 splat/star/星号(*)类似于那些时间划分的文件组;它们匹配每月的每一天、每月的每一天以及一周的每一天。这一行运行我的备份两次;一次备份到内部专用备份硬盘上,一次备份到我可以带到保险箱的外部 USB 硬盘上。

img/462716_1_En_9_Fig8_HTML.png

图 9-8

/etc/crontab 中的这一行运行一个脚本,为我的系统执行每日备份

图 9-9 所示的行使用系统时钟作为精确时间的来源来设置计算机上的硬件时钟。这条线路设置为每天早上 5 点过 3 分运行。

img/462716_1_En_9_Fig9_HTML.png

图 9-9

这一行使用系统时间作为源来设置硬件时钟

最后一个 cron 作业,如图 9-10 所示,是我们特别感兴趣的一个。它用于在每月第一天的 04:25 执行我们的更新。这里假设我们使用的是图 9-5 中非常简单的doit程序。cron 服务没有“当月最后一天”的选项,所以我们使用下个月的第一天。

img/462716_1_En_9_Fig10_HTML.png

图 9-10

用于运行 doit 命令的 cron 作业,doit 命令又会运行 doUpdates

因此,现在我们网络中的所有主机每个月都会更新,完全不需要我们的干预。这是懒惰的系统管理员的终极表现。

克朗·德

cron 服务提供了一些其他选项,我们也可以使用它们定期运行我们的doit程序。目录/etc/cron.d 用于各种用户运行的系统级作业。这是一些应用在没有用户的情况下运行时安装 cron 文件的地方,这些程序需要一个位置来定位 cron 文件,因此它们被放置在/etc/cron.d 中。root 也可以将其他 cron 文件放置在该目录中,包括非 Root 用户的 cron 文件。许多 Linux 系统管理员更喜欢使用 cron.d 目录来存储 cron 文件,而不是使用位于/var/spool/cron 中的旧 crontab 系统来管理 cron 文件。

位于/etc/cron.d 中的 cron 文件具有与常规 cron 文件相同的格式。对于位于 cron.d 目录中的每个文件,我们上面提到的关于常规 cron 文件的所有信息都是相同的。

位于 cron.d 目录中的文件按字母数字排序顺序运行。这就是 0hourly 文件在其名称的开头有一个零的原因,因此它首先运行。

管理 cron 作业的 crontab 系统的一个缺点是,一些用户使用标准编辑器来修改文件。该方法不会将更改通知 crond 守护进程,因此更改后的 cron 文件在 crond 重新启动之前不会被激活。位于/etc/cron.d 中的 cron 文件则不是这种情况,因为 crond 每分钟都会检查文件修改时间。如果对文件进行了更改,crond 会将其重新加载到内存中。这是一种更积极的方法,可以确保 cron 文件的更改在做出更改后立即得到识别。

让我们为/etc/cron.d 目录创建一个简单的 cron 作业,该作业每分钟运行一次,因此我们不需要长时间等待结果。

实验 9-1

以 root 用户身份执行此实验。只有 root 可以向 cron.d 添加文件。

将/etc/cron.d 设为 PWD,并列出已经位于那里的文件。在一个简单的培训系统或虚拟机上,应该有三个。

[root@david ~]# cd /etc/cron.d ; ls -l
total 12
-rw-r--r-- 1 root root 128 Aug  2 15:32 0hourly
-rw-r--r-- 1 root root  74 Mar 25  2017 atop
-rw-r--r-- 1 root root 108 Aug  3 21:02 raid-check

现在使用您最喜欢的编辑器在 cron.d 中创建一个名为 myfree 的新文件,其内容如下。

# Run the free command every minute. The accumulated
# data is stored in /tmp/free.log where it can be viewed.
* * * * * root /usr/bin/free >> /tmp/free.log

保存新文件。它不应该成为可执行的。不需要对其权限进行任何更改。在另一个根终端会话中,使/tmp 成为 PWD 并列出文件。如果您没有看到 free.log 文件,请等到一分钟结束后大约一秒钟,然后重试。

当 free.log 文件出现时,使用 tail 命令跟踪文件的内容。它应该看起来与我的结果相似。

[root@testvm1 tmp]# tail -f free.log
          total        used        free      shared  buff/cache   available
Mem:    4042112      271168     2757044        1032     1013900     3484604
Swap:   8388604           0     8388604
          total        used        free      shared  buff/cache   available
Mem:    4042112      261008     2767212        1032     1013892     3494860
Swap:   8388604           0     8388604
          total        used        free      shared  buff/cache   available
Mem:    4042112      260856     2767336        1032     1013920     3495012
Swap:   8388604           0     8388604
          total        used        free      shared  buff/cache   available
Mem:    4042112      260708     2767452        1032     1013952     3495148
Swap:   8388604           0     8388604
          total        used        free      shared  buff/cache   available
Mem:    4042112      260664     2767468        1032     1013980     3495176
Swap:   8388604           0     8388604
          total        used        free      shared  buff/cache   available
Mem:    4042112      260772     2767280        1032     1014060     3495040
Swap:   8388604           0     8388604

几个周期之后,删除/etc/cron.d/myfree 文件,或者将其移动到另一个位置。这将停止该作业的执行。您也可以使用 Ctrl-C 退出 tail 命令。

有一个重要的服务依赖于位于/etc/cron.d,anacron 中的 0hourly cron 文件,我们应该查看一下。还有其他的,但是这个为运行计划任务提供了一些有趣的选项。

特点

crond 服务假定主机一直在运行。这意味着,如果计算机关闭了一段时间,而 cron 作业是在这段时间安排的,那么它们将被忽略,直到下次安排它们时才会运行。如果没有运行的 cron 作业很关键,这可能会导致问题。因此,当计算机不需要一直开着时,还有另一个定期运行作业的选择。

anacron 程序执行与常规 cron 作业相同的功能,但是它增加了运行作业的能力,如果计算机关闭或在一个或多个周期内无法运行作业,则会跳过这些作业。这对于关闭或进入睡眠模式的笔记本电脑和其他电脑非常有用。

在计算机打开并启动后不久,anacron 会检查已配置的作业是否错过了最后一次计划运行。如果有,这些作业会立即运行,但不管错过了多少个周期,都只运行一次。例如,如果一个每周作业因为系统在您外出度假时关闭而连续三周未运行,它将在您打开计算机后立即运行,但它将运行一次而不是三次。

anacron 程序提供了一些简单的选项来运行定期调度的任务。只需将您的脚本安装在/etc/cron 中。[每小时|每天|每周|每月]目录,这取决于它们需要运行的频率。

这是如何工作的?这个序列比它第一次出现时要简单。

  1. The crond service runs the cron job specified in /etc/cron.d/0hourly as seen in Figure 9-11.

    img/462716_1_En_9_Fig11_HTML.png

    图 9-11

    /etc/cron.d/0hourly 的内容导致位于/etc/cron.hourly 中的 shell 脚本运行

  2. /etc/cron . d/0 中指定的 cron 作业每小时运行一次 run-parts 程序。run-parts 程序运行位于/etc/cron.hourly 目录中的所有脚本。

  3. The /etc/cron.hourly directory contains the 0anacron script that runs the anacron program using the /etdc/anacrontab configuration file shown in Figure 9-12.

    img/462716_1_En_9_Fig12_HTML.png

    图 9-12

    /etc/anacrontab 文件的内容运行 cron 中的可执行文件。适当时间的[每日|每周|每月]目录

  4. anacron 程序每天运行一次/etc/cron.daily 中的程序;它每周运行一次/etc/cron.weekly 中的作业,每月运行一次 cron.monthly 中的作业。请注意每一行中指定的延迟时间,这有助于防止这些作业自身与其他 cron 作业重叠。

而不是将完整的 bash 程序放在 cron 中。x 目录,我将它们安装在/usr/local/bin 目录中,这允许我从命令行轻松地运行它们。然后,我在适当的 cron 目录中添加一个符号链接,比如/etc/cron.daily。

anacron 程序不是为在特定时间运行程序而设计的。相反,它旨在以指定的时间间隔运行程序,例如每天的凌晨 3 点(参见图 9-12 中的 START_HOURS_RANGE)、开始一周的周日以及每月的第一天。如果错过了任何一个或多个周期,那么 anacron 将尽快运行一次错过的作业。

日程安排提示

我在 crontab 文件中为我的各种系统设置的一些时间看起来相当随机,在某种程度上确实如此。尝试调度 cron 作业可能具有挑战性,尤其是随着作业数量的增加。我通常只在自己的每台计算机上安排几个任务,所以这比我工作过的一些生产和实验室环境要简单一些。

在我担任系统管理员的一个系统中,通常有大约 12 个 cron 作业需要每天晚上运行,另外还有 3 到 4 个必须在周末或月初运行。这是一个挑战,因为如果同时运行太多的作业,特别是备份和编译,系统将耗尽 RAM,然后几乎填满交换文件,这将导致系统崩溃,同时性能下降,从而什么也做不了。我们增加了更多的内存,并且能够更好地安排任务。调整任务列表包括删除一个写得很差并且使用大量内存的任务。

关于克隆的思考

我使用这些方法中的大部分来计划在我的计算机上运行的任务。所有这些任务都需要以 root 权限运行。我只见过几次用户真正需要任何类型的 cron 作业的情况,其中一次是开发人员在开发实验室开始日常编译。

限制非 root 用户对 cron 函数的访问非常重要。然而,在某些情况下,用户可能需要设置任务在预先指定的时间运行,cron 可以允许用户在必要时这样做。系统管理员意识到许多用户不理解如何使用 cron 正确配置这些任务,并且用户在配置中会犯错误。这些错误可能是无害的,但它们会给自己和其他用户带来问题。通过设置使用户与系统管理员交互的程序策略,这些单独的 cron 作业不太可能干扰其他用户和其他系统功能。

可以对分配给单个用户或组的总资源设置限制,但这是另一篇文章了。

cron 资源

cron、crontab、anacron、anacrontab 和 run-parts 的手册页都有关于 cron 系统如何工作的极好的信息和描述。

其他自动化可能性

我已经自动化了许多其他需要在我负责的 Linux 计算机上执行的任务。下面的列表当然不是包罗万象的,只是想给你一些开始的想法。

  • 备份。

  • 升级(dnf-upgrade)。

  • 将本地 shell 脚本的更新分发到一系列主机。

  • 查找和删除非常旧的文件。

  • 创建每日消息(/etc/motd)。

  • 检查病毒、rootkits 和其他恶意软件。

  • 更改/添加/删除邮件列表订户电子邮件地址。

  • 定期检查主机的运行状况,如温度、磁盘使用情况、RAM 使用情况、CPU 使用情况等。

  • 任何重复的东西。

一些另类想法

这里是我在互联网上发现的一些不寻常的自动化想法,它们推动了自动化和适当性这两个目标的界限。原始信息来自 GitHub 知识库,许多程序都有厌女症和 NSFW 的名字。我让你决定是否要在网上搜索这个人,但我不会帮你找到他。

在我找到的参考资料中,这些程序的创建者总是会自动完成每一项耗时超过 90 秒的任务。让我们从我最喜欢的几个开始。

首先是与连接到内部网络的“智能”办公室咖啡机一起工作的 shell 脚本。当这个程序员运行脚本时,它会等待 17 秒,连接到机器并告诉它开始煮一杯咖啡。它会等 24 秒,然后把咖啡倒进杯子里。显然,这是程序员走向咖啡机所花的时间。

接下来是一个脚本,让我们懒惰的系统管理员可以睡懒觉,而不用担心让团队知道他不在。如果他在早上的特定时间还没有登录到他的开发服务器,脚本会发送一封电子邮件来表明他将在家工作。该程序从一个数组中随机选择一个借口,并在发送前将其添加到电子邮件中。该程序由 cron 作业触发。

当然,这家伙工作到很晚的时候还会做什么?如果他在晚上的特定时间仍然登录,这个脚本会向他的妻子|女朋友发送一封带有适当随机借口的电子邮件。

这些脚本与他的编程工作没有直接关系。然而,它们会让他更有效率,因为他不必每天花时间处理这些事情。就我个人而言,我肯定不会自动给我妻子发电子邮件!

但这里的想法表明,几乎任何事情都可以自动化。也许这些“替代”想法会给你一些你自己的节省时间的自动化想法。

深化哲学

系统管理员自己工作的自动化是这项工作的一大部分。正因为如此,面向系统管理员的 Linux 理念的许多原则都与使用 shell 脚本和特定命令行编程来支持自动化的任务和工具有关。

计算机被设计用来自动化各种各样的日常任务,为什么不把它应用到系统管理员的工作中呢?我们这些懒惰的系统管理员利用我们工作的计算机的能力来使我们的工作更容易。尽我们所能实现自动化意味着我们通过创建自动化而腾出的时间现在可以用于响应其他人,尤其是 PHB 的一些真实或感知的紧急情况。它还可以为我们提供更多的自动化时间。

如果你反思我们在这一章中所做的,你会发现自动化不仅仅是创建一个程序来执行每一项任务。它可以使这些程序变得灵活,以便它们可以以多种方式使用,例如从其他脚本调用和作为 cron 作业调用的能力。

我的程序几乎总是使用选项来提供灵活性。本章中使用的doit程序可以很容易地扩展成比现在更通用的程序,同时仍然非常简单。如果它的目标是在一系列主机上运行特定的程序,它仍然可以做好一件事。

我的 shell 脚本并不仅仅包含成百上千行代码。在大多数情况下,它们作为一个单独的特别命令行程序启动。我从临时程序创建了一个 shell 脚本。然后,另一个命令行程序被添加到这个简短的脚本中。然后是另一个。随着简短的脚本变长,我添加了注释、选项和一个帮助特性。

然后,有时候,让一个脚本更通用是有意义的,这样它可以处理更多的情况。通过这种方式,doit脚本变得不仅仅能够为单个程序“做”更新。

十、总是使用 Shell 脚本

当编写程序来自动化——嗯,一切——时,总是使用 shell 脚本。因为 shell 脚本是以 ASCII 文本格式存储的,所以人们可以像计算机一样轻松地查看和修改它们。您可以检查一个 shell 程序,看看它到底做了什么,以及在语法或逻辑上是否有任何明显的错误。这是一个强有力的例子,说明开放意味着什么。

我知道一些开发人员倾向于认为 shell 脚本不是真正的编程。shell 脚本和编写它们的人的边缘化似乎是基于这样一种想法,即唯一真正的编程语言是必须从源代码编译来产生可执行代码的语言。凭经验我可以告诉你,这绝对不是真的。

我用过很多语言,包括 BASIC、C、C++、Pascal、Perl、Tcl/Expect、REXX 以及它的一些变种包括 Object REXX,很多 shell 语言包括 Korn 和 Bash,甚至一些汇编语言。每一种计算机语言都有一个目的——让人类告诉计算机做什么。当你写一个程序时,不管你选择哪种语言,你都是在给计算机指令以特定的顺序执行特定的任务。

定义

shell 脚本或程序是至少包含一个 shell 命令的可执行文件。它们通常有不止一个命令,有些 shell 脚本有数千行代码。综合起来看,这些命令是执行预期任务并获得特定结果所必需的。

虽然包含一行 shell 命令的可执行文件可以在当前的 shell 中运行,但是最好添加一行“shebang ”,用于定义程序运行的 shell。让我们两方面都试试。

实验 10-1

这个实验应该以学生用户的身份进行。我们在您的主目录中创建一个最小的脚本,使它可执行,并运行它。

首先用 vim 在您的主目录中打开一个新文件。

[student@testvm1 ~]$ vim test1

在文件的开头添加一行并保存文件。不要退出 vim,因为我们将对 test1 脚本进行更多的修改。

echo "Hello world!"

在另一个终端会话中,列出一长串新程序。

[student@testvm1 ~]$ ls -l test1
-rw-rw-r-- 1 student student 20 Dec 31 15:27 test1

文件权限显示它不可执行。使其对用户和组可执行,并再次列出。

[student@testvm1 ~]$ chmod ug+x test1
[student@testvm1 ~]$ ls -l test1
-rwxrwxr-- 1 student student 20 Dec 31 15:38 test1

现在让我们运行程序。我们使用。/放在文件名前面,以指定程序文件位于当前目录中。主目录不是路径的一部分,所以我们必须指定可执行文件的路径。

[student@testvm1 ~]$ ./test1
Hello world!

现在让我们在 echo 命令之前添加 shebang 行。这规定了无论我们在哪个 shell 下运行,程序都将在 bash shell 下运行。

现在我们的程序分成两行,看起来像这样。

#!/bin/bash
echo "Hello world!"

再次运行该程序。结果应该不会改变。从 vim 退出。

对于这样一个简单的 shell 脚本,我们是否添加 shebang 行并不重要。我用这个脚本试验的所有 shells 都产生了相同的结果。但是有一些内置的 shell 命令可能在其他 shell 中不存在,或者一些命令可能以不同的方式实现,不同的结果可能会影响程序运行时的结果。

无论如何,包含 shebang 行始终是一个好的做法。

SysAdmin 上下文

上下文很重要,这个原则“总是使用 shell 脚本”应该在我们作为系统管理员的工作上下文中考虑。

系统管理员的工作与开发人员和测试人员的工作有很大的不同。除了解决硬件和软件问题之外,我们还负责管理我们负责的系统的日常运行。我们监控这些系统的潜在问题,并尽一切努力在这些问题影响到我们的用户之前阻止它们。我们安装更新,并对操作系统进行完整版本升级。我们解决由用户引起的问题。系统管理员开发代码来做所有这些事情,甚至更多;然后我们测试代码;然后我们在生产环境中支持这些代码。

我们中的许多人还管理和维护我们的系统所连接的网络。在其他情况下,我们会告诉网络人员问题出在哪里以及如何解决,因为我们首先发现并诊断出问题。

我们系统管理员比开发运维这个术语存在的时间要长得多。事实上,SysAdmin 工作更像是 dev-test-ops-net,而不仅仅是 devops。我们的知识和日常任务清单涵盖了所有这些专业领域。

在这种情况下,创建 shell 脚本的需求是复杂的、相互关联的,而且很多时候是矛盾的。让我们看看系统管理员在编写 shell 脚本时必须考虑的一些典型因素。

要求

这种冗余意味着创建 shell 脚本的一个要求是从请求脚本的最终用户那里获得一组需求。即使我们碰巧既是开发人员又是用户,我们也应该在开始编写代码之前坐下来创建一组需求。

即使是一个简短的两三个项目目标的列表也足以作为一组需求。我将接受的最低限度是输入数据的描述和样本;所需的任何公式、逻辑或其他处理;以及所需输出或功能结果的描述。当然,越多越好,但有了这些东西作为起点,我就可以开始工作了。

随着项目的继续,需求自然会变得更加明确。最初没有考虑到的事情就会出现。假设会被改变。

发展速度

程序通常必须快速编写,以满足环境或 PHB 强加的时间限制。我们编写的大多数脚本都是为了解决问题,清理问题的后果,或者交付一个在编译好的程序被编写和测试之前就必须运行的程序。

快速编写程序需要 shell 编程,因为它允许快速响应客户的需求,无论客户是我们自己还是其他人。如果代码中存在逻辑问题或 bug,它们几乎可以立即得到纠正和重新测试。如果最初的需求集有缺陷或不完整,shell 脚本可以很快修改以满足新的需求。因此,总的来说,我们可以说,系统管理员工作中对开发速度的需求压倒了对使程序尽可能快地运行或尽可能少地使用系统资源(如 ram)的需求。

让我们看看图 10-1 中的 BASH 命令行程序。它旨在列出当前登录到系统的每个用户 ID。我们以前看过这个程序,但是让我们从不同的角度来看它。

img/462716_1_En_10_Fig1_HTML.png

图 10-1

重新访问我们的 CLI 程序以列出登录用户

因为用户可能会多次登录,所以这个一行程序只显示每个 ID 一次,并用逗号分隔 ID。用 C 语言对此进行编程需要大量的专用代码。表 10-1 显示了上述 BASH 程序中使用的每个 CLI 命令的代码行数。几年前我发现这些数字时,它们是准确的。如果自那以后它们发生了变化,也不会有太大影响。

表 10-1

CLI 的强大功能来自这些单独的程序

|

命令

|

源代码行

|
| --- | --- |
| 回声 | One hundred and seventy-seven |
| 谁 | Seven hundred and fifty-five |
| 使用 | Three thousand four hundred and twelve |
| 分类 | Two thousand six hundred and fourteen |
| 金圣柱 | Three hundred and two |
| 一项 Linux 指令 | Two thousand and ninety-three |
| 总数 | Nine thousand three hundred and fifty-three |

您可以看到上面的 BASH 脚本使用的程序总共包含 9000 多行 C 代码。所有这些程序包含的功能远远超过我们在脚本中实际使用的功能。然而我们把这些已经写好的程序结合起来,使用我们需要的部分。

编写和测试生成的 BASH 脚本所需的时间比编译后的程序做同样的事情要少得多。

性能速度

就执行速度而言,脚本性能现在远不如过去重要。今天的 CPU 速度非常快,大多数计算机都有多个处理器。我自己的大多数电脑都有 4 个超线程内核,运行频率为 3GHz 或更高。我的主工作站有一个英特尔酷睿 i9 处理器,有 16 个内核和 32 个 CPU。我倾向于在从事各种项目时同时打开大量的虚拟机,包括为这本书所做的研究。

img/462716_1_En_10_Figa_HTML.png

一般来说,唯一要问的问题是工作是否能及时完成。如果是的话,那就不用担心。如果没有,那么用编译语言编写和测试相同程序所需的时间很可能会更晚。编译后的程序运行时节省的时间少于使用 shell 程序开发时节省的时间。请记住,我们正在考虑系统管理员的工作环境。

考虑图 10-1 中的示例程序和表 10-1 中的 C 代码量。事实是,我们的示例 CLI 程序仍在使用大量已经编写并经过广泛测试的 C 代码。作为懒惰的系统管理员,我们已经有大量的 C 代码以 Linux 核心实用程序和其他命令行实用程序的形式存在。我们应该总是使用已经存在的东西。

这并不意味着在极少数情况下可能不需要进行一些性能调优。我发现有必要提高 shell 脚本的性能。我发现的问题通常更多的是关于处理大量数据,而不是程序的功能逻辑。

而且下周硬件会更快。

变量

几乎所有的事情都使用变量而不是硬编码的值。即使您认为某个值只需要使用一次,比如目录名或文件名,也要创建一个变量,并在应该放置硬编码名称的地方使用该变量。

很多时候,我在脚本中的更多地方需要一个特定的值,所以如果它作为变量被访问,我已经做好了准备。输入变量名比输入完整的目录名花费的时间要少,尤其是当它很长的时候。如果值发生变化,更改脚本也更容易。在一个位置固定变量值比在几个位置替换它要容易得多。

我总是在脚本中有一个单独的位置来设置变量的初始值。将初始变量设置放在同一个地方有助于容易找到它们。

测试

一旦最基本的代码结构完成,在开发过程中的所有阶段,当代码完成时,以及当已经进行了任何需要的更改时,就可以完成 Shell 脚本的交互式测试。

测试计划应该从需求陈述中创建。测试计划将列出需要测试的需求,例如,“对于输入 X,输出应该是 Y,”以及“对于错误的输入,应该显示错误消息 X。”

有了测试计划,我就可以在每个新特性被添加到程序中时对其进行测试。它有助于确保测试在程序开发过程中始终如一。

在第 11 “尽早测试,经常测试”一章中,我们将详细探讨测试,但是现在,测试的重要性不能低估。测试必须从一开始就进行。

开放和开源

就其本质而言,shell 脚本是开放的,因为我们可以阅读它们。它们以 ASCII 文本格式编写,从不被编译或更改为二进制或其他人类不可读的格式。例如,bash shell 读取 shell 程序的内容,并动态地解释它们。它们作为 ASCII 文本文件的存在也意味着 shell 脚本可以很容易地修改并立即运行,而不必等待重新编译。

这种对代码的开放访问也意味着我们可以探索 shell 脚本来帮助理解它们的功能逻辑。这在编写我们自己的脚本时很有用,因为我们可以轻松地将这些现有代码包含在我们自己的脚本中,而不是编写我们自己的代码来执行相同的任务。

当然,这种代码共享依赖于原始代码的开源许可。我总是在代码本身中包含一个明确的许可声明,在这个声明下我共享我写的代码,通常是 GPL V2。很多时候,我甚至可以在程序中选择显示 GPL 许可声明。

对我来说,让我写的所有代码都开源并获得适当的许可是另一个基本要求。

作为原型的 Shell 脚本

我看过许多关于 Unix 哲学的文章和书籍,其中讨论了将 shell 脚本作为大型复杂程序原型的工具。我认为这对应用开发人员而不是系统管理员来说可能有一些价值。这种方法可以允许快速原型和早期测试,以确保程序正是客户想要的。

作为一名系统管理员,我发现 shell 脚本对于原型和完整的程序都是完美的。我的意思是,为什么要花额外的时间把已经运行良好的东西翻译成另一种语言呢?嘿——我们在努力偷懒呢!

过程

我们都有自己的流程——工作方式,使我们能够以自己的方式完成项目。我们都不一样,我们的过程也不一样。有时我们有不止一个过程,这取决于我们的起点。我想向你描述几种对我有效的方法。

暂且应急的

我的大多数编程项目都是从快速而肮脏的命令行程序开始的,我用它们来执行特定的任务。图 9-2 中的 doUpdates 程序就是一个很好的例子。毕竟,安装更新是一个简单的yumdnf命令,对吗?没有那么多。

在很长一段时间里,我会登录每台主机,运行dnf -y update命令,然后如果内核已经更新,就手动重启。当我预先确定内核正在更新时,下一步就发生了。我使用了复合命令dnf -y update && reboot,如果更新成功,它会重启计算机。但是我仍然在命令行上输入命令。

随着家庭网络中计算机数量的增加,我意识到我也在更新 man 数据库;做出决定;如果有内核更新,更新 GRUB 配置文件并运行 reboot 命令。此时,我编写了一个简单的脚本来执行这些任务。

但是那个剧本需要一些自己的决定和我的指导。我不想让脚本每次运行时都随意重启主机。所以我添加了一个选项,只有在内核或 glibc 更新时才重启。嗯,这需要我添加case命令来解释选项。我还添加了一个包含程序当前版本的变量和一个显示版本的选项。稍后,我添加了一个“verbose”选项,这样如果程序遇到问题,我可以获得更多的调试信息。

随着选项的增加,我需要一个帮助工具,所以我添加了它。然后我添加了一个选项来显示 GPL 语句。

很多工作已经完成了,因为我已经在我的其他程序和我用于新程序的模板中包含了这些特性。从模板中复制我需要的那些特性,粘贴到 doUpdates 程序中,并修改它们以满足这个特定程序的需要,这是一件很简单的事情。

许多大型程序都是从那些日常的小命令行程序发展而来,并成为我们日常工作生活中不可或缺的一部分。有时这个过程并不明显,直到你意识到你手上有一个完整的工作脚本。

规划和远见

有些系统管理员写的程序其实是提前计划好的。我再一次从一组需求开始,虽然我试图花更多的时间来制定它们,而不是快速和肮脏的程序。

为了开始编码,我复制了一份脚本模板,并适当地命名它。该模板包含所有的标准程序和基本结构,我需要开始任何项目。该模板包括一个框架帮助工具,一个用适当的返回代码(RC)结束程序的过程,以及一个允许使用选项的case语句。

所以我用这个模板做的第一件事就是编写帮助工具。然后,我测试这是否有效,看起来是否如我所愿。首先对帮助工具进行编码也是文档化过程的开始。它帮助我定义脚本的功能以及一些特性。

在这一点上,我想添加定义特定功能的注释,并在脚本中创建执行序列。如果我需要编写一个新的过程,我会为该过程创建一个小框架,并在其中添加包含其功能的简短描述的注释。通过首先添加这些注释,我已经将我之前创建的需求集嵌入到了代码的结构中。这使得跟踪变得容易,并确保我已经将所有这些需求转化为代码。

然后,我开始向注释的每个部分添加代码。然后,我测试每个新的部分,以确保它满足注释中陈述的需求。

然后我再加一点,测试一下。再加一点然后测试。每次测试,我都会测试所有东西,甚至是我之前测试过的特性和代码段,因为新代码也会破坏现有代码。我遵循这个过程,直到 shell 脚本完成。

模板

我已经提到了很多次,我有一个模板,从我喜欢创建我的程序。让我们看看这个模板,并对它进行实验。您可以在 https://github.com/Apress/linux-philo-sysadmins/tree/master/Ch10 下载 script.template.sh 模板文件。

代码

既然你已经下载了模板,让我们来看看图 10-2 ,我将指出它的一些关键特征。然后我们会做一个实验,看看效果如何。注意图 10-2 中的字体大小有所减小,以减少换行次数,提高可读性。

当然,所有的脚本都应该以 shebang 开头,这个也不例外。然后我添加了几段注释。

第一个注释部分是程序名称和描述以及更改历史。这是我在 IBM 工作时学到的一种格式,它提供了一种记录程序的长期开发和应用于它的任何修复的方法。这是记录你的程序的一个重要的开始。

第二个注释部分是版权和许可声明。我使用 GPL2,这似乎是根据 GPL2 许可的程序的标准声明。如果您选择使用不同的开源许可,这没问题,但是我建议您在代码中添加类似这样的明确声明,以消除任何可能的许可混淆。我最近读了一篇有趣的文章,“源代码就是许可证, 1 ”这有助于解释这背后的推理。

过程部分在这两个注释部分之后开始。这是 Bash 中过程所需的位置。它们必须出现在程序体之前。作为我自己记录一切的需要的一部分,我在每个过程之前放置一个注释,其中包含对它打算做什么的简短描述。我还在过程中加入了注释,以提供进一步的阐述。您可以在这里添加自己的程序。

我不会剖析每个过程的功能。在注释和你阅读代码的能力之间,它们应该是可以理解的。不过,在图 10-2 的最后,我会讨论这个模板的一些其他方面。

img/462716_1_En_10_Fig2a_HTML.pngimg/462716_1_En_10_Fig2b_HTML.pngimg/462716_1_En_10_Fig2c_HTML.png

图 10-2

我将 script.template.sh 模板文件用作新程序的起点

程序的主要部分在程序部分结束后开始。我通常以设置程序中使用的所有变量的初始值开始这一部分。这确保了我使用的所有变量都被设置为某个默认的初始值。它还提供了程序中使用的所有变量的列表。

接下来,我检查 root 是否正在运行这个程序,如果没有,显示一条消息并退出。如果你的程序可以被非根用户运行,你可以删除这个部分。

然后我有getopscase语句来检查命令行以确定是否输入了任何选项。对于每个选项,case 语句设置指定的变量或调用像Help()Quit()这样的过程。如果输入了一个无效的选项,最后一个 case 节设置一个变量来表明这一点,下一段代码抛出一个错误消息并退出。

最后,程序的主体是你的大部分代码将要去的地方。这个程序是可执行的,因为它没有错误。但是因为没有功能代码,你所能做的就是显示帮助和 GPL 许可声明,并生成一个使用无效选项的错误。除非你给程序添加一些功能代码,否则它什么也不会做。

让我们通过实验 10-2 来探索这个模板代码。

实验 10-2

以学生用户的身份执行此实验。如果您尚未这样做,请将文件 script.template.sh 从 https://github.com/Apress/linux-philo-sysadmins/tree/master/Ch10 下载到学生用户的主目录中。将用户和组的权限设置为可执行,并将所有权设置为 student.student。

在作为用户学生的终端会话中,确保 PWD 是您的主目录。在继续下一步之前,创建一个名为 test1.sh 的模板工作副本。

[student@testvm1 ~]$ cp script.template.sh test1.sh

显示帮助信息。

[student@testvm1 ~]$ cd
[student@testvm1 ~]$ ./test1.sh -h

You must be root user to run this program

这段代码告诉您,您必须是 root 用户。您可以通过使用您最喜欢的编辑器注释掉这些代码行来绕过它。这部分代码现在看起来像这样。请务必保存您所做的更改。

#---------------------------------------------------------------------------
# Check for root. Delete if necessary.

# if [ `id -u` != 0 ]
# then
#    echo ""
#    echo "You must be root user to run this program"
#    echo ""
#    Quit 1
# fi

现在使用-h 选项再次运行脚本来查看帮助。

[student@testvm1 ~]$ ./test1.sh -h
Add description of the script functions here.

Syntax: template <option list here>
options:
g     Print the GPL license notification.
h     Print this Help.
v     Verbose mode.
V     Print software version and exit.

好像剧本名字不太对。编辑 test1.sh 脚本,将第一个注释部分顶部和帮助过程中的名称更改为脚本的新名称。在我们处理帮助过程时,添加选项列表。帮助中的“语法”行应该如下所示。

echo "Syntax: test1.sh  -ghvV"

保存更改,并使用-h 选项再次运行脚本。

[student@testvm1 ~]$ ./test1.sh -h
Add description of the script functions here.

Syntax: test1.sh  -ghvV
options:
g     Print the GPL license notification.
h     Print this Help.
v     Verbose mode.
V     Print software version and exit.

让我们看看当你给程序一个它不识别的选项时会发生什么。

[student@testvm1 ~]$ ./test1.sh -a

ERROR: Invalid option
Add description of the script functions here.

Syntax: test1.sh  -ghvV
options:
g     Print the GPL license notification.
h     Print this Help.
v     Verbose mode.
V     Print software version and exit.

Program terminated with error ID 10T

这很好——它显示帮助,并以一条错误消息终止。大多数人不会理解错误消息 ID 的幽默之处——我不会把它留在任何生产脚本中。

因此,让我们至少让我们的小测试脚本执行一些有用的工作。在大量注释之后添加以下行,指示代码主体的开始,但在 Quit 函数调用之前。

free

是的——就这些,只是自由命令。应该是这样的。

#############################################################################
#############################################################################
#############################################################################
#############################################################################
# The main body of your program goes here.
#############################################################################
#############################################################################
#############################################################################
#############################################################################

free

Quit

#############################################################################
# End of program
#############################################################################

保存脚本,不带任何选项再次运行。

[student@testvm1 ~]$ ./test1.sh
            total      used      free    shared  buff/cache   available
Mem:      4046060    248256   3384972       988    412832   3566296
Swap:       4182012           0     4182012
[student@testvm1 ~]$

现在,您已经从一个相当简单的模板中创建了一个工作脚本。您已经执行了一些简单的测试来验证该脚本是否按预期执行。

我喜欢的一个选项是“测试”模式,在这种模式下,程序运行并描述它将做什么,或者将一些调试数据打印到 STDOUT,以便我可以直观地看到它是如何工作的。让我们将该选项添加到模板中。

getopts语句(get options)允许我们指定 bash 脚本的选项输入。然后,我们使用case语句对所有选项进行排序,设置值,执行小任务,或者调用更长的过程。while语句一直循环,直到所有选项都被处理完,除非其中一个选项选择了一条以某种方式退出循环的路径。

实验 10-3

首先,让我们添加一个新变量,Test,并设置初始值为 0(零)。在模板的变量初始化部分添加以下代码行。

Test=0

现在让我们将新的选项字符(t)添加到getopts语句中。

while getopts ":gchrtvV" option; do

现在我们给case语句添加一个新的节。完成的选项处理代码如下所示。

while getopts ":gchrtvV" option; do
   case $option in
      g) # display GPL
         gpl
         Quit;;
      t) # Set test mode
         test=1;;
      v) # Set verbose mode
         verbose=1;;
      V) # Set verbose mode
         echo "Version = $version"
         Quit;;
      h) # display Help
         Help
         Quit;;
     \?) # incorrect option
         badoption=1;;
   esac
done

根据您在case语句节中编写代码的方式,它们出现的顺序会影响结果。

我们还没有完成。您应该在 Help()过程中添加一行。将下面一行添加到帮助过程中。任何对你有意义的地方都可以,但我喜欢按字母顺序排列选项。

echo "t     Set test mode. The program runs but does not perform any actions."

您还应该在更改历史中添加一行。

# 01/30/2018  David Both    Add an option for setting test mode.           #

现在我们需要测试。首先让我们确保我们没有破坏任何东西,然后我们可以通过绕过我们的free语句向“test”添加代码。我在这里只展示了几种可能的测试模式,但是您应该测试每一种可能的选项和选项组合,以确保没有任何问题。

[root@david development]# ./script.template.sh
              total        used        free      shared  buff/cache   available
Mem:      65626576    8896704    48397920      159924     8331952    55963460
Swap:      15626236           0    15626236
[root@david development]# ./script.template.sh -x
ERROR: Invalid option
Add description of the script functions here.

Syntax: template <option list here>
options:
g     Print the GPL license notification.
h     Print this Help.
t     Set test mode. The program runs but does not perform any actions.
v     Verbose mode.
V     Print software version and exit.

Program terminated with error ID 10T
[root@david development]# ./script.template.sh -t
            total       used       free     shared  buff/cache   available
Mem:      65626576     8895716    48399104    159924   8331756    55964424
Swap:      15626236           0    15626236
[root@david development]# ./script.template.sh -h
Add description of the script functions here.

Syntax: template <option list here>
options:
g     Print the GPL license notification.
h     Print this Help.
t     Set test mode. The program runs but does not perform any actions.
v     Verbose mode.
V     Print software version and exit.

现在让我们添加一些代码,如果我们设置了测试模式,这些代码会阻止 free 语句的执行。

# Execute the code only if this is not test mode
if [ $test ]
then
   Msg="Test mode. No action taken."
   PrintMsg
else
   free
fi

我们还测试了一些。

[root@david development]# ./script.template.sh -t
[root@david development]# ./script.template.sh
           total       used       free     shared  buff/cache  available
Mem:    65626576    8904512   48395196     159924    8326868   55955156
Swap:   15626236          0   15626236
[root@david development]#

我在这里只展示了几个结果,但是你可以看到有一个问题。你能看出这是什么吗?当我们处于测试模式时,消息不打印。你能看出为什么吗?如果您查看 PrintMsg()过程,您会看到只有在设置了 verbose 模式的情况下才会打印该消息。

有很多方法可以解决这个问题。一个是从 PrintMsg()过程中删除详细要求。另一种是在 if 语句的测试路径中设置 verbose 模式。您可以在-t case 节中设置详细模式。另一种选择是在运行程序时使用-v 选项。后面的结果看起来是这样的。

[root@david development]# ./script.template.sh -tv
########## Test mode. No action taken. ##########
Program terminated normally

在测试模式下,您会选择哪个选项来显示测试消息?我倾向于在 case 节中设置详细模式,如下所示。

      t) # Set test mode
         verbose=1
         test=1;;

继续进行您选择的任何更改,以确保显示测试模式消息,然后进行广泛的测试,直到您知道一切都正常工作。

请记住,这是一个模板,是具有特定和有用目的的脚本的起点。不必要的代码,比如我们在实验 10-3 中添加的位,可以安全地忽略或删除。

我们将使用这个脚本模板作为第十一章“尽早测试,经常测试”中更有用的脚本的基础

您可以随意使用这个模板,并根据自己的需求进行修改。因为该模板在 GPL2 下是开源的,所以可以共享和修改它。如果你愿意的话,我想让你用它。请记住,懒惰的管理员总是使用免费可用的代码,以避免重复编写已经完成您需要的代码。希望你觉得有用。

最后的想法

编译程序是必要的,并且满足了一个非常重要的需求。但是对于系统管理员来说,总有更好的方法。我们应该总是使用 shell 脚本来满足我们工作的自动化需求。

Shell 脚本已打开;他们的内容和目的是可知的。它们可以很容易地修改,以满足不同的要求。就我个人而言,我没有发现任何我在担任 SysAdmin 角色时需要做的事情是不能用 shell 脚本来完成的。

在极少数情况下,您会发现 shell 脚本做不到的事情,不要用编译语言编写整个程序。尽可能多写 shell 脚本。然后,如果——且仅当——无法通过使用一个 shell 命令或管道中的一系列 shell 命令来完成剩下的那一点点工作,那么就编写一个小程序来做好一件事——这一点点在其他任何地方都找不到。

十一、尽早测试,经常测试

你知道,我差点忘了包括这一章。忘记测试我编写的程序就像忽略测试程序本身一样容易。

这是为什么呢?

我希望我有一个明确的答案。在某些方面,它类似于文档。一旦程序看起来工作了,我们只想继续做最初让我们写这个程序的任务。

总会多一个 bug。

—卢巴尔斯基的控制论昆虫学定律

不管卢巴斯基是谁,他都是正确的。我们永远也找不到代码中的所有 bug。对于我发现的每一个,似乎总会有另一个突然出现,通常是在非常不合时宜的时候。

在第十章,“总是使用 Shell 脚本”,我们开始谈论测试和我用于测试的过程。本章更详细地介绍了测试。您将了解测试如何影响系统管理员所做的许多任务的最终结果。你还会学到测试是哲学的一个组成部分。

然而,测试不仅仅是关于程序。这也是为了验证我们应该已经解决的问题——无论是由硬件、软件还是用户似乎永无止境的破坏方式引起的——实际上已经解决了。这些问题可能与我们编写的应用或实用软件、系统软件、应用和硬件有关。同样重要的是,测试也要确保代码易于使用,界面对用户有意义。

程序

我之前的一份工作是在思科公司做基于 Linux 的设备测试员。我开发测试计划,编写 Tcl/Expect 代码来实现测试计划,并帮助跟踪失败的根本原因。我喜欢那份工作,从中学到了很多东西。

我在第十章中简单地提到了测试,但是这里有必要提供更多关于我的程序的细节。在编写和测试 shell 脚本时,遵循定义良好的过程有助于获得一致和高质量的结果。我的程序很简单。

  1. 创建测试计划,至少是简单的测试计划。

  2. 在开发之初就开始测试。

  3. 当代码完成时,执行最终测试。

  4. 转向生产并进行更多测试。

创建测试计划

测试是一项艰苦的工作,它需要一个基于需求陈述的设计良好的测试计划。不管情况如何,从测试计划开始。甚至一个非常基本的测试计划也提供了一些保证,测试将是一致的,并且覆盖代码的所需功能。

任何好的计划都包括测试来验证代码做了它应该做的一切。也就是说,如果你输入 X 并点击按钮 Y,你应该得到结果 Z。因此,您编写一个测试来创建这些条件,然后验证 Z 是结果。

最好的计划包括测试来确定代码失败的程度。当我在 1982 年得到我的第一台 IBM 个人电脑时,我艰难地发现了这一点。

PC 在 1981 年 8 月刚刚发布,员工购买直到 1982 年初才开始。没有太多的项目,尤其是针对孩子的。我想给我的小儿子介绍个人电脑,但是找不到合适的,所以我用 BASIC 写了一个小程序,我想他会喜欢的。坦白地说,我甚至不记得它应该做什么。

我用我能想到的所有方法测试了那个程序。它做了它应该做的一切。然后我把电脑交给儿子,走出房间。我还没走多远,他就喊道:“爸爸!它应该这样做吗?”不是的。我问他做了什么,他描述了一些非常奇怪的按键,我说,“你不应该这样做,”并立即意识到这对他来说是多么愚蠢。

我的问题是我没有测试过程序对意外输入的反应。这似乎是所有程序的一个共同问题。但是我永远不会忘记那个特殊的教训。因此,我总是试图包含测试意外输入的代码,然后我测试以确保程序检测到它并正常失败。

测试计划有许多不同的格式。我处理过所有的问题,从在脑子里记下所有的内容,到在一张纸上记下一些笔记,再到一组复杂的表格,这些表格需要对每个测试进行完整的描述,测试哪些功能代码,测试要完成什么,以及输入和结果应该是什么。

作为一个曾经是但现在不是测试人员的系统管理员,我试图采取中间立场。至少有一个简短的书面测试计划将确保从一个测试运行到下一个测试运行的一致性。您需要多少细节取决于您的开发和测试过程的正式程度。

测试计划内容

我使用 Google 找到的所有样本测试计划文档都很复杂,并且是为具有非常正式的开发和测试过程的大型组织设计的。虽然这些测试计划对那些职位名称中带有“测试”的人来说是好的,但是它们真的不适用于系统管理员和我们更加混乱和快速的依赖于时间的工作环境。正如我们工作的大多数其他方面一样,我们需要有创造力。因此,这里有一个简短的列表,列出了您想要考虑包含在您的测试计划中的事情。修改它以适合你的需要。

  • 被测试软件的名称和简短描述。

  • 待测试软件特性描述。

  • 每次测试的开始条件。

  • 每次测试应遵循的程序。

  • 对每项测试的预期结果的描述。

  • 包括为测试负面结果而设计的特定测试。

  • 测试程序如何处理意外输入。

  • 对每项测试的通过或失败的清晰描述。

  • 模糊测试,将在下面描述。

这个简短的列表应该给你一些创建你自己的测试计划的想法。对于大多数系统管理员来说,这应该保持简单和相当不正式。

从头开始测试

我总是在完成可执行的第一部分后就开始测试我的 shell 脚本。无论我是在编写一个简短的命令行程序还是一个可执行文件的脚本,都是如此。

我通常用 shell 脚本模板开始创建新程序,您在实验 10-2 中已经有机会探索过了。我编写帮助过程的代码并测试它。这通常是流程中微不足道的一部分,但它帮助我开始,并确保模板中的东西在一开始就正常工作。此时,很容易修复脚本的模板部分的问题,或者修改它以满足标准模板不能满足的特定需求。

当模板和帮助过程工作时,我继续通过添加注释来创建程序体,以记录满足程序规范所需的编程步骤。现在我开始添加代码来满足每个注释中陈述的需求。这段代码可能需要添加在模板的那个部分中初始化的变量——这现在变成了我们的 shell 脚本。

在这里,测试不仅仅是输入数据和验证结果。这需要一点额外的工作。有时,我会添加一个命令,简单地打印我刚刚编写的代码的中间结果,并进行验证。其他时候,对于更复杂的脚本,我会为“测试模式”添加-t 选项在这种情况下,只有在命令行输入-t 选项时,才会执行内部测试代码。

最终测试

代码完成后,我使用已知的输入产生特定的输出,对所有特性和功能进行完整的测试。我还测试了一些随机输入,看看程序现在是否可以处理意外的输入。

最终测试的目的是验证程序现在已经完成,基本上按预期运行。最终测试的很大一部分是为了确保在开发周期早期工作的功能没有被在周期后期添加或更改的代码破坏。

如果您在向脚本中添加新代码时一直在测试脚本,那么在最后的测试中应该不会有什么意外。错了!最终测试中总会有惊喜。一直都是。期待那些惊喜,并准备好花些时间去修复它们。如果在最终测试中没有发现任何错误,那么就没有必要进行最终测试,不是吗?

生产中的测试

嗯——什么?

直到一个程序投入生产至少六个月后,最有害的错误才会被发现。

—Troutman 的编程假设

是的,生产中的测试现在被认为是正常的和可取的。作为一名测试人员,这看起来确实是合理的。“但是等等!这很危险,”你说。我的经验是,它并不比在专用测试环境中进行广泛而严格的测试更危险。在某些情况下,没有选择,因为没有测试环境,只有生产环境。

我的一份工作就是这样,我负责维护大量为网站生成动态页面的 Perl CGI 脚本。这个庞大组织的电子邮件管理界面的整个网站都运行在一个非常陈旧的戴尔台式机系统上。那是我们的关键服务器。我有一台更旧的戴尔台式机,我可以通过它登录服务器进行编程。这两台电脑都运行早期版本的 Red Hat Linux。

我们不得不做的唯一选择是在中午的时候进行许多关键的修改,然后在生产中进行测试。那是多么有趣啊!

最终,我们获得了几个额外的旧台式机作为开发和测试环境,但在我们做到这一点之前,这是一个棘手的挑战。缺乏运行这个大型电子邮件系统的设备的部分原因是,它最初只是一个部门的小规模试点测试。随着越来越多的部门一听说这件事就要求加入,这件事迅速失去了控制。试点测试从来没有资金支持,通常幸运的是得到了另一个部门的旧的和不需要的设备。

因此,系统管理员对在生产中测试新的或修改过的脚本并不陌生。任何时候一个脚本被转移到产品中,那都成为最终的测试。生产环境本身构成了该测试最关键的部分。测试人员在测试环境中想象出来的任何东西都不能完全复制真实的生产环境。

所谓的生产中测试的新实践只是对我们系统管理员一直以来所知道的事情的认识。最好的测试是生产——只要它不是唯一的测试。

最终测试之后,程序可以进入生产阶段。生产本身总是一个考验。在隔离的开发和测试环境中编写代码并不能代表真实生产环境中遇到的情况。

无论脚本写得多好,测试得多好,总会有新的 bug 出现在产品中。正如 Troutman 的假设所说,最有害的错误在程序投入生产后的相当长一段时间内不会被发现,并且每个人都认为结果总是正确的。最有害的错误不是那些导致程序崩溃的错误;他们是悄悄导致不正确结果的人。

继续检查脚本产生的结果,即使它已经投入生产。寻找下一个 bug,你最终会找到的。

模糊测试

这是我第一次听到时引起我翻白眼的又一个流行语。我了解到它的本质含义很简单——让某人敲击键盘,直到某件事情发生,然后看看程序处理得如何。但是实际上不止如此。

模糊测试有点像我儿子用他的随机输入在不到一分钟的时间里破坏了我的代码。大多数测试计划利用非常具体的输入来生成具体的结果或输出。不管测试是正面的还是负面的成功结果,它仍然是受控的,并且输入和结果是指定的和预期的,例如针对特定故障模式的特定错误消息。

模糊测试是关于处理测试所有方面的随机性,例如开始条件、非常随机和意外的输入、选择的选项的随机组合、低内存、与其他程序的高水平 CPU 争用、测试中程序的多个实例,以及您能想到的应用于测试的任何其他随机条件。

我试着从一开始就做一些模糊测试。如果 bash 脚本不能在早期阶段处理显著的随机性,那么随着我们添加更多的代码,它也不可能变得更好。这也是捕捉这些问题并在代码相对简单时修复它们的好时机。在完成的每个阶段进行一点模糊测试也有助于在问题被更多代码掩盖之前找到问题。

代码完成后,我喜欢做一些更广泛的模糊测试。总是做一些模糊测试。我确实对我遇到的一些结果感到惊讶。测试预期的事情很容易,但是用户通常不会用脚本做预期的事情。

自动化测试

测试可以是自动化的,但是我们作为系统管理员所做的大部分工作都有内在的时间压力,不允许花时间编写代码来测试我们的代码。这些压力是我们写的大多数代码又快又脏的原因。所以我们匆忙地编写代码并测试它。

使用像 Tcl/Expect 这样的工具为我们的 shell 脚本编写一个复杂的测试套件是可能的。作为一名系统管理员,我从来没有时间做那么正式的事情。作为一名系统管理员,我做过的最自动化的事情就是编写一个非常短的脚本,通过一组命令来验证测试脚本的一些关键方面。大多数时候,我在程序完成的每一步都进行手工测试。使用 bash 历史可以作为一种合理的替代,并且至少提供一些半自动测试。

我在思科担任测试人员时,使用 Tcl/Expect 编写了很多测试。我的任务是编写将被之前编写的测试床调用的模块。我编写的 Tcl/Expect 代码本来可以作为独立的测试运行,但是测试平台提供了一个框架,该框架聚合了来自各个测试的所有结果,并生成了一组很好的报告,使我们能够看到我们在将错误修复应用于代码方面取得了多大的进展。

有许多商业测试套件可用。许多都非常昂贵,并不特别适合系统管理员使用,因为学习它们需要付出努力,准备测试也需要时间。

编写 Tcl/Expect 程序非常耗时,但在开发大型代码库时,它会非常有用。我最喜欢的 Tcl/Expect 的书是 Exploring Expect1,里面包含了大量关于 Tcl 的信息。维基百科上有一篇关于软件测试的优秀文章,有很多更深入的链接。

尝试一下

在实验 10-2 中,你对 shell 脚本模板的副本做了一些修改。在该实验的每一步,您都测试了所做更改的结果,因此您已经熟悉了基本的 SysAdmin 开发过程。本章中的实验将使用该过程来开发和测试一个程序,该程序将列出一些关于您的 Linux 主机的有趣信息和统计数据。最后,我们将会有一个经过充分测试的相当长的脚本。该脚本的典型输出如图 11-1 所示。

img/462716_1_En_11_Fig1_HTML.png

图 11-1

由您将在本章的实验中创建的 shell 脚本生成的 MOTD 示例

我将这个脚本作为 cron 作业运行,每天生成一个报告,并存储为/etc/motd,这是当天消息文件。每当有人使用远程终端或虚拟控制台登录时,就会显示 MOTD。

在我们开始编码之前,我们需要首先创建一组需求,然后创建一个简单的测试计划。

MOTD 脚本的要求

一组简单的要求将有助于我们设计程序,并针对我们希望包含的特定功能提供帮助。这些要求应该工作得很好,但要为创造性留有余地。

  • 所有的输出都发送到 STDOUT 和 STDERR,以便根据需要进行重定向。

  • 提供打印脚本发布版本的选项。

  • 以令人满意的格式打印下列数据。

    • 带有当前日期的标题

    • 主机名

    • 机器类型–虚拟机或物理机

    • 主机硬件架构 X86_64 或 i386

    • 主板供应商和型号

    • CPU 型号和超线程状态

    • 内存容量(GB)

    • 交换空间的大小(GB)

    • Linux 的安装日期

    • Linux 发行版

    • 内核版本

    • 磁盘分区信息

    • LVM 物理卷信息

  • 包括描述代码的注释。

  • 应该不需要选项来产生期望的输出。

这似乎是一个很长的列表,但是与我看到的一些需求集相比,这是相当短的。我根据一组类似的需求创建了最初的 bash 脚本。我们在第十章“总是使用脚本”中创建的脚本模板已经有了可以帮助满足这些需求的代码。

有人可能对这个列表的唯一问题是“取悦”这个词。谁知道对于使用这个脚本的人来说,什么是令人愉快的,什么是令人不愉快的。所以对于这个实验来说,取悦将是我所说的。在其他环境中,可能需要许多页的需求来定义输出的显式格式。在 SysAdmin 环境中,取悦通常是对我们或任何要求编写程序的人有用的。

MOTD 脚本的测试计划

我们的测试计划简单明了。

  • 验证 help (-h)选项显示了正确的帮助信息。

  • 验证 GPL (-g)选项显示 GPL 许可证声明。

  • 验证要求中规定的所有输出数据均已生成。

  • 验证执行测试的系统的所有打印输出是否正确。

  • 通过与其他来源进行比较,验证数字输出的值是否正确。由于任何正在运行的计算机的动态性质,这些数字中的一些可能会在运行之间以及与其他来源进行比较时发生变化,但是它们应该相当接近。

  • 确保不正确的选项选择会产生相应的错误代码。

  • 如果可能,在多个系统上进行测试,包括物理硬件和虚拟机,以验证不同条件下的正确结果。包括英特尔、AMD 以及 ARM 硬件。

  • 如果可能的话,用多个 Linux 发行版进行测试。

这个简单的测试计划是我们在测试脚本时需要知道的一切。我们知道需要检查的输出,因为它们是在程序需求中定义的。

最后两项在学习环境中可能是不可能的,但它总是需要考虑的。不同的环境应该用这个脚本产生不同的结果。在我们实际的开发/测试环境之外进行测试有助于确保其他环境的逻辑和结果是准确的。生产中的测试可以帮助解决这个问题。

开发脚本

记住,对于系统管理员来说,开发也意味着测试。由于创建完整的脚本需要大量的工作,我将它分成了一系列的实验,在这些实验中,每个实验将开发和测试一段代码来满足部分或全部特定需求。

注意

如果您不清楚一些命令是如何工作的,尤其是管道的各个阶段是做什么的,您应该研究一下它们。首先查看管道中每个命令的手册页,了解它的作用。然后构建管道——一次一个命令阶段,以查看结果。当我刚开始做系统管理员的时候,这对于我理解看起来复杂的代码非常有帮助。我仍然依赖这种方法来帮助我理解一些代码是如何工作的。

基础知识

让我们从基础开始——从修改后的模板复制脚本,在内部更改脚本名称,添加简短描述,并更改帮助过程以匹配我们程序的功能。

实验 11-1

使用 mymotd 的新名称制作 test1.sh 的副本。您可以以 root 用户或学生用户的身份编辑这个新脚本 mymotd,但是在测试时它必须以 root 用户的身份运行。我建议以非 root 用户的身份编辑 shell 脚本。打开两个终端会话并 su–root 其中一个会话。在终端会话中,以用户 student 的身份在您最喜欢的编辑器中打开 mymotd 脚本。

在我们编辑脚本时,请务必经常保存您的工作。

首先,让我们更改标题注释中的脚本名称,并添加脚本的简短描述。结果应该是这样的。

#!/bin/bash
###########################################################################
#                                 mymotd                                  #
#                                                                         #
# This bash shell extracts various interesting bits of information about  #
# the Linux host and the operating system itself. It prints this data to  #
# STDOUT in a nice looking format. The results can also be redirected to  #
# the/etc/motd file to create an informational message of the day.        #
#                                                                         #
# Change History                                                          #
# 01/08/2018  David Both    Original code.                                #
#                                                                         #
#                                                                         #
###########################################################################

请确保将更改历史记录中的第一行设置为当前日期和您的姓名。

接下来,让我们学习帮助程序。这里所需要的只是添加一个简短的描述、一行描述命令语法的文字和一个可能选项的列表。

########################################################################
# Help                                                                 #
########################################################################
Help()
{
   # Display Help
   echo "                  mymotd"
   echo "Generate an MOTD that contains information about the system"
   echo " hardware and the installed version of Linux."
   echo
   echo "Syntax:  mymotd [-g|h|v|V]"
   echo "options:"
   echo "g     Print the GPL license notification."
   echo "h     Print this Help."
   echo "v     Verbose mode."
   echo "V     Print software version and exit."
   echo
}

让我们进行第一次测试。在作为 root 的终端会话中,将/home/student 设为 PWD,这是新代码所在的位置。现在,每次运行时使用三个选项之一来运行程序;-h 测试帮助工具,-g 测试打印 GPL 语句,-x 测试无效选项。

[root@testvm1 student]# ./mymotd -h
                  mymotd
Generate an MOTD that contains information about the system
 hardware and the installed version of Linux.

Syntax:  mymotd [-g|h|v|V]
options:
g     Print the GPL license notification.
h     Print this Help.
v     Verbose mode.
V     Print software version and exit.

[root@testvm1 student]# ./mymotd -g

#############################################################################
#  Copyright (C) 2007, 2016  David Both                                      #
#  Millennium Technology Consulting LLC                                      #
#  http://www.millennium-technology.com                                      #
#                                                                            #
#  This program is free software; you can redistribute it and/or modify      #
#  it under the terms of the GNU General Public License as published by      #
#  the Free Software Foundation; either version 2 of the License, or         #
#  (at your option) any later version.                                       #
#                                                                            #
#  This program is distributed in the hope that it will be useful,           #
#  but WITHOUT ANY WARRANTY; without even the implied warranty of            #
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             #
#  GNU General Public License for more details.                              #
#                                                                            #
#  You should have received a copy of the GNU General Public License         #
#  along with this program; if not, write to the Free Software               #
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA #
##############################################################################

[root@testvm1 student]# ./mymotd -x
ERROR: Invalid option
                  mymotd
Generate an MOTD that contains information about the system
 hardware and the installed version of Linux.

Syntax: Syntax:  mymotd [-g|h|v|V]
options:
g     Print the GPL license notification.
h     Print this Help.
v     Verbose mode.
V     Print software version and exit.

Program terminated with error ID 10T

如果你还能想到任何需要执行的测试,比如模糊测试,现在就去做吧。如果您在测试时发现任何问题,现在就修复它们,然后再次测试。

添加健全性检查

在第十章中,我们注释掉了健全性检查,以确保脚本由 root 运行,因此我们需要恢复它。我们还将添加一项检查,以确保脚本在 Linux 主机上运行。作为一个 bash 脚本,它可以与各种 Unix 系统兼容,但是一些特定于 Linux 的功能会失败。

实验 11-2

首先从根检查中移除注释散列。现在看起来像这样。

#---------------------------------------------------------------------------
# Check for root.

if [ `id -u` != 0 ]
then
   echo ""
   echo "You must be root user to run this program"
   echo ""
   Quit 1
fi

现在做两个快速测试。以 root 用户身份运行程序,以确保 root 用户仍然可以使用该程序。

[root@testvm1 student]# ./mymotd
        total       used        free      shared  buff/cache   available
Mem:  4046060     254392     2947324         984      844344     3532200
Swap:  4182012         0     4182012

以学生用户的身份运行程序,验证非根用户是否收到错误。

[student@testvm1 ~]$ ./mymotd

You must be root user to run this program

如果您发现任何错误,请在我们继续之前修复它们。

让我们添加第二个健全性检查,以确保这个程序在 Linux 系统上运行。该测试使用uname命令返回操作系统名称。

在检查 root 用户的代码下面添加以下代码。

#---------------------------------------------------------------------------
# Check for Linux

if [[ "$(uname -s)" != "Linux" ]]
then
   echo ""
   echo "This script runs on Linux only -- OS detected: $(uname -s)."
   echo ""
   Quit 1
fi
#---------------------------------------------------------------------------

我们只能测试积极的结果——也就是说,我们在 Linux 上运行——而不能测试消极的结果,除非我们在非 Linux 主机上测试。但至少让我们做阳性测试。只需运行该命令并验证您没有得到任何错误。

版本号

所有程序都应该有一个版本号。这个脚本已经有一个指定版本号的变量,但是它是脚本模板的版本,而不是我们正在工作的新程序的版本。让我们设置我们的版本号。由于这是在开发过程的早期,它不会是一个完整的版本级别。

我喜欢用三个两位数的部分来表示我的版本号,以保证灵活性。所以让我们从版本 00.01.00 开始,因为它表示代码远未准备好。随着我们越来越接近释放到野外,这个数字将上升,第一次完全释放将是 01.00.00。

实验 11-3

首先在代码的变量部分设置版本号。让我们也删除这个部分中的 RC(返回代码)行。这就是我们目前在变量部分所拥有的,但是当我们在本章的其余实验中进行时,更多的变量将被添加到这里。

img/462716_1_En_11_Figa_HTML.png

# Set initial variables
badoption=0
error=0
verbose=0
Version=00.01.00

现在我们甚至测试这一点点新代码,尽管它看起来简单无害。

[root@testvm1 student]# ./mymotd -V
Version = 00.01.00

但是您还没有真正完成测试。您应该对以前编码的函数做一些额外的测试,以确保它们没有被新代码破坏。

至此,基础工作已经完成。我们有一个显示帮助、GNU 许可证声明、执行一些健全性检查和显示版本号的部分脚本。我们不需要向 case 语句添加任何选项,因为我们需要的一切都已经存在了。

主体

现在我们可以添加代码的主体来收集我们想要的数据并显示出来。我们将按照所需的顺序收集数据,并在进行过程中将其打印到 STDOUT。这将使早期测试变得容易。

实验 11-4

首先,删除我们用来从脚本中提供一些输出的free命令。

现在让我们给程序添加一些代码。在程序主体中,在Quit函数调用之前,添加执行这些函数的代码。我们还添加了一些代码来打印我们收集的数据。这提供了一个简单的测试,同时也产生了预期的结果。

这段代码现在看起来像这样。

########################################################################
########################################################################
# The main body of your program goes here.
########################################################################
########################################################################
# Get the date
Date=`date`
# Get the hostname
host=`hostname`
########################################################################
# Start printing the data using printf to make it pretty               #
########################################################################
printf "#############################################################\n"
printf "# MOTD for $Date\n"
printf "# HOST NAME: \t\t$host \n"

在 printf 语句中,\t 在输出中插入一个制表符,\n 是一个换行符。当我第一次编写这个脚本的时候,我花了一段时间来正确格式化整个输出。你有我的知识优势,这是我先做的。

将以下变量添加到初始化部分。

host=""
Date=""

现在让我们运行程序来测试这些结果。

[root@testvm1 student]# ./mymotd
########################################################################
# MOTD for Sat Jan 13 12:14:34 EST 2018
# HOST NAME:            testvm1

这对于我的虚拟机主机来说看起来是正确的。因此,在五行活动代码中——不包括注释行——我们已经开始编写和测试我们的脚本。

到目前为止,代码非常适合这样的开发/测试周期。我们添加代码来获取一些数据,并添加一些代码来打印这些数据。事情会变得更加复杂。

实验 11-5

现在,我们想要添加一些代码来确定主机是物理机还是虚拟机,并且我们还想要一些关于主板的信息。我们需要这样做的 Linux 命令dmidecode并不总是被安装,所以我们需要确保它存在于我们的主机上。最简单的方法就是尝试安装它。如果不存在,它将被安装。以 root 用户身份执行此操作。

[root@testvm1 ~]# dnf install -y dmidecode

dmidecode 实用程序(其中 dmi 代表桌面管理界面)可以访问由系统管理 BIOS (SMBIOS)维护的硬件数据表。例如,使用以下命令检索有关主板的数据。“-t”表示“类型”,类型 2 是主板。dmidecode 手册页列出了所有可用的数据类型。

[root@david ~]# dmidecode -t 2
# dmidecode 3.1
Getting SMBIOS data from sysfs.
SMBIOS 3.0.0 present.

Handle 0x0002, DMI type 2, 15 bytes
Base Board Information
        Manufacturer: ASUSTeK COMPUTER INC.
        Product Name: TUF X299 MARK 2
        Version: Rev 1.xx
        Serial Number: 170807951700403
        Asset Tag: Default string
        Features:
                Board is a hosting board
                Board is replaceable
        Location In Chassis: Default string
        Chassis Handle: 0x0003
        Type: Motherboard
        Contained Object Handles: 0

上面 dmidecode 命令的结果来自我的主工作站,而不是测试虚拟机。

注意这个 mymotd 脚本是为 Intel 处理器和 Fedora Linux 编写的。它可以与其他芯片和发行版一起工作,但是某些代码部分的结果可能不正确。您可能想自己做一些实验,让这些部分在您的环境中工作。

现在我们已经安装了 dmidecode 工具,我们可以继续添加到我们的程序中。首先,我们想知道主机是虚拟机还是物理机。

实验 11-6

现在让我们添加一些代码来告诉我们主机是否是 VM 的物理机器。我们为此需要的信息是每次引导时启动的 dmesg 日志缓冲区的一部分。我们只需要搜索适当的文本字符串。将下面的代码添加到上一个实验的日期和主机名之后。

########################################################################
# Is this a VirtualBox, VMWare, or Physical Machine.                   #
########################################################################
if dmesg | grep -i "VBOX HARDDISK" > /dev/null
then
   MachineType="VM running under VirtualBox."
elif dmesg | grep -i "vmware" > /dev/null
then
   MachineType="VM running under VMWare."
else
   MachineType="physical machine."
fi
printf "# Machine Type: \t$MachineType\n"

将 MachineType 变量添加到脚本的变量部分。然后测试这个新代码。

[root@testvm1 student]# ./mymotd
#######################################################################
# MOTD for Sun Jan 14 09:49:29 EST 2018
# HOST NAME:            testvm1
# Machine Type:         VM running under VirtualBox.

这对于我的测试虚拟机来说也是正确的。您的结果可能不同。

了解主机的架构,即它是 32 位还是 64 位的,通常会有所帮助。下一个实验添加了一些代码来确定这一点。

实验 11-7

添加以下三行代码,确定主机的架构是 32 位还是 64 位。

# Get the host physical architecture
HostArch=`echo $HOSTTYPE | tr [:lower:] [:upper:]`
printf "# Host architecture: \t$HostArch\n"

将 HostArch 添加到变量部分并进行测试。

[root@testvm1 student]# ./mymotd
#######################################################################
# MOTD for Sun Jan 14 10:43:05 EST 2018
# HOST NAME:            testvm1
# Machine Type:         VM running under VirtualBox.
# Host architecture:    X86_64

这表明我们的虚拟机是 64 位的,这是正确的。如今大多数主机都是 64 位的,只有少数 32 位的还在,比如我的华硕 EeePC。然而,一些小型的单板计算机(SBC)仍然是 32 位的。

即使在虚拟机中,主板信息也很有趣,所以现在让我们来获取这些数据。不用拆开电脑就能获得这些信息的能力非常有用。

实验 11-8

以下代码添加在主机架构代码之后,并使用 dmidecode 提取有关主板的信息。

########################################################################
# Get the motherboard information                                      #
########################################################################
MotherboardMfr=`dmidecode -t 2 | grep Manufacturer | awk -F: '{print $2}' | sed -e "s/^ //"`
MotherboardModel=`dmidecode -t 2 | grep Name | awk -F: '{print $2}' | sed -e "s/^ //"`
printf "# Motherboard Mfr: \t$MotherboardMfr\n"
printf "# Motherboard Model: \t$MotherboardModel\n"
printf "#------------------------------------------------------------\n"

注意,获得主板信息的代码行都包含在上面的清单中。请确保在一行中输入它们。我还添加了一行来打印分隔符,以便将其与下一部分数据分开。

将两个新变量添加到变量初始化部分。

MotherboardMfr=""
MotherboardModel=""

现在测试程序以验证结果。

[root@testvm1 student]# ./mymotd
#######################################################################
# MOTD for Sun Jan 14 10:57:05 EST 2018
# HOST NAME:            testvm1
# Machine Type:         VM running under VirtualBox.
# Host architecture:    X86_64
# Motherboard Mfr:      Oracle Corporation
# Motherboard Model:    VirtualBox
#----------------------------------------------------------------------

结果的第一部分对虚拟机来说非常好,非常正确。它看起来很好,容易阅读。

至此,我们已经提取并打印了一些关于主机的一般信息。现在我们想添加一个部分,向我们展示一些关于 CPU 本身的信息。这些信息大部分位于/proc 文件系统中。很多时候它不能作为一个单一的、格式良好的数据点,所以我们需要使用我们的 Linux 工具来提取我们想要的东西并适当地格式化它。

实验 11-9

我们从/proc 文件系统中获取 CPU 型号信息开始获取 CPU 信息。这几行代码就是这样做的,所以将它们添加到程序的末尾,正好在Quit函数调用的上面。

########################################################################
# Get the CPU information                                              #
########################################################################
# Starting with the specific hardware model
CPUModel=`grep "^model name" /proc/cpuinfo | head -n 1 | cut -d : -f 2 | sed -e "s/^ //"`CPUModel
printf "# CPU Model:\t\t$CPUModel\n"

给 CPUModel 变量赋值的那一行是换行的,所以一定要在程序中的一行中输入。将 CPUModel 变量添加到 variables 部分并进行测试。

[root@testvm1 student]# ./mymotd
#######################################################################
# MOTD for Sun Jan 14 15:54:24 EST 2018
# HOST NAME:            testvm1
# Machine Type:         VM running under VirtualBox.
# Host architecture:    X86_64
# Motherboard Mfr:      Oracle Corporation
# Motherboard Model:    VirtualBox
#----------------------------------------------------------------------
# CPU Model:            Intel(R) Core(TM) i9-7960X CPU @ 2.80GHz

我们代码的这个最新添加产生了关于我们系统中安装的 CPU 的良好信息。

让我们找到一些额外的 CPU 信息,例如 CPU 的数量和封装信息——每个芯片有多少个内核——以及我们是否有超线程技术。

实验 11-10

该实验收集了一些 CPU 数据,然后对 CPU 的封装方式以及 CPU 是否能够超线程进行了一些有根据的判断。

首先,让我们在初始化部分添加一些新变量。

PhysicalChips=0
Siblings=0
HyperThreading="No"
CPUSpeed=""
NumCores=0
Package=0
Arch=""
CPUdata=""
CPUArch=""

这里有很多代码,因为它们都是相关的,并且需要完整的测试才能成功。进入时要小心。特别要注意下面清单中的代码行。

#############################################################################
# Get some CPU details.                                                     #
#############################################################################
# Get number of actual physical chips
PhysicalChips=`grep "^physical id" /proc/cpuinfo | sort | uniq | wc | awk '{print $1}'`
if [ $PhysicalChips -eq 0 ]
then
   let PhysicalChips=1
fi
# Get the total number of cores
CPUs=`cat /proc/cpuinfo | grep "cpu cores" | head -n 1 | cut -d : -f 2 | sed -e "s/^ //"`

# Do we have HyperThreading
Siblings=`grep "^siblings" /proc/cpuinfo | head -n 1 | cut -d : -f 2 | sed -e "s/^ //"`
if [ $Siblings -gt $CPUs ]
then
   # Yes we have HyperThreading
   HyperThreading="Yes"
fi

# Now Cores per CPU
# We are assuming each package has the same number of cores – the next line is wrapped
NumCores=`grep "^cpu cores" /proc/cpuinfo | sort | uniq | awk -F: '{print $2}' | sed -e "s/^ //"`
case "$NumCores" in
   1) Package="Single Core";;
   2) Package="Dual Core";;
   4) Package="Quad Core";;
   6) Package="Six Core";;
   8) Package="Eight Core";;
  12) Package="Twelve Core";;
  16) Package="Sixteen Core";;
  18) Package="Eighteen Core";;
  20) Package="Twenty Core";;
  24) Package="Twenty-four Core";;
  26) Package="Twenty-six Core";;
  28) Package="Twenty-eight Core";;
  30) Package="Thirty Core";;
  32) Package="Thirty-two Core";;
   *) Package="Single Core"
      NumCores=1;;
esac

# Get the CPU architecture which can be different from the host architecture
CPUArch=`arch`
# Now lets put some of this together to make printing easy
CPUdata="$PhysicalChips $Package $CPUArch"

# Get the CPU speed – The next line is wrapped
CPUSpeed=`grep "model name" /proc/cpuinfo | sed -e 's/.*\( [0-9]*.[0-9]*[GM]Hz\)/\1/' -e 's/^ *//g' | uniq`
# Let's print what we have
printf "# CPU Data:\t\t$CPUdata\n"
printf "# HyperThreading:\t$HyperThreading\n"
printf "#-----------------------------------------------------------------\n"

输入代码时,请务必仔细检查。然后我们再次测试。

[root@david development]# ./mymotd
#######################################################################
# MOTD for Mon Jan 15 15:35:09 EST 2018
# HOST NAME:            david
# Machine Type:         physical machine.
# Host architecture:    X86_64
# Motherboard Mfr:      ASUSTeK COMPUTER INC.
# Motherboard Model:    TUF X299 MARK 2
#----------------------------------------------------------------------
# CPU Model:            Intel(R) Core(TM) i9-7960X CPU @ 2.80GHz
# CPU Data:             1 Sixteen Core x86_64
# HyperThreading:       Yes
#----------------------------------------------------------------------

我们的前两部分已经完成。下一节详细介绍系统内存要简单一些。

实验 11-11

这个实验添加代码来显示一些内存统计数据。这里我们再次使用现成的资源。

/proc/meminfo 文件有我们需要的数据,但它是以 KB 为单位的,为了清楚起见,我们希望它以 GB 为单位。为此,我们将以下过程添加到脚本的过程部分。Bash 没有像样的数学能力,所以这段代码使用 bc calculator 命令,它有自己独特的语法。

########################################################################
# Convert KB to GB                                                     #
########################################################################
kb2gb()
{
   # Convert KBytes to Giga using 1024
   # first convert the input to MB
   echo "scale=3;$number/1024/1024" | bc
}

下面的代码应该添加在脚本末尾的最后一个退出过程调用之前,它从/proc/meminfo 文件中获取数据,然后将数字转换为 GB。然后打印结果。

############################################################################
# Memory and Swap data                                                     #
############################################################################
# Get memory size in KB.
number=`grep MemTotal /proc/meminfo | awk '{print $2}'`
# Convert to GB
mem=`kb2gb`
# Get swap size in KB
number=`grep SwapTotal /proc/meminfo | awk '{print $2}'`
# Convert to GB
swap=`kb2gb`

printf "# RAM:\t\t\t$mem GB\n"
printf "# SWAP:\t\t\t$swap GB\n"
printf "#-----------------------------------------------------------------\n"

我们需要在变量部分添加新的变量。

number=0
mem=0
swap=0

现在又到了考验的时候了。

[root@david development]# ./mymotd
#######################################################################
# MOTD for Mon Jan 15 21:56:40 EST 2018
# HOST NAME:            david
# Machine Type:         physical machine.
# Host architecture:    X86_64
# Motherboard Mfr:      ASUSTeK COMPUTER INC.
# Motherboard Model:    TUF X299 MARK 2
#----------------------------------------------------------------------
# CPU Model:            Intel(R) Core(TM) i9-7960X CPU @ 2.80GHz
# CPU Data:             1 Sixteen Core x86_64
# HyperThreading:       Yes
#----------------------------------------------------------------------
# RAM:                  62.586 GB
# SWAP:                 14.902 GB
#----------------------------------------------------------------------

在 testvm1 主机上,测试结果如下所示。

[root@testvm1 student]# ./mymotd
#######################################################################
# MOTD for Mon Jan 15 21:57:32 EST 2018
# HOST NAME:            testvm1
# Machine Type:         VM running under VirtualBox.
# Host architecture:    X86_64
# Motherboard Mfr:      Oracle Corporation
# Motherboard Model:    VirtualBox
#----------------------------------------------------------------------
# CPU Model:            Intel(R) Core(TM) i9-7960X CPU @ 2.80GHz
# CPU Data:             1 Quad Core x86_64
# HyperThreading:       No
#----------------------------------------------------------------------
# RAM:                  3.854 GB
# SWAP:                 7.999 GB
#----------------------------------------------------------------------

如果您有其他要测试的主机,您应该这样做来验证结果对于其他情况是否正确。

虽然根据我在本章开始时为它创建的需求集,这个脚本还没有完成,但我认为我们已经走得足够远了,你可以自己完成了。所以,我把这个脚本的完成作为一个练习留给你们,全世界的系统管理员。

如果您喜欢这段代码,但是需要一些额外的指导,或者只是不关心如何完成它,那么可以从以下网址下载这段 bash 脚本的完整代码:

https://github.com/Apress/linux-philo-sysadmins/tree/master/Ch11

无论您选择哪种方法,您都可以随意修改这些代码,以满足自己的需求。

修复脚本

很多时候我们需要修复现有的脚本。我最近写的一个剧本出了点问题。幸运的是,在造成任何损害之前,我就意识到了这个问题。正如您在 mymotd 脚本中看到的,我喜欢提供适量的注释来帮助我在以后解决问题。如果我不必在每次需要修改代码以修复它或添加新功能时都确定代码是如何工作的,这就容易多了。

正如我在本章的“生产测试”一节中提到的,有时候修改一个脚本需要从最基础的开始。在这种情况下,我添加了注释,使代码更容易阅读,并解决了我遇到的任何明显的错误。由于前面提到的情况,我们仅有的环境就是生产。每当我对代码进行哪怕是很小的修改时,我都必须进行测试,以确保修改按预期进行,并且没有任何可能依赖于修改后的代码的东西被破坏。

在最近的一个案例中,是我自己的注释相当好的代码工作不太正常。在这种情况下,在 u 盘上创建了一个 MP3 文件,其文件名包含 MMDDYYYY-X 格式的日期,其中 X 是序列号。我的程序将文件的名称改为包含直接取自文件名的日期,但重新排列为 YYYYMMDD-X,然后将文件复制到服务器。如果记录设备在一天内创建了两个文件,它们具有相同的日期,但是这些文件通过序列号来区分。我的脚本旨在保持它们的顺序,这样它们就可以按照创建的顺序进行排序。

当一个文件被创建并传输,然后同一天又创建了另一个文件时,问题就出现了。它将具有与第一个文件相同的文件名,并在服务器上覆盖它。

为了解决这个问题,我决定使用文件的时间戳以 YYYYMMDD-HHMMSS 的格式创建自己的时间戳,并在新文件名中消除序列号的使用。

这个特殊的修复是一个简单的问题,做了一个小的改动,修正了我以前没有考虑到的边缘情况。测试很容易;只需创建边界条件并运行程序,同时确保当边界条件不存在时程序仍能正确运行。我手头有几个样本 MP3 文件用于测试,只需将它们复制到 USB 记忆棒即可。

摘要

为系统管理员测试代码很像写代码——快速且不严谨。我希望最后一点听起来比“反复无常”更好快速编写代码通常意味着快速测试代码。这并不意味着测试 shell 脚本需要随机进行。“尽早测试,经常测试”是让测试成为编码的一部分的一个很好的口号。随着这一原则的应用,测试 shell 脚本的任务变成了第二天性,并且成为了编写脚本过程中不可或缺的一部分。

我发现本章中的 mymotd 脚本是重写我的原始版本的一个很好的借口。我在 2007 年写了这个脚本,我对硬件架构和 Linux 中的报告的更好理解帮助了我这个版本。我对 Linux 工具的了解,不管是新的还是新的,都有所提高,并给了我更多的灵活性来简化这个新脚本。

请注意,变量名都是对正在执行的任务有意义的名称。查看变量名并了解它应该包含的数据类型是可能的。这也有助于简化测试。

十二、使用常识命名

我在本书的几个地方提到过,打字不是我的强项,懒惰的系统管理员会尽一切可能减少打字。我对此很认真。这个原则对此进行了扩展,但是它不仅仅是减少我需要做的打字量。这也是关于脚本的可读性和命名的事情,以便他们更容易理解。

最初的 Unix 哲学原则之一——尽管是较次要的原则之一——是始终使用小写字母并保持名称简短。这是一个令人钦佩的目标,但在系统管理员的世界里却不那么容易实现。在许多方面,我自己的信条似乎是对原文的彻底驳斥。但是,最初的版本面向不同的受众,而这一版本面向具有不同需求的系统管理员。

我认为最终的目标是创建易读易懂的脚本,以便于维护。然后使用其他简单的脚本和 cron 作业来自动运行这些脚本。保持脚本名称的合理简短还可以减少从命令行执行这些脚本时的输入,但是当从另一个脚本或作为 cron 作业启动它们时,这几乎没有关系。

脚本和程序名称

dbu 这个程序名对你有什么意义吗?在你成为 Linux 极客之前,dd 这个程序名字对你来说有什么意义吗?这两个问题的答案可能都是否定的。虽然 dd 是一个常见的 GNU 实用程序“磁盘转储”,但 dbu 是我自己创建的一个 shell 脚本。这个名字对你来说毫无意义,但对我来说,它意味着大卫的备份。它很容易输入,一旦你知道它的意思,你就会记住它。

如果你研究了所有最初的 GNU 核心工具,你会发现它们的名字都很短——很多都是两三个字母。这很好,但是总共有成千上万的 Linux 命令,只有这么多有意义的简短组合。任何名称的一个属性应该是它与程序或脚本的目的有某种有意义的联系。

我们在第十一章中为脚本使用的名称 mymotd 稍长一些,但也比使用较短名称的情况更有意义。我们可以使用 davesmotd、dmotd、mmotd、davesMOTD、dMOTD 或任何其他相对有意义的名称。一些名字中的大写字母确实有助于更容易辨别脚本的功能,但是它们确实使在命令行上键入名字有点困难。

和我的“dbu”程序一样,仍然有一些空间用于非常短的名字。例如,有一个名为“mtr”的非常好的程序,它是旧的 traceroute 程序的交互式替代程序。mtr 程序维护一个活动的、连续的 traceroute,动态显示每一跳丢失的数据包数量,如果数据包由于某种原因被重新路由,还可以显示多条路由。非常有趣和有用。

mtr 计划最初被命名是因为一个叫 Matt Kimball 的人编写并维护了它。因此,这是“马特的跟踪路线。”马特停止支持后,罗杰·沃尔夫接手。它仍被命名为 mtr,但现在代表“我的追踪路线”

用非常短的名字命名脚本可能是一个挑战,因为许多现有的短字母组合已经被采用。在给一个脚本命名时,我总是试图做一些研究,以确保它不会对我电脑上已经安装的可执行文件造成问题。我通常用which命令做一个快速检查,如实验 12-1 所示。

实验 12-1

因为我们没有将 mymotd 脚本复制到任何标准的可执行路径位置,所以当我们使用下面的命令时,它应该不会出现。

[root@david ~]# which mymotd
/usr/bin/which: no mymotd in (/usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin)

which命令显示它在试图定位指定的可执行文件时搜索的路径。

不管它的完成状态如何,将 mymotd 脚本复制到/usr/local/bin,这是 Linux FHS 中存储本地创建的可执行文件的正确位置。然后再次运行which命令。

[root@testvm1 student]# cp mymotd /usr/local/bin
[root@testvm1 student]# which mymotd
/usr/local/bin/mymotd

我们已经确定当前没有安装可能导致我们为文件选择的名称出现问题的可执行文件,然后我们将可执行文件复制到适当的位置。

不是所有的冲突都会在你第一次写新剧本的时候出现。检查完安装的程序后,我还会进行谷歌搜索。我试图找到任何与我的程序有命名冲突的信息。较长的名字冲突的可能性要小得多,但是它们真的不需要太长。

我曾经遇到过这样一种情况,问题出现得很晚。我试图用yum,安装一个新程序,和它的继任者dnf一样,它是rpm命令的包装器。我收到一个错误,指出需要从新的 RPM 包中安装的一个文件与另一个包中同名的文件冲突。

这不是我的一个脚本,而是另一对冲突的 rpm。我能够通过删除已经安装的 RPM 来消除冲突,因为不再需要它了。

这种类型的冲突应该非常罕见。即使文件具有相同的名称(这首先是不太可能的),只要它们位于不同的目录中,它们在安装过程中就不会冲突。但是,如果它们都是可执行文件,并且位于\(PATH 的不同目录中,则位于\)PATH 中列出的第一个目录中的文件将由命令运行。要运行另一个,您需要使用完全限定的路径名来确保运行正确的程序。

像这样的潜在冲突,命名脚本可能有点棘手。我喜欢使用稍微长一点的脚本名,比如 4 到 8 个或 10 个字符,是为了防止命名冲突。我有时会在我的脚本名称中添加一个大写字母,以帮助它脱颖而出,并在一个长列表中帮助澄清名称。

这里的底线是名称应该容易记忆和有意义——对您和其他系统管理员来说,容易键入,并且容易在列表中找到。这些是我个人的标准。你可以有其他的,这是非常好的。请记住,其他系统管理员可能有一天需要与您的脚本所在的主机一起工作。

变量

当我在 1981 年购买我的第一台 IBM 个人电脑时,我订购了最大 64KB 的主板 1 ,而最便宜的型号只有 16K。这不是一个很大的工作空间。BASIC 包含在 PC 的一个板载 ROM 中,是当时许多学习编程的人的一个很好的选择。

因为空间有限,所以在用 BASIC 编写程序时节约内存是很重要的。我没有把它做得看起来很像缩进循环和子程序,因为每个制表符或空格占用了一个字节的内存,而这个内存可能是一些重要的东西所需要的。我尽可能保持变量名的简短。我通常使用单字母或双字符变量名,包括一个字母和一个数字,比如 A7。如果我们编写相当大的程序,注释是不存在的,因为没有空间容纳它们。我做这些都是为了节省内存。但是这使得我的程序很难阅读。

我希望我保存了一些我自己写的或者我不得不修改的由别人写的非常糟糕的代码。两者都有很多。

命名变量

我倾向于让我的脚本变量足够长,以表示它们的内容,就像你在第十一章的项目中看到的那样。这些变量名的长度从相当短到适中不等。所有这些都是为了让我自己和未来的脚本维护者更容易阅读和理解代码。

我的脚本中的变量名倾向于反映它们的内容。因此,您应该能够推断出名为$CPUArch 的变量可能包含有关 CPU 架构的信息。您可能不知道数据的确切类型,但是在查看该变量的内容时,您应该有一个大致的概念。对于该变量的值,您可能会看到类似“X86_64”或“64”的内容。至少在我的剧本里,是这样的。

需要记住的是,变量名只在编写和维护脚本时输入。该脚本可以根据需要运行多次,我永远不需要输入任何变量名。

让一切都成为变量

这是一个非常常见的最佳实践。即使您需要使用像圆周率、欧拉常数或与特定领域相关的常数这样的“常数”,也应该将它们声明为变量,然后在计算中使用该变量。当然,bash 本身只做整数运算,但是还有其他类型的变量。

我喜欢在脚本中使用变量作为路径和文件名。我还为将要打印的数据使用了变量,比如上一章中的 mymotd 脚本。正如我们在脚本中所做的那样,使用\(Date、\)host、\(MachineType、\)MotherboardModel 等变量可以更容易地阅读和理解功能。当我看到类似图 12-1 中的语句时,我立刻明白了代码应该完成什么——即使它与这里的上下文无关。我们期望找到的分配给变量的类型值是清楚的。

img/462716_1_En_12_Fig1_HTML.png

图 12-1

从变量名可以明显看出变量的期望内容

您应该能够从变量名中推断出一点代码是如何工作的。显然,dmidecode 实用程序用于获取有关主板的信息,而“type”2 是主板信息。它还告诉我们包含字符串“Name”的输出行包含我们正在寻找的特定信息。剩下的代码是提取包含模型信息的数据字符串,并清理它以供我们的脚本使用。

所以我们来试试这个小实验来说明。

实验 12-2

在一行中输入以下故意不正确的命令行程序。

[root@david ~]# MotherboardModel=`dmidecode -t 2 | grep Version | awk -F: '{print $2}' | sed -e "s/^ //"`;echo $MotherboardModel
Rev 1.xx

变量值显然是错误的,因为数据与变量名的预期值不匹配。这显然不是主板的型号,而是修订版号。

现在使用下面更正的代码。又是一行。

[root@david ~]# MotherboardModel=`dmidecode -t 2 | grep Name | awk -F: '{print $2}' | sed -e "s/^ //"`;echo $MotherboardModel
TUF X299 MARK 2

即使我们在运行代码之前不知道主板的确切型号,结果显然更适合变量。

即使变量在赋值后只使用一次,这样做也是有意义的。我多次发现,在后来的脚本维护中,我添加了更多也使用该变量的代码。这节省了我在脚本中第二次或第三次输入长路径名的时间。

例如,如果我的脚本中有客户发票的路径名,~/Documents/business/Customer/Invoices,那么很容易设置一个变量,比如$Invoices,并在我的脚本中使用它,而不是完整的路径。这使得在脚本中的其他地方引用该变量也很容易。通过不必再次键入长路径名,我还防止了路径名中可能的键入错误,这将导致执行期间的错误。

很多时候,我从多个变量构建路径名,因为我需要额外的灵活性,这也减少了输入。例如,我的 rsbu 备份程序每天为一组新的备份使用一个新的目录。这棵树的结构是这样的。

/-
 |
 \-path to backup media
     |
     \Backups
         |
         |--host1
         |   |--2018-01-01
         |   |      \--data
         |   |--2018-01-02
         |   |      \--data
         |   |--2018-01-03
         |   |      \--data
         |   etc
         --host2
         |   |--2018-01-01
         |   |      \--data
         |   |--2018-01-02
         |   |      \--data
         |   |--2018-01-03
         |   |      \--data
        etc etc

每天为每个主机添加一个新的日期子目录。因此,我们需要创建一系列变量,用在可以生成这个目录结构的代码中。实验 12-3 给出了实现这一点的一种方法。

实验 12-3

我们不需要为这个实验创建脚本。输入以下命令开始安装。这些变量一旦在命令行定义,就一直是环境的一部分,直到使用unset命令取消设置或设置为 null。

[student@testvm1 ~]$ BasePath="/media/Backup-Drive/Backups"
[student@testvm1 ~]$ BackupDate=`date +%Y-%m-%d`
[student@testvm1 ~]$ YesterdaysDate=`date -d "now-1days" "+%Y-%m-%d"`

现在验证我们刚刚设置的变量的值。

[student@testvm1 ~]$ echo $BasePath;echo $BackupDate;echo $YesterdaysDate;echo $HOSTNAME
/media/Backup-Drive/Backups
2018-01-22
2018-01-21
testvm1

注意$HOSTNAME 变量是一个 BASH 内置变量,所以我们不需要设置它。现在,为该主机设置主备份路径。我使用这个程序来备份多台主机,所以我将每台主机的备份保存在一个单独的目录中。这对远程主机不起作用,但在本实验中这是一个很好的快捷方式。

[student@testvm1 ~]$ BackupPath="$BasePath/$HOSTNAME/"
[student@testvm1 ~]$ echo $BackupPath
/media/Backup-Drive/Backups/testvm1/

要完成当前备份路径,只需添加今天的日期。

[student@testvm1 ~]$ TodaysBackupPath="$BackupPath$BackupDate"
[student@testvm1 ~]$ echo $TodaysBackupPath
/media/Backup-Drive/Backups/testvm1/2018-01-22

但是因为我使用 rsync 和它的一些最有趣的特性, 2 ,我还需要为昨天的备份生成路径。

[student@testvm1 ~]$ YesterdaysBackupPath="$BackupPath$YesterdaysDate"
[student@testvm1 ~]$ echo $YesterdaysBackupPath
/media/Backup-Drive/Backups/testvm1/2018-01-21

我已经为昨天的备份生成了路径,这样 rsync 就可以简单地创建从昨天的备份文件到今天的目录的硬链接,并且只对已经更改的文件执行备份。

我在脚本的不同部分使用了这一系列包含路径元素的变量,以便为多台主机生成多条路径。对$BasePath 变量的修改也可以用来挂载我用于备份的外部硬盘驱动器。

尽管变量名相当长,但在脚本中输入它们相当容易。这些名字便于理解每个变量的功能以及它们如何融入整体。毫无疑问,这些名字可以变得更短,仍然可以理解,但我喜欢这样。

当然,有一些边缘情况,这些代码没有直接处理,但是我在脚本中有更复杂的代码来处理这些情况。我不想用边缘情况来混淆基础知识,例如没有以前的备份时会发生什么,以及如何处理以前的备份存在但不是昨天备份的可能性。实验 12-3 中定义的许多变量也被用来帮助处理那些边缘情况。

程序

Bash 是一种命令行语言,支持过程的使用。脚本中的过程就像变量一样需要名字。我们在第十一章中创建的脚本包含几个过程,命名这些过程是为了提供对其功能的深入了解。

例如,Help()过程显然是为了打印帮助信息,而 GPL()过程打印 GPL 许可证声明。kb2gb()过程有点晦涩,但是只要稍微考虑一下,就应该清楚它是将千字节转换成千兆字节的。

主机

是的,主机——网络上的计算机——需要命名。大多数组织都有某种命名主机的惯例。我认识的大多数系统管理员都已经建立了某种约定,即使他们的组织并没有强制实施。

我工作过的一个地方使用主要的希腊和罗马神来命名他们的 Linux 服务器,而他们的 Unix 和 Linux 工作站接收次要神和神话人物的名字。其他地方用《星际迷航》或《??》或《星球大战》中的名字来称呼他们的东道主。

大多数系统管理员都有家庭网络,我们都有自己使用的某种命名约定。无论是基于游戏,神话中的神和人物,孩子和孙子,鸟,宠物,船,电影,国家,矿物,亚原子粒子,化学物质,科学家的名字,还是其他什么,我都见过许多不同的约定。本·科顿,我的技术评论员,使用了他追逐风暴的地方的城镇名称。

我用埃塞克斯级航空母舰的名字称呼我家庭网络中的大多数主机,以此向我的父亲致敬,二战期间他在太平洋上的邦克山号上。对于这样一个两个单词的名字,我只是把这些单词放在一起创造了“bunkerhill”按照惯例,主机名总是小写。稍加测试就会发现,互联网 DNS 系统在执行查找时会忽略大小写,但我确实喜欢按照惯例来处理这类事情。

组织命名

许多组织都有定义良好的命名约定,而其他组织则将这些细节留给系统管理员。

我工作过的一些组织有命名主机、其他网络节点、程序和脚本的惯例。我认为这有点矫枉过正,但是有一个广泛的、有良好文档记录的约定总比没有好。

我工作过的大多数地方都有网络主机和节点的命名约定,但是大多数低级别的命名(比如脚本)都留给了系统管理员。这是我喜欢的情况。大多数组织不需要如此详细地处理命名约定。

无论您在系统管理员级别有什么约定,都应该被很好地记录下来。

摘要

对于系统管理员来说,与 Linux 哲学的其他原则一样,在为文件、过程、脚本、变量和其他任何东西创建名称时,没有一种特定的“正确”方法。这真的是关于什么最适合你。除了对你来说有意义和有意义的东西之外,你不应该感到有任何压力。

命名的常识是这里真正的关键。我使用的主要标准是,“几年后当脚本需要维护时,这个名称对我或另一个系统管理员有意义吗?”

系统管理员在命名事物时使用常识有助于我们成为懒惰的系统管理员。易于阅读的代码比不易于阅读的代码需要更少的维护时间。在完全重写之前,必须维护写得很差的难以理解的代码,这耗费了大量的时间和精力,这些时间和精力本可以更好地用在其他地方。

十三、以开放格式存储数据

我们使用计算机的原因是为了处理数据。它过去被称为“数据处理”是有原因的,这是一个准确的描述。尽管数据可能以视频和音频流、网络和无线流、文字处理数据、电子表格、图像等形式存在,但我们仍在处理数据。这仍然只是数据。

我们使用 Linux 中的工具来处理和操作文本数据流。这些数据通常需要存储,当需要存储数据时,以开放的文件格式存储总比以封闭的文件格式存储好。

尽管许多用户应用以 ASCII 格式存储数据,包括简单的平面 ASCII 和 XML,但本章是关于与 Linux 直接相关的配置数据和脚本。本章中我们要考虑的文件是关于系统配置的。

封闭是无法穿透的

早在 Windows 3.1 引入注册表 1 之前,大多数实用程序和应用都将其配置数据存储在注册表中。ini 文件。这些。ini 文件存储为 ASCII 文本,易于访问、阅读,甚至修改。只需要一个简单的文本编辑器就可以对这些进行修改。ini 配置文件。

注册表通过将配置数据存储在一个单一的、庞大的、难以理解的二进制数据文件中改变了这一切。虽然个别程序可以将配置数据存储在。ini 文件,注册表被吹捧为一种集中控制程序配置的方式,据说它的二进制格式比 ASCII 文本文件解析起来更快。

作为系统管理员,我们需要使用许多不同类型的数据。二进制格式本质上是晦涩难懂的,需要特殊的工具和知识来操作。有很多工具可以提供注册表查看和编辑功能。这些工具从所谓的免费软件到昂贵的商业程序都有。为了管理计算机,必须使用本身是封闭的特殊工具,这是向不可穿透性的进一步迈进。

所有这些问题的一部分是,这些工具的作者需要了解正在查看或编辑的注册表项的内容。没有来自专有软件供应商的内部知识,这些工具也是无用的。专有软件以二进制和专有格式存储配置数据的一个原因是为了对用户隐藏信息。

这一切都源于这些厂商所坚持的封闭和专有哲学。表面上看,这是为了保护用户不做“蠢事”,但这也是掩盖信息的好方法。

我试图在/etc 中找到一个二进制格式的 Linux 系统配置文件,但是没有找到。该目录中的数百个配置文件没有一个是二进制格式的。这确实是一件好事,但它让我没有一个二进制配置文件的示例,我可以用它来向您展示它是什么样子的。

二进制格式的一个问题是,没有理由创建我们在 Linux 中拥有的许多强大的工具。可以从二进制格式文件中生成的数据流没有一个可以用于 grep、awk、sed、cat、vim、emacs 等工具,或者我们在管理我们负责的系统时每天理所当然使用的数百种其他基于文本的工具。

开放是可知的

“开放源代码”是指代码,并使任何想要查看或修改它的人都可以使用源代码。“开放数据 2 ”讲的是数据本身的开放性。

术语“开放数据”不仅仅意味着可以访问数据本身,还意味着数据可以被查看、以某种方式使用以及与他人共享。实现这些目标的确切方式可能取决于某种归属和开放许可。与开放源码软件一样,这种许可旨在确保数据的持续开放可用性,而不是以任何方式对其进行限制。

开放数据是可知的。这意味着对它的访问是不受限制的。真正开放的数据可以自由阅读和理解,无需进一步解释或解密。在系统管理员的世界里,开放意味着我们用来配置、监控和管理我们的 Linux 主机的数据很容易被找到、读取和修改。它以易于访问的格式存储,如 ASCII 文本。当一个系统是开放的时,数据和软件都可以通过开放工具来管理——处理 ASCII 文本的工具。

平面 ASCII 文本

平面文本文件是开放和可知的。程序和系统管理员都很容易阅读它们,所以很容易看出什么时候工作,什么时候不工作。大多数 Linux 配置文件都是简单的平面 ASCII 文本文件,这使得它们易于使用我们已经掌握的简单 Linux 文本操作工具进行查看和修改。

所以我们可以使用catless来查看 Linux 配置文件,使用grep来提取和查看包含指定字符串的行。我们可以使用 vi、vim、emacs 或任何其他文本编辑器来修改 ASCII 文本格式的配置文件。

在我的一项工作中——我们使用 Perl CGI 脚本来管理电子邮件系统——我们使用纯文本文件来存储我们所有的数据。这些数据包括部门信息,例如谁被授权访问该部门的数据。它还包含每个部门的电子邮件用户的 ID 和登录信息。

我们编写了一些 Perl 程序来管理对这些数据的访问,这既适用于作为总体电子邮件系统管理员的我们,也适用于部门管理员。数据仍然是平面的 ASCII 文本文件,因此我们可以使用基本的 Linux 命令行工具来访问和修改数据,尤其是在对文件进行大规模更改时。同时,我们还能够使用基于 web 的 Perl CGI 脚本来处理个人和部门记录。

我们确实考虑过使用 MySQL 进行记录管理,但我们认为 ACII 文件更容易访问。我们的一个系统管理员在大约一周的时间里编写了一系列 Perl 脚本,允许我们在 Perl 脚本中使用类似 SQL 的函数调用,因此我们拥有了两个世界的优点。

系统配置文件

大多数系统范围的配置文件位于/etc 目录及其子目录中。/etc 中的文件提供了许多系统服务和服务器的配置数据,例如电子邮件(SMTP、POP、IMAP)、web (HTTP)、时间(NTP 或 chrony)、SSH、网络适配器和路由、GRUB 引导加载程序、显示屏和打印机配置等等。

您还可以找到提供影响所有用户的系统级配置的配置文件,例如/etc/bashrc。/etc/bashrc 文件在所有用户打开 bash shell 时为他们提供初始设置和配置。图 13-1 显示了我的 Fedora VM 上/etc/bashrc 文件的内容。

img/462716_1_En_13_Fig1a_HTML.pngimg/462716_1_En_13_Fig1b_HTML.png

图 13-1

/etc/bashrc 文件为所有打开的 bash shell 会话提供配置

放松——我们不会检查图 13-1 中/etc/bashrc 文件的每一行。但是,在这个文件中我们应该注意一些事情。

首先,看看所有的评论。这个文件是供用户阅读的。我们系统管理员毕竟是高级用户。我喜欢基于 Red Hat 的发行版的一点是大多数配置文件和脚本都有很好的注释。

这个脚本的功能之一是设置 shell 命令提示符。该脚本确定 shell 是标准 xterm 还是 vte 终端会话,或者它是否在屏幕会话中。它根据不同的条件设置不同的提示字符串。它还使用诸如/etc/sys config/bash-prompt-xterm 之类的外部文件,这些文件将提示符配置包含在一个文件和位置中,很容易由系统管理员进行管理。

靠近文件顶部是一系列注释,简要描述了脚本的功能,并告诫不要更改这个特定的文件。注释也告诉你你自己的修改应该去哪里。我们将进一步研究这个问题。

注意缩进是如何使这个脚本片段的结构比所有东西都挤在左边更容易阅读。

我们经过的时候你看到了吗?这个配置文件是一个可执行程序。这是一个 bash 脚本,它包含的程序逻辑可以根据外部条件决定采用哪条执行路径。这个脚本本身并不完整;它实际上是一个片段,可以在必要时来源-导入-到其他脚本中。

Sourcing 是一种 bash shell 方法,用于将其他 bash 脚本或片段的内容包含到脚本中。这允许多个脚本使用所获取的片段内容。你可以把它想象成编译程序使用的函数库。源文件被加载到调用脚本中的源命令位置。然后立即执行。

可以使用 source 命令来完成源操作。句号(。)是源命令的别名。这如图 13-2 所示,是图 13-1 中的一段代码。

img/462716_1_En_13_Fig2_HTML.png

图 13-2

这段代码片段提供了*。sh 文件位于/etc/profile.d。该目录中的其他文件将被忽略

图 13-2 中突出显示的代码来自所有的*。将/etc/profile.d 中的 sh 文件复制到这个代码片段中。

那么图 13-1 中的程序片段是如何执行的呢?哪里是代码或触发器导入-源-这个代码到它,所以它可以被执行。好问题。图 13-3 中的/etc/profile 脚本来源于/etc/bashrc 文件。

img/462716_1_En_13_Fig3a_HTML.pngimg/462716_1_En_13_Fig3b_HTML.png

图 13-3

/etc/profile 脚本在启动时为系统上的所有 shells 设置全局环境。它还在/etc/profile.d 和/etc/bashrc 中提供 bash 脚本片段。

/etc/profile 文件也是一个脚本片段。我们可以在这里花一些时间来定位/etc/profile 启动的方式,但是这将把我们引向错误的方向。可以说,当从 bash 本身调用时,它是作为登录 shell 被调用的,它首先读取/etc/profile(如果存在),然后读取/。bash_profile,/。bash_login 和~/。概要文件,按此顺序排列(如果存在的话 3 )。

全局 Bash 配置

现在,让我们对 bash 进行一些全局配置更改。

/etc/bashrc 文件提到了/etc/profile.d 目录。让我们看看实验 13-1 中的目录及其文件。同时,我们将添加一些我们自己的全局 bash 配置。

实验 13-1

这个实验应该以 root 用户身份进行。我们的目标是对 bash shell 的全局配置进行一些补充。

制作/etc/profile . d PWD 并列出内容。

[root@testvm1 ~]# cd /etc/profile.d/ ; ls -l
total 100
-rw-r--r--. 1 root root  664 Jul 25  2017 bash_completion.sh
-rw-r--r--. 1 root root  196 Aug  3 04:18 colorgrep.csh
-rw-r--r--. 1 root root  201 Aug  3 04:18 colorgrep.sh
-rw-r--r--. 1 root root 1741 Nov 10 12:53 colorls.csh
-rw-r--r--. 1 root root 1606 Nov 10 12:53 colorls.sh
-rw-r--r--. 1 root root   69 Aug  4 19:53 colorsysstat.csh
-rw-r--r--. 1 root root   56 Aug  4 19:53 colorsysstat.sh
-rw-r--r--. 1 root root  162 Aug  5 02:00 colorxzgrep.csh
-rw-r--r--. 1 root root  183 Aug  5 02:00 colorxzgrep.sh
-rw-r--r--. 1 root root  216 Aug  3 04:57 colorzgrep.csh
-rw-r--r--. 1 root root  220 Aug  3 04:57 colorzgrep.sh
-rwxr-xr-x. 1 root root  249 Sep 21 03:40 kde.csh
-rwxr-xr-x. 1 root root  288 Sep 21 03:40 kde.sh
-rw-r--r--. 1 root root 1706 Jan  2 10:36 lang.csh
-rw-r--r--. 1 root root 2703 Jan  2 10:36 lang.sh
-rw-r--r--. 1 root root  500 Aug  3 11:02 less.csh
-rw-r--r--. 1 root root  253 Aug  3 11:02 less.sh
-rwxr-xr-x. 1 root root   49 Aug  3 21:06 mc.csh
-rwxr-xr-x. 1 root root  153 Aug  3 21:06 mc.sh
-rw-r--r--. 1 root root  106 Jan  2 07:21 vim.csh
-rw-r--r--. 1 root root  248 Jan  2 07:21 vim.sh
-rw-r--r--. 1 root root 2092 Nov  2 10:21 vte.sh
-rw-r--r--. 1 root root  120 Aug  4 23:29 which2.csh
-rw-r--r--. 1 root root  157 Aug  4 23:29 which2.sh

所有带*的文件。sh 扩展由/etc/bashrc 或。etc/个人资料。不执行带有其他扩展名的文件。我们将通过在这个目录中创建一个新文件来添加 bash 配置。

使用您喜欢的编辑器在这个目录中创建一个名为“mybash.sh”的新文件。将以下内容添加到文件中。

################################################################
# The following are global changes to BASH configuration       #
################################################################
alias lsn='ls --color=no'
alias vim='vim -c "colorscheme desert" '
TestVariable="Hello World"
set -o vi

在测试之前,让我们确保别名还没有出现,并且 TestVariable 为 null。

[root@testvm1 profile.d]# alias
alias cp='cp -i'
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'
alias ls='ls --color=auto'
alias mc='. /usr/libexec/mc/mc-wrapper.sh'
alias mv='mv -i'
alias rm='rm -i'
alias which='(alias; declare -f) | /usr/bin/which --tty-only --read-alias --read-functions --show-tilde --show-dot'
alias xzegrep='xzegrep --color=auto'
alias xzfgrep='xzfgrep --color=auto'
alias xzgrep='xzgrep --color=auto'
alias zegrep='zegrep --color=auto'
alias zfgrep='zfgrep --color=auto'
alias zgrep='zgrep --color=auto'
[root@testvm1 profile.d]# echo $TestVariable

[root@testvm1 profile.d]#

现在测试结果。这一更改不会影响已经打开的 bash 会话。新的会话将反映这些更改。所以打开一个新的终端会话。作为学生用户,运行以下命令来验证结果。

[root@testvm1 profile.d]# echo $TestVariable
Hello World
[root@testvm1 profile.d]# alias
alias cp='cp -i'
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'
alias ls='ls --color=auto'
alias lsn='ls --color=no'
alias mc='. /usr/libexec/mc/mc-wrapper.sh'
alias mv='mv -i'
alias rm='rm -i'
alias vim='vim -c "colorscheme desert" '
alias which='(alias; declare -f) | /usr/bin/which --tty-only --read-alias --read-functions --show-tilde --show-dot'
alias xzegrep='xzegrep --color=auto'
alias xzfgrep='xzfgrep --color=auto'
alias xzgrep='xzgrep --color=auto'
alias zegrep='zegrep --color=auto'
alias zfgrep='zfgrep --color=auto'
alias zgrep='zgrep --color=auto

正如这个实验所示,对 ASCII 文件进行修改是很容易的。请注意,不需要重启就可以使这些更改生效——它们会立即对新的 bash 终端会话生效。

用户配置文件

让我们看看您自己的主目录中所谓的隐藏文件——那些名称以句点(.).这些是用户特定的配置文件,您可以根据自己的需要和偏好进行更改。让我们看看。bashrc 文件,这是一个配置文件,单个用户可以在其中设置他们自己的 bash 配置,比如别名、函数和他们独有的环境变量。

实验 13-2

以学生用户的身份执行此实验。

那个。bashrc 文件很短,所以我们可以用cat查看它。让我们确保我们位于学生用户的主目录中,然后显示该文件。

[student@testvm1 ~]$ cd ; cat .bashrc
# .bashrc

# Source global definitions
if [ -f /etc/bashrc ]; then
        . /etc/bashrc
fi

# Uncomment the following line if you don't like systemctl's auto-paging feature:
# export SYSTEMD_PAGER=

# User specific aliases and functions

这个文件也有很好的注释,甚至告诉我们在哪里添加我们自己的配置。因此,让我们添加一些无关紧要的东西来测试这个本地配置。使用您最喜欢的编辑器将下面一行添加到文件的末尾。

StudentVariable="This is a local variable."

查看变量。

[student@testvm1 ~]$ echo $StudentVariable

[student@testvm1 ~]$

变量尚未添加到环境中。从现在开始,它将成为 bash 终端会话环境的一部分。可以将它添加到现有的 bash 终端会话中。bashrc 文件是这样的。

[student@testvm1 ~]$ . .bashrc
[student@testvm1 ~]$ echo $StudentVariable
This is a local variable.

这些都是微不足道的例子,但是它们应该让您对开放格式配置文件的灵活性有所了解。遵循文件的逻辑很容易,并且在需要时修改它们也很容易。尽管每个发行版在如何向这些文件添加注释方面有所不同,但我使用的所有发行版都在注释中包含了足够的信息,使我能够找出适当的位置来修改配置。它们还包含了足够的信息,让我能够理解其中的逻辑。这并不意味着我不需要努力去理解它,但是如果我需要或者只是好奇的话,我可以做到。

请注意,本地用户 bash 配置会覆盖全局配置。因此,如果用户了解全局配置参数,并想为自己修改它,他们可以通过在~/中设置它来实现。bashrc 文件。

ASCII 岩石

现在我们可以看到,通过使用 ASCII 文本文件进行配置所创建的开放性是如何允许我们探索和理解我们的 Linux 操作系统的许多进程的。ASCII 是配置文件和 shell 脚本的首选格式。

许多系统级可执行文件也是设置配置和启动二进制文件的 bash 脚本。让我们检查一下/bin 目录来验证这一点。

实验十三-三

以 root 用户身份执行此实验。

制作/bin PWD 并计算文件的数量,看看总共有多少个可执行文件。

[root@testvm1 ~]# cd /bin/ ; ls | wc -l
2605

我们来算算有多少是 ASCII 文本文件。

[root@testvm1 bin]# for I in `ls`;do file $I;done | grep ASCII | wc -l
355

/bin 中超过 13%的可执行文件是 ASCII shell 脚本。现在查看 ASCII 脚本文件列表。你的主人给出的具体结果几乎肯定会和我的不同。

[root@testvm1 bin]# for I in `ls`;do file $I;done | grep ASCII | less

我不会在这里列出这些文件,但是您应该浏览一下,看看有什么。

现在让我们来看看其中的一个脚本。我选择了 ps2ascii 脚本,它被用作 ghostscript 程序的包装器。

注意如果您使用的主机没有安装 ps2ascii 程序,您可以安装它或者选择一个不同的 ascii 文件来完成本实验的其余部分。

[root@testvm1 bin]# cat ps2ascii
#!/bin/sh
# Extract ASCII text from a PostScript file.  Usage:
#       ps2ascii [infile.ps [outfile.txt]]
# If outfile is omitted, output goes to stdout.
# If both infile and outfile are omitted, ps2ascii acts as a filter,
# reading from stdin and writing on stdout.

# This definition is changed on install to match the
# executable name set in the makefile
GS_EXECUTABLE=gs

trap "rm -f _temp_.err _temp_.out" 0 1 2 15

OPTIONS="-q -dSAFER -sDEVICE=txtwrite"
if ( test $# -eq 0 ) then
    $GS_EXECUTABLE $OPTIONS -o - -
elif ( test $# -eq 1 ) then
    $GS_EXECUTABLE $OPTIONS -o - "$1"
else
    $GS_EXECUTABLE $OPTIONS -o "$2" "$1"
fi

ghostscript 程序通过从原件中提取文本,将 Postscript 和 PDF 文件转换为 ASCII 文本文件。这个包装器有注释告诉我们程序做什么。它设置一些变量,然后用不同条件的选项运行程序。

像 ps2ascii 这样的脚本在启动程序时允许很大的灵活性。它们使用户的生活变得更容易,因为脚本可以管理设置传递给主程序的选项和参数的任务。

最后的想法

Linux 中的开放数据使我们作为系统管理员能够探索一切,以满足我们对 Linux 如何工作的好奇心。使用 ASCII 文本文件作为脚本和配置文件允许我们访问我们每天工作的环境的内部工作方式。

我们能够利用这种开放性来跟踪一些相关的 bash 配置程序和文件。我们发现了如何做出全球性和地方性的改变。我们添加了一些我们自己的配置,所以 bash 现在的配置更符合我们的喜好。

而且,如果我们想要或者需要,我们可以下载用于编译内核可执行代码的源代码,以及我们的 Linux 发行版中提供的所有开源程序和实用程序。我已经这样做了几次,因为我想知道更多。如果你的好奇心带你去那里,你也可以。

所有这一切只有在开放的操作系统中才有可能。

十四、为数据使用单独的文件系统

这个特别的原则有很多内容,它需要理解 Linux 文件系统和挂载点的本质。如果你跳过了第六章“使用 Linux FHS”,你现在应该回去读一读。

注意

本章中术语“文件系统”的主要含义是位于独立分区或逻辑卷上的目录树的一部分,该分区或逻辑卷必须安装在根文件系统的指定安装点上,以便能够对其进行访问。我们还使用这个术语来描述分区或卷上的元数据结构,如 EXT4、XFS 或其他结构。这些不同的用法应该从它们的上下文中弄清楚。

为什么我们需要独立的文件系统

在我们的 Linux 主机上维护独立的文件系统至少有三个很好的理由。首先,当硬盘崩溃时,我们可能会丢失损坏的文件系统上的部分或全部数据,但是,正如我们将看到的,崩溃的硬盘上的其他文件系统上的数据仍然可以挽救。

其次,尽管可以访问大量的硬盘空间,文件系统还是有可能被填满。发生这种情况时,单独的文件系统可以最大限度地减少直接影响,并使恢复更容易。

第三,当某些文件系统(如/home)位于不同的文件系统上时,升级会变得更容易。这使得升级变得容易,而不需要从备份中恢复数据。

在我的职业生涯中,我经常遇到这三种情况。在某些情况下,只有一个分区,因此恢复相当困难。当主机配置了单独的文件系统时,从这些情况中恢复总是更容易、更快。

保证所有类型数据的安全是系统管理员工作的一部分。使用单独的文件系统来存储数据可以帮助我们实现这一点。这种做法也可以帮助我们实现成为一个懒惰的管理员的目标。备份确实允许我们恢复在崩溃场景中可能丢失的大部分数据,但是使用单独的文件系统可能允许我们恢复到崩溃时刻的所有数据。从备份恢复需要更长的时间。

硬盘崩溃

你的电脑硬盘是否崩溃过,导致你的 Linux 电脑无法启动;崩溃的硬盘上的所有数据;没有最近的备份?我们大多数人都经历过。我们的朋友、同事或顾客也都经历过这种情况。

拥有独立的文件系统使得在某些(但不是全部)硬盘崩溃的情况下从潜在的未受影响的文件系统中恢复数据成为可能。当单个文件系统用于整个目录树时,任何类型的硬盘崩溃都极有可能导致所有数据丢失。

不幸的是,有些硬盘故障模式会导致硬盘无法正常工作,硬盘上的所有数据都会丢失。

完整的文件系统

尽管有大量的硬盘空间可供我们使用,文件系统还是会被填满。失控的程序会很快填满文件系统。如果只有一个文件系统,主机很可能会崩溃,许多有价值的数据将会丢失。

我见过文件系统瞬间填满。在只有一个文件系统的主机中,结果可能是灾难性的。具体症状可能各不相同,从用户无法创建新文件、保存修改过的文件或登录桌面,到主机完全无响应甚至无法通过 SSH 远程访问。在某些情况下,重新获得控制的唯一方法是关闭系统并引导至恢复模式。然后,就有可能找到并删除填充磁盘的文件,并尝试了解是什么导致了这种情况。我遇到的最糟糕的情况是为这本书测试一个虚拟机,这个虚拟机不会终止。

在配置了独立文件系统的主机中,填充一个文件系统的任何影响都将被最小化,并且症状的破坏性可能会更小。从这种状况中恢复通常会更快更容易。

笔记本哀叹

这发生在今天。

今天——是的,真的,就在我 2017 年 12 月 26 日写这篇文章的今天——我的一个朋友给我发了一条短信,告诉我我一直为她提供支持的笔记本电脑无法启动。

几年前,我的朋友辛迪,也是我的瑜伽教练,变得不开心,因为她的电脑由于恶意软件的侵扰而不断变慢。她请我帮忙,我同意了。我给她讲了 Linux,她决定试试。

这些年来,我帮助辛迪通过硬件维修、软件更新和升级到 Fedora 的新版本来保持她的计算机运行,这也是我让她起步的地方。当她遇到自己无法解决的电脑问题时,她总是先打电话给我。今天也不例外。

第一个文本是在我写这本书的第八章时出现的。它说她的电脑无法启动,并在屏幕上打印出一系列重复的信息。经过几次短信交流,我决定我应该看看笔记本电脑,她把它带到了我的家庭办公室。根据/dev/sda 上显示一长串 I/O 错误的消息,我确定硬盘出现了故障。我还告诉她,我不确定是否能从硬盘中恢复数据,但我会尽力而为。

此时,我检查了她的外部 USB 备份驱动器,并确定最近的备份是几个月前的。那部分是我的错,我已经修好了。我们讨论了情况,她告诉我尽我所能。在她离开后,我从笔记本电脑中取出 320GB 硬盘,并将其插入我的硬盘扩展坞的一个插槽中进行探索。

这是我已经知道的。当我在笔记本电脑上安装 Linux 时,我为/usr、/var、/tmp、/swap 和/home 使用了单独的文件系统,就像我安装 Linux 时经常做的那样。这意味着/home 文件系统与根(/)文件系统位于不同的逻辑卷上。因为问题发生在启动过程中——从技术上讲是在启动之后,在 systemd 启动各种服务的过程中——很有可能其他逻辑卷没有受到影响,包括我安装了她的主目录/home 的卷。

硬盘启动后,我使用lvscan工具定位我的主工作站上的所有逻辑卷。结果包括故障硬盘上的逻辑卷,如图 14-1 所示。

img/462716_1_En_14_Fig1_HTML.png

图 14-1

lvscan 显示逻辑卷,包括故障硬盘上的逻辑卷

lvscan命令的结果还列出了分配给笔记本电脑硬盘上逻辑卷的设备文件。这些设备文件是由 udev 1 在硬盘达到速度并可以读取时创建的。正如我在第五章“一切都是文件”中提到的,udev 负责检测新设备何时插入系统,并在/dev 中为它们创建设备文件。

在列出的逻辑卷中有/dev/fedora_mobilemantra/home,这是我朋友的硬盘的主目录。此时,我知道了在我的工作站上的/mnt 上挂载主目录所需要的全部内容。我这样做了,并开始探索主目录。一切看起来都很好,我可以毫无问题地阅读几个文件。

注意

我很幸运,硬盘没有发生灾难性的故障。在这种情况下,错误显然是/(根)分区上的坏扇区,这些坏扇区使其他分区保持完整,因此它们可以被恢复。

创建主目录的备份并发现其中的许多文件是否有错误的最简单方法是使用 tar 命令。在创建 tarball 的过程中没有发生错误,所以我能够从主目录中检索所有数据。

当我打电话告诉辛迪,她的数据将继续以数字形式存在时,她非常高兴。

我使用了dd命令,如图 14-2 所示,对整个硬盘进行了快速测试。在总共 320GB 中仅读取 115GB 后,出现 I/O 错误。我本来可以继续关注错误的位置,但是现在知道错误导致了启动问题就足够了,这表明它们位于/(根)文件系统中。

img/462716_1_En_14_Fig2_HTML.png

图 14-2

使用 dd 命令测试硬盘。这也显示了发生的 I/O 错误

将“outfile”数据发送到/dev/null 可以防止它在终端会话中显示为 STDOUT。向终端显示 STDOUT 也会显著降低整个过程的速度。但是,任何指示 I/O 错误的 STDERR 消息仍会显示在终端上。运行此命令时,硬盘或其任何分区都未安装。

我为她的笔记本电脑订购了一个新的硬盘并安装了它。然后,我安装了 Fedora 27,这是目前最新的版本,并将保存的数据恢复到她的主目录中。一切正常,她的所有数据都被证明是完整的。

这个故事很好地说明了为什么要为数据使用单独的文件系统。它还很好地展示了理解 Linux 文件系统的层次结构对我们这些系统管理员来说是很重要的。它显示了/mnt 挂载点和/dev/null 设备的正确用法。这也是一个很好的例子,说明了一切都是一个文件,而/dev 中的设备专用文件可以通过简单的工具来使用。

数据安全

这一原则与数据安全和其他任何东西一样重要。在这种情况下,我指的是维护数据的持续存在和完整性意义上的安全性。今天的硬盘是巨大的,有些达到了数 TB。硬盘是电脑中最常见的故障设备之一,还有其他带有机械运动部件的设备,如风扇。因此,硬盘越大,发生故障时丢失的数据就越多。

当然,保护数据安全的一部分就是备份。另一个非常重要的部分是确保数据(实际数据,如文档、项目文件、财务文件、图形、视频、音频、用户配置文件等)尽可能地安全,以免被破坏。因为备用系统也会失灵。

根据 Linux 文件系统分层标准,“破坏根文件系统上的数据的磁盘错误是比任何其他分区上的错误更大的问题。小的根文件系统不容易因为系统崩溃而损坏。 2 “这背后的原因是,在大多数系统中,最大数量的磁盘写入发生在根分区中,因此它最有可能被问题破坏。前一个例子似乎就是这种情况。

由此得出的推论是,作为根文件系统一部分的目录比不是根文件系统一部分的目录更容易受到这些系统崩溃的副作用的影响。这是显而易见的,因为我无法挂载根文件系统,所以无法恢复上面的任何文件。我能够挂载/home 文件系统。

这使我们得出结论,为包含用户数据的目录树维护单独的文件系统是一个好主意。它还强化了上面引用的语句,即根文件系统应该尽可能的小。我的主工作站的根文件系统中的已用空间量只有 444MB,这不是很多。尽管如此,考虑到当前硬盘驱动器的巨大容量,我还是建议为根文件系统分配大约 5GB 的磁盘空间,以备不时之需。

推荐

我建议将 Linux 目录树的一些特定部分放在单独的文件系统中。有时,我甚至建议将它们放在单独的硬盘上,以进一步确保它们的安全并便于恢复,因为如果需要替换包含根文件系统的驱动器,则在单独驱动器上维护的文件系统上的数据不需要从备份中恢复。

作为重新安装的目录树的一部分,挂载预先存在的文件系统所需要做的就是在/etc/fstab 文件中添加适当的条目。这样,在新驱动器中安装操作系统后,当系统重新启动时,就可以挂载它们。这种配置可以在 Linux 安装期间使用“定制”磁盘配置来完成。该过程的细节超出了本书的范围。

当前基于 Red Hat 的安装(如 Fedora 和 CentOS)使用的默认磁盘配置可能远非理想。CentOS 7 将除/boot 和用于交换的单独卷之外的所有内容都放在根(/)分区中。

Fedora 27 将 1GB 放在/boot 中,50GB 放在/root 中,几 GB 放在 swap 中——实际容量取决于系统中的 RAM 容量,其余的放在/home 中。在我的测试虚拟机上,这是 195GB。对于我使用的大多数系统来说,这实在是太多了。我通过大量的实验发现,实际数字会随着硬盘的大小而变化。但结果还是一样。

这些默认值可能会导致磁盘使用率达不到最佳水平,并在主机生命周期的后期导致问题。尽管在这些默认安装中,/home 是一个独立的文件系统,但它的大小对于许多环境来说都太大了。例如,我的主工作站上的主目录只有大约 30GB 的数据,包括这本书和许多照片,都可以追溯到 20 多年前。还有其他需要考虑的文件系统。

Linux 文件系统层次结构的三个主要分支专门设计为位于单独的分区或卷上,作为单独的文件系统。 3 这是可能的,因为符合 Linux FHS 使它如此。这三个分支是/usr、/opt 和/var。我还发现了其他一些分支,它们作为独立的文件系统/home、/tmp 和/opt 工作得很好。

让我们看看目录树的分支,我推荐它们是独立的文件系统。所有这些文件系统都可以放在一个或多个独立于包含根文件系统的硬盘上。这有助于确保不在故障驱动器上的目录分支的整体生存能力。

你可以回头参考表 6-1 对 Linux FHS 的这些分支进行简单的描述,也可以参考文件系统层次标准 V3.0 4

/boot

/boot 目录树很有趣,因为它不能是逻辑卷配置的一部分。它必须是一个独立的磁盘分区,具有 Linux EXT2、EXT3、EXT4、VFAT 或 XFS 文件系统。这些是当前版本的 Fedora、CentOS6 和 CentOS7 安装程序 Anaconda 支持的唯一文件系统。

因为大多数现代发行版使用逻辑卷管理,所以这个目录必须是一个单独的文件系统。如果是这样,你就没得选了。但是,如果您不使用逻辑卷管理,而是使用直接在原始分区上创建的文件系统,比如 EXT4,那么您可以将/boot 作为根(/)文件系统的一部分。

我建议/boot 始终是一个单独的文件系统,即使主机上的其他文件系统不使用逻辑卷。

/home

显然/home 应该是一个单独的文件系统。我上面讨论的经验,以及这些年来其他类似的经验,让我非常清楚/home 应该永远是一个独立于目录树其余部分的文件系统。我所熟悉的发行版的默认文件系统配置表明,除了任何大小问题之外,这是一个最佳实践。

例如,当从 Fedora 的一个版本升级到下一个版本时,将我的数据(尤其是/home)放在一个单独的文件系统上使得版本级升级变得很容易,即使我选择不使用提供的升级工具,而只是在旧版本上安装新版本的 Fedora。这需要与从根文件系统损坏中恢复相同的过程;有必要执行文件系统的定制配置,并选择保留现有的主目录而不进行格式化。

/home 也是我建议放在独立于操作系统的硬盘上的文件系统之一。这有助于确保主驱动器出现故障时/home 上的数据是安全的。它还通过将磁盘访问分散到多个硬盘来提高性能,这样操作系统就不必等待用户数据访问,反之亦然。

尽管 FHS 将/home 指定为“主”目录,但它也认识到主目录在目录树中的位置通常取决于组织的标准和管理系统的系统管理员的判断。我遇到过/var/home、/opt/home、/homedirs 等。我喜欢遵循这个标准。

将主目录作为一个单独的文件系统,可以在需要时将它移动到不同的挂载点。为了实现这一点,可能还需要更改其他内容,比如默认路径。

root 用户的主目录是/root,这个目录应该是根文件系统的一部分。这样做的原因是为了确保为了方便起见,我们存储在根主目录中的工具和文件在不挂载其他文件系统的恢复模式和运行级别中仍然可用。

许多与系统服务相关的非登录帐户在其他位置也有主目录。具体位置因服务而异,但通常是作为服务本身一部分的目录。

/usr

/usr 分支包含不重要的命令,即用户命令,而不是在引导和启动过程中或在恢复模式下运行时需要的系统管理命令。

虽然前面的陈述是在 FHS 文档中发现的,但在 Fedora 和 CentOS 7 上严格来说不再是真实的。为了简化文件系统层次结构,这些发行版在/bin、/sbin、/lib 和/lib64 中使用符号链接来挂载点。/bin 目录是指向/usr/bin 的符号链接,/sbin 是指向/usr/sbin 的符号链接,/lib 是指向/usr/lib 的符号链接,/lib64 是指向/usr/lib64 的符号链接。

引导所需的文件现在是 Linux 初始 RAM 文件系统 initramfs 的一部分。 5 所以现在是符号链接的目录在引导时不再需要可用。

我通常将/usr 作为一个单独的文件系统,以防止它作为根文件系统的一部分出现问题,同时确保这里数据的安全性。大多数用户级命令和库都位于这里。这被认为是一个包含静态文件的目录树,这些文件在主机运行过程中通常不会改变。

这种树被称为“二级层次”的原因之一是,在许多方面,它的结构类似于从根目录开始的主树。有一个用于本地文件的子目录树/usr/local。

/usr/local 目录树包含子目录 etc、bin、sbin、include、lib、lib64、share 等等。/usr/local 树的目的是存储本地程序和配置文件。

这是我放置所有本地编写的脚本和它们需要的任何配置文件的地方。脚本本身位于/usr/local/bin 中,配置文件位于/usr/local/etc 中。为这些脚本编写的文档(如手册页)位于/usr/local/share 中。

另一种选择是将/usr 作为根文件系统的一部分,但是将/usr/local 作为一个单独的文件系统。反正我只备份/usr/local,因为我从来不对整个系统进行裸机恢复,所以这很有意义。如果我的操作系统或安装它的硬盘有问题,那就需要完全重新安装,除了/usr/local 树之外的所有内容都会在安装过程中重新创建。

/usr 和/usr/local 也可以是单独的文件系统。/usr 文件系统将挂载在/usr 挂载点上,然后本地分支将挂载在/usr/local 挂载点上。

/user 树显然不适合大型程序,如商业软件或大型开源应用。它适用于小型到中等规模的本地编码程序,以满足系统管理员或本地普通用户的需求。

大型软件应用应安装在另一个位置。建议使用/opt 目录树。

/opt

大型程序应该安装在/opt 目录树中。这个目录应该创建为一个单独的文件系统,以便在必要时可以轻松地扩展它的大小。

/opt 分支支持多个供应商安装其软件的完整子目录层次结构,以及为本地系统管理员使用而保留的完整目录集/opt/bin、/opt/doc、/opt/include、/opt/info、/opt/lib 和/opt/man。 6

/var

Linux 文件系统层次结构的/var 分支是一个有趣的组合——嗯,东西。它旨在包含“可变”数据——可以改变的数据,但不是配置数据。因为在任何类型的恢复或维护模式下,或者在初始引导过程中,操作系统都不需要/var 中的数据,所以可以将它安全地创建为一个单独的文件系统。

位于/var 中的数据是用户数据和数据库。我们在/var 中找到许多不同类型的数据。例如,如果主机是 web 服务器,则/www 将包含网站所需的文件。事实上,我的 web 服务器上运行着多个站点,每个站点都有自己的目录,比如/var/wwwboth、/var/wwwlinuxdatabook 等等。这使得确定哪个目录分支包含每个网站的数据变得容易。完成这项工作所需的唯一配置是在网站配置文件中。我使用 Apache web 服务器,所以应该是/etc/httpd/conf/httpd.conf。

Maria db——MySQL 的一个分支——在/var/lib/mysql 中维护它的数据库。SendMail 将用户收件箱存储在/var/spool/mail 中。BIND 为位于/var/named 中的数据库提供名称服务。那里也存储了更多的数据。

/tmp

/tmp 目录是用户和服务可以临时存储文件的地方。把/tmp 想象成一个任何类型的数据都可以被任何用户或进程临时存储的地方。存储在/tmp 中的文件很有可能会被删除,通常是在下次引导时。

我使用/tmp 下载大文件,比如 ISO 映像,比如各种发行版的安装映像。这些文件可能非常大,加上各种系统进程创建的文件,可以填满一个小的/tmp 文件系统。因此,我喜欢让我的/tmp 文件系统非常大,通常是 10GB 或更多。对于我的主工作站,我目前有 30GB 分配给/tmp。在多 TB 硬盘的今天,30GB 是一个非常合理的大小。

如果/tmp 文件系统被填满,就会发生奇怪的事情。我在第六章“使用 Linux FHS”中提到了当我设法填满/tmp 文件系统时出现的问题。GUI 桌面登录失败,因为桌面无法在/tmp 中创建新文件,但是控制台和远程 SSH 登录继续工作。

然而,如果/tmp 是根文件系统的一部分,问题会更严重。在这种情况下,可能会出现许多其他症状,因为各种附加服务无法找到足够的磁盘空间来工作。

其他分支

Linux 文件系统层次结构的所有其他分支都必须是根文件系统的原子部分。它们不能作为单独的文件系统创建,也不能在适当的挂载点挂载到目录树中。存储在目录树的这些其他分支中的程序和数据需要在引导的早期阶段以及在低级恢复或维护模式下运行时可用。

从独立的文件系统开始

为 Linux 文件系统层次结构的一个或多个组件设置单独的文件系统的最佳时机是首次安装操作系统的时候。大多数 Linux 安装程序,比如 Red Hat 的 Anaconda,都提供了在安装过程中进行定制磁盘配置的能力。此时,您可以指定单独的逻辑卷来包含一个或多个我们在本章中讨论过的文件系统,这些文件系统可以单独挂载。

安装程序——至少是我熟悉的安装程序——还能够识别现有的分区、卷和文件系统,并显示关于它们的标识信息。这使得在包含操作系统的硬盘崩溃并需要更换后重新安装 Linux 变得容易。当需要完全重新安装时,它还支持轻松的版本升级,例如从 Fedora 27 升级到 Fedora 28。无需接触/home、/usr 和/var 文件系统,就可以安装操作系统并重新格式化根文件系统。例如,这将使我所有的个人数据、电子邮件收件箱和我的网站数据保持完整。

稍后添加单独的文件系统

在初始安装之后,将这里讨论的一个或多个目录转换成一个单独的文件系统并不特别困难。它只是需要一些远见和计划。

基本流程很简单。事实上,有多种方法可以做到这一点。下面是/home 目录的一个进程的样子。这个过程假设/home 当前不在与根文件系统不同的文件系统上。

  1. 如有必要,安装新的硬盘。

  2. 在驱动器上创建分区或逻辑卷。

  3. 向新分区或卷添加文件系统标签。这使得新文件系统在未挂载时很容易识别,并允许通过标签挂载。

  4. 备份当前主目录中的数据。如果有空间,将备份存储在/tmp 中。这是让/tmp 变大的一个很好的理由。

  5. 从当前/主目录中删除数据。这个步骤释放了在新文件系统被挂载到文件系统层次结构的主干上的/home 之后不可访问的空间。

  6. 在/etc/fstab 中添加一个条目,指定新文件系统在/home 上的挂载。

  7. 将新卷挂载到/home。

  8. 将数据恢复到/home。

  9. 测试并验证所有数据都已正确恢复。

让我们在实验 14-1 中做一个类似的实验。

实验 14-1

这个实验应该以 root 用户身份进行。在备份了现有的/home 目录后,我们将删除 USB 设备上的现有分区并创建一个 Linux 分区,将新分区格式化为 EXT4,挂载为/home,然后恢复备份的数据。

注意如果您没有注销所有学生用户会话,可能会出现意外结果。

如果您以学生用户身份登录,请注销所有学生登录会话。

警告!该实验可能会导致您的个人目录中的数据丢失。您应该仅在用于培训而非用于生产的虚拟机或主机上执行此实验。

我们将使用 USB 驱动器作为新家庭驱动器的位置。如果 USB 设备已经插入您的系统,请将其卸载并移除。

警告!该实验将销毁 USB 设备上的所有现有数据。在继续之前,请确保您使用的是为这些实验指定的设备。

将 USB 驱动器插入 USB 端口。不要安装它。使用dmesg确定分配给设备的设备专用文件。在我的虚拟机中,这是/dev/sdb。

我们将使用fdisk删除现有的分区并创建一个新的 Linus 分区。使用fdisk查看现有分区。

[root@testvm1 ~]# fdisk /dev/sdb

Welcome to fdisk (util-linux 2.30.2).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Command (m for help): p
Disk /dev/sdb: 62.5 MiB, 65536000 bytes, 128000 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x73696420

Device     Boot Start    End Sectors  Size Id Type
/dev/sdb1        2048 127999  125952 61.5M  c W95 FAT32 (LBA)

Command (m for help):

注意,现有的分区可能是 FAT32 (VFAT)分区。我们希望将 EXT4 用于我们的/home 文件系统。删除现有分区,然后打印结果以验证该分区已被删除。

img/462716_1_En_14_Figa_HTML.png

Command (m for help): d
Selected partition 1
Partition 1 has been deleted.

Command (m for help): p
Disk /dev/sdb: 62.5 MiB, 65536000 bytes, 128000 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x73696420

使用设备上的所有空间创建新分区。采用分区类型和数量的默认值。当被问及是否要删除 VFAT 签名时,请回答是。然后采用起始和结束扇区的默认值。

Command (m for help): n
Partition type
   p   primary (0 primary, 0 extended, 4 free)
   e   extended (container for logical partitions)
Select (default p):

Using default response p.
Partition number (1-4, default 1):
First sector (2048-127999, default 2048):
Last sector, +sectors or +size{K,M,G,T,P} (2048-127999, default 127999):

Created a new partition 1 of type 'Linux' and of size 61.5 MiB.
Partition #1 contains a vfat signature.

Do you want to remove the signature? [Y]es/[N]o: y

The signature will be removed by a write command.

验证新分区是 Linux type 83 分区。

Command (m for help): p
Disk /dev/sdb: 62.5 MiB, 65536000 bytes, 128000 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x73696420

Device     Boot Start    End Sectors  Size Id Type
/dev/sdb1        2048 127999  125952 61.5M 83 Linux

Filesystem/RAID signature on partition 1 will be wiped.

Command (m for help):

现在将新的分区表写入 USB 设备。

Command (m for help): 
w

The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks

[root@testvm1 ~]#

创建 EXT4 文件系统。注意,我们将文件系统添加到 sdb1 分区,而不是磁盘本身 sdb。在小型设备上,这不会花费很长时间。

[root@testvm1 ~]# mkfs -t ext4 /dev/sdb1
mke2fs 1.43.5 (04-Aug-2017)
Creating filesystem with 62976 1k blocks and 15744 inodes
Filesystem UUID: 915c4857-cc81-4637-80ac-5e69d40329df
Superblock backups stored on blocks:
        8193, 24577, 40961, 57345

Allocating group tables: done                            
Writing inode tables: done                            
Creating journal (4096 blocks): done
Writing superblocks and filesystem accounting information: done

为分区创建一个标签,然后验证它是否已创建。

[root@testvm1 ~]# e2label /dev/sdb1 home
[root@testvm1 ~]# e2label /dev/sdb1
home
[root@testvm1 ~]#

是时候备份当前的主目录了。由于您执行这些实验的主机是为培训而指定的,因此应该不会有太多备份,所以这不会花很长时间。我们将创建一个简单的 tarball 作为备份。

[root@testvm1 ~]# tar -cvf /tmp/home.tar /home

现在我们需要小心一点。对于这个实验,我们不会在/etc/fstab 中添加条目。并且我们不会删除/home 当前的任何内容。

提示在/home 挂载点上挂载位于 USB 设备上的主文件系统不会删除或损坏当前主目录中的现有数据。新文件系统安装在现有数据上,无法再访问这些数据。卸载 USB 设备上的主文件系统后,原始主目录及其数据将再次可访问。

现在让我们在/home 上挂载新创建的主文件系统。我们将使用设备专用文件来明确指定我们想要挂载的设备。这防止了与任何其他可能带有“home”标签的主文件系统的任何潜在冲突我们还快速查看了除 lost+found 目录之外应该为空的内容。

[root@testvm1 ~]# mount /dev/sdb1 /home ; ls -lR /home
/home:
total 12
drwx------. 2 root root 12288 Feb  2 14:49 lost+found

/home/lost+found:
total 0
[root@testvm1 ~]#

请记住,/home 中的原始数据仍然存在,只是被挂载在/home 挂载点上的空文件系统所掩盖。

现在,我们可以将备份数据恢复到主目录。当从 tarball 中提取数据时,它总是被恢复到当前目录中。因此,如果我们想要恢复/home,我们需要在执行提取之前将根目录(/)设置为 PWD,我们在下面的命令中这样做。

[root@testvm1 ~]# cd / ; tar -xf /tmp/home.tar
[root@testvm1 /]#

让我们验证提取是否正常工作。

[root@testvm1 /]# ls -l /home
total 16
drwx------.  2 root    root    12288 Jan 15 10:04 lost+found
drwx------.  6 student student  1024 Jan 31 09:03 student
[root@testvm1 /]# ls -l /home/student/
total 37
-rw-rw-r--. 1 student student   84 Jan 27 15:28 error.txt
-rw-rw-r--. 1 student student   15 Jan 27 11:41 file0.txt
-rw-rw-r--. 1 student student   15 Jan 27 11:41 file1.txt
-rw-rw-r--. 1 student student   15 Jan 27 11:41 file2.txt
-rw-rw-r--. 1 student student   15 Jan 27 11:41 file3.txt
-rw-rw-r--. 1 student student   15 Jan 27 11:41 file4.txt
-rw-rw-r--. 1 student student   15 Jan 27 11:41 file5.txt
-rw-rw-r--. 1 student student   15 Jan 27 11:41 file6.txt
-rw-rw-r--. 1 student student   15 Jan 27 11:41 file7.txt
-rw-rw-r--. 1 student student   15 Jan 27 11:41 file8.txt
-rw-rw-r--. 1 student student   15 Jan 27 11:41 file9.txt
-rw-rw-r--. 1 student student   60 Jan 27 15:28 good.txt
-rwxr-xr--. 1 student student 9830 Jan 30 09:28 script.template.sh
-rw-rw-r--. 1 student student   42 Jan 27 15:16 test1.txt

你也可以这样做。

[root@testvm1 /]# df -h /home
Filesystem      Size  Used Avail Use% Mounted on
/dev/sdb1        56M   36M   16M  70% /home

请注意,由于我使用的 USB 设备非常小,所以只有少量的可用空间。

此时,用户 student 正在使用新的/home 文件系统。以学生用户的身份重新登录,验证一切正常。然后以学生用户的身份注销。

现在我们需要卸载/home 文件系统。

[root@testvm1 /]# umount /home

现在可以安全地从主机上移除 USB 设备了。原来的主目录现在被取消屏蔽,正在使用中。

最后的想法

独立的文件系统使我们作为系统管理员的工作更容易。在单独的文件系统中维护部分目录树,我们在驱动器崩溃的情况下提供了更大的灵活性,能够灵活地将文件系统移动到不同的挂载点,并在需要时更容易执行完整的操作系统重新安装。它还提高了目录结构的其他部分在一个硬盘崩溃时的生存能力。

我花了一段时间才明白这是一个好主意,但是从那时起,我一直为这里讨论的目录维护独立的文件系统。不止一次保存了我的数据。

十五、使程序可移植

可移植程序让懒惰的系统管理员的生活变得更加轻松。可移植性是一个重要的考虑因素,因为它允许程序在广泛的操作系统和硬件平台上使用。使用可以在许多类型的系统上运行的解释性语言,如 bash 和 Perl,可以节省大量的工作。

当从一个平台移植到另一个平台时,用 C 等编译语言编写的程序至少必须重新编译。在许多情况下,必须在源代码中维护特定于平台的代码,以便支持二进制文件预期运行的不同硬件平台。这产生了许多额外的工作,包括编写和测试程序。

Perl、bash 和许多其他脚本语言在大多数环境中都可用。除了极少数例外,用 Perl、bash、Python、PHP 和其他语言编写的程序可以不加修改地在许多不同的平台上运行。

英特尔 PC 到大型机

在我工作的一个地方,我负责一台运行 Linux 的 Intel 主机和一个相当大的带有内置数据库引擎的 Apache 网站。我们编写了大量的 Perl 和 bash 程序,它们被用作 CGI,根据从数据库中检索到的数据生成网页。甚至数据库软件也是由我们的一个系统管理员用 Perl 编写的。

作为灾难恢复计划的一部分,我们运行的所有大型机和 Unix 程序(我们的整个软件库存)都应该迁移到位于费城的恢复服务中。该服务不提供基于英特尔的计算机,因此我们的网站不能直接得到相同硬件的支持。

但是,我们组织中的一部分硬件(也在灾难恢复站点复制)是 IBM Z 系列大型机,它可以支持大量的 Red Hat 实例。我们决定明智的做法是测试我们的软件,看看它是否可以移植到 IBM Z 系列机器上。我们希望不需要做太多的改动就能让它工作。我可以访问 Z 系列主机上的专用 Red Hat 实例,并被告知报告我的结果。

我从识别必须移动的软件和相关数据开始。这很容易,因为我们按照 Linux 文件系统分层标准的定义,使用文件和数据的标准目录位置。

创建我们需要传输的文件的 tarball 不到五分钟,将 tarball scp(安全拷贝)到大型机只需几秒钟。我从 tarball 中提取文件,使用启动 shell 程序启动各种服务器,“自动化一切”,并开始测试。一切都完美无缺。除了测试本身,从开始到结束再到传输、启动和运行的总时间为 12 分钟。

这部分是因为我们的数据库实际上是一个平面 ASCII 文本文件,符合第十三章中的原则:“以开放格式文件存储数据。”修改它,从一种二进制格式转换到另一种,或者从 ASCII 转换到 EBCDIC,或者从一个系统导出到另一个系统,都不需要神奇的咒语。刚刚成功了。

但是这种简单的移植也是因为我们使用了 Perl 和 bash,这有利于程序的移植。

架构

Linux 运行在许多架构上。其实硬件架构挺多的。 1 维基百科维护了一长串 Linux 支持的硬件架构列表,这里只是其中的几个。

当然 Linux 支持 Intel 和 AMD。

它还支持 32 位和 64 位 ARM 架构,这在地球上几乎所有的手机和设备中都可以找到,如 Raspberry Pi。 2 大多数手机使用的是一种叫做 Android 的 Linux 形式。

飞思卡尔(原摩托罗拉)68K 架构;德州仪器 320 系列,高通六角形;惠普的 PA-RISC;IBM 的 S390 和 Z 系列;MIPSIBM 的 Power、PowerPC、SPARC 等等。

这些架构中的每一个在硬件指令集 3 级别上都是不同的。每个架构需要不同的编译器,或者至少需要一个能够支持各自指令集的编译器。这反过来意味着,当从一个架构移植到另一个架构时,任何使用编译语言的程序都必须重新编译。这是一种可移植性,尽管程序需要重新编译。

我在这一章中的意思是,当程序从一种架构转移到另一种架构时,它应该能够正常工作。不需要重新编译或重写。只有 shell 和其他解释性脚本语言可以做到这一点。

便携性限制

当我第一次听到与软件有关的术语“可移植性”时,它指的是复制一个程序,可以从一台计算机转移到另一台具有相同架构和操作系统的计算机上,并在那里运行。在谷歌上搜索会找到大量的结果,这些结果都与使用各种技术将软件从一台 Windows 电脑转移到另一台电脑有关,包括从可以插入任何电脑的 USB 驱动器上运行程序。其他技术描述得不太清楚。

批准

其他结果指的是简单地在多个硬盘上安装程序。供应商可以出于各种原因试图阻止这种情况,在某些情况下,这是相当非法的。最终用户许可协议(EULA)可能明确声明您有权仅在一台计算机上安装和使用程序。更宽松的版本可能允许您在多台计算机上安装它——有一些特定的限制——但一次只能在一台计算机上使用它。

我不想卷入关于许可协议的讨论。但是真正的可移植性受到许可的影响,所以有必要考虑一下。有时候技术并不是便携性的限制因素。

技术

然而,有时技术是软件可移植性的限制因素。

编译器和代码

我们已经研究了与支持平台相关的可移植性。对于编译过的程序,这意味着编译器必须能够创建与支持的平台兼容的二进制文件。我们已经看到 Linux 在广泛的硬件平台上得到支持,所以显然有编译器支持这些平台。

我们可以说,这些平台有一定程度的兼容性,代码在它们之间是半可移植的。这基本上意味着,如果需要的话,代码可以放在一个单一的代码库中,但是为了支持目标平台,需要在代码中进行考虑。这些差异是由于每个平台的硬件指令集的固有差异。

好消息是,Linux 使用的 GNU 编译器集合 4 (GCC)包含 C、C++、Objective C、Fortran、Java 和 Ada 编程语言的编译器。GCC 可以运行在 60 多种操作系统平台上,包括 Linux、DOS、Windows、许多 Unix 变体、MIPS、NeXT 和一大堆我在找到脚注 4 中引用的 GCC 定义之前从未听说过的平台。我们还可以在该文档中看到,GCC 支持多种处理器,可以针对这些处理器编译二进制代码。

这意味着我们在编译的二进制世界中有了某种程度的可移植性。缺点是为一个硬件平台编译的代码不能在不同的硬件平台上运行,所以必须重新编译。有时,为了使代码能够编译,必须对其进行重大修改。这需要很大的努力,而且大多数开发人员都不愿意尝试让他们的代码在所有甚至大部分硬件平台上编译。他们通常会挑选一两家最有潜在客户的公司,不会超出这个范围。

如果源代码是开放源代码,那么一些需要或希望让这些代码在一个不太常见的硬件平台上运行的程序员可以这样做。如果他们真的这么做了,那肯定需要大量的工作和知识才能实现。

对于我们这些懒惰的管理员来说,这绝对不是一个合适的选择。让我们从一开始就使我们的代码更加可移植,并消除大部分额外的工作。在我看来,编译代码的可移植性很低,因为从一个平台转移到另一个平台需要大量的工作。可以做,但是我不想自己做。

书店老板

LibreOffice 5 是可移植的编译代码的一个很好的例子。我在各种项目中广泛使用 LibreOffice,包括写这本书。LibreOffice 可用于许多操作系统平台,包括 Linux、各种 Windows 版本、Mac OS 和 Android。甚至还有一个“便携”6版本采用 PortableApps.com 打包。 7 这种打包使应用可以从自己的 u 盘上使用,例如,在任何 Windows 计算机上。

所以 LibreOffice 在多种意义上都是可移植的。它也是开源的,因此您可以从 LibreOffice 网站下载源代码,并根据自己的需要进行修改。我们大多数人都不会这样做,但是代码是可用的,所以如果我们需要或想要的话,我们可以查看或更改它。LibreOffice 是在 Mozilla 公共许可证 2.0 版下分发的。 8

Shell 脚本

现在,我们回到 shell 脚本。为什么呢?因为绝大多数 shell 脚本可以在 Linux 下的任何硬件平台上运行。在大多数情况下,它们也可以在其他 Unix 和类似 Unix 的操作系统上工作。

shell 实际上是一种编程语言:它有变量、循环、决策等等。

Unix 编程环境 9

这句话适用于我用过的每一款 shell。在前面的章节中,你已经看到了如何直接在命令行编写简短的 shell 程序,以便于快速解决问题。我们还介绍了如何创建可执行文件来存储这些特别的程序,以便将来可以使用它们,并供其他可能需要相同解决方案的系统管理员使用。

我更喜欢使用 bash,因为它是所有 Linux 发行版的默认 shell,并且它也适用于 Unix。其他 shells 也很普遍,如 ksh、csh、tcsh 和 zsh,但它们可能需要安装,因为它们可能不是默认安装的。

bash shell 与可移植操作系统接口(POSIX 11 )标准几乎 100%兼容,这意味着在一个操作系统和硬件平台上运行的 bash shell 脚本也可以在支持 bash 的所有其他平台上运行。这并不意味着你不会遇到一些问题。例如,我们在第十一章中编写的 mymotd 脚本寻找一些特定的硬件数据,这些数据可能不可用,也可能可用,但方式与我们的脚本假设的不同。脚本将会运行,但是您可能会遇到一些异常的结果。

Windows 的可移植性

到目前为止,我们一直关注 Linux 和 Unix 操作系统的兼容性。但是 Windows 呢?尽管这本书和《系统管理员的 Linux 哲学》是关于 Linux 环境的,但是如果我们不看看 Windows,不管多么简短,这一章都是不完整的。

如上所述,可以创建可以在 Linux、各种版本的 Unix、Windows 和其他操作系统上编译的源代码。要做到这一点需要做很多工作,但这是可以做到的,而且已经做到了。真正的问题是我们如何在 Linux 和 Windows 上运行我们的 shell 脚本。

有几种方法可以在 Linux 和 Windows 之间提供脚本可移植性。

Cygwin

Cygwin 是一个免费的开源产品,可以下载并安装在您的 Windows 计算机上。Cygwin 支持 Windows Vista 和更高版本,并安装了一个非常灵活的 Linux 环境和一套从 Linux 和 GNU 实用程序移植过来的几乎完整的程序、实用程序和桌面环境。

可以使用 Cygwin 安装 bash、tcsh、其他 shells、KDE 和其他 Linux 桌面,以及我们系统管理员已经习惯的许多 Linux 实用程序。不仅可以在 Windows 上体验 Linux,而且我们的 bash 和其他脚本现在也可以移植到 Windows 上。Cygwin 环境甚至扩展到强加/dev 目录和我们期望在任何 Linux 主机上找到的通常的设备专用文件。

不过,这种便携性也有其局限性。例如,硬件和操作系统特定的功能可能无法正常工作。因此,可能有必要在 shell 脚本中添加一些代码来确定操作系统环境,并相应地考虑到这些差异。这并不是什么新鲜事,在不同的 Linux 发行版之间以及 Linux 和各种版本的 Unix 之间都有过。向脚本添加一点额外的代码以允许它在多个操作系统上运行,这是确保更高水平的可移植性的一种非常简单的方法。

在其他情况下,bash 脚本可能会在运行的意义上进行移植,但在一般情况下没有意义。例如,我编写的安装后脚本可以处理 Fedora Linux 安装无法运行的所有任务,但它会生成许多错误。

我花了一点时间安装并学习了一些关于 Cygwin 的知识,但是除了像这样的一些测试之外,我通常不使用 Windows。Cygwin bash shell 是大家熟悉的,它提供了一个使用不依赖于操作系统的 Linux 命令和脚本的好机会。

管理员

微软在 2006 年发布了第一版 PowerShell 12 。2018 年 1 月,他们在麻省理工学院的许可下推出了 PowerShell。代码本身现在可以在许多平台上使用,包括 Linux。PowerShell 是一种面向对象的脚本语言,它旨在提供 Windows 和 Linux 平台之间的脚本可移植性。

我没有用过 PowerShell,虽然我只是玩了一会儿,看看它是关于什么的。它与我使用过的任何 Linux 和 Unix shells 都非常不同。我怀疑如果我花一些时间使用它,我可以像使用 bash shell 一样使用它。由于我还需要学习 Linux,我自己可能不会使用 PowerShell。但是,如果您需要 Linux 和 Windows 操作系统之间的脚本可移植性,您绝对应该查看一下。

面向 Linux 的视窗子系统

用于 Linux 的 Windows 子系统 14 (WSL)允许 Linux ELF 二进制文件在 X64 版本的 Windows 10 主机上运行。这个兼容层使 Windows 用户能够从 Windows 商店安装和运行许多不同的 Linux 发行版。

WSL 有其局限性,但它为需要跨平台兼容性的用户提供了另一种选择。

互联网和便携性

我们一直在从命令行运行 shell 脚本。当我们使用其他程序运行我们的脚本时会发生什么?例如,使我们的脚本可移植的一种方法是在 web 服务器上将它们作为 CGI 程序运行,并将结果交付给发出请求的 web 浏览器。

这种可移植性方法的优点是用户不需要特殊的工具、虚拟机或兼容层。不需要在客户端,即用户的主机上下载和安装软件。脚本运行,工作在 web 服务器上完成。只有浏览器用来生成和显示包含 web 服务器完成的工作所产生的信息的页面的数据流被发送到请求客户端。

让我们快速地看一下为这种类型的环境创建脚本。

创建网页

回到互联网的石器时代,当我第一次创建我的第一个商业网站时,生活是美好的。我安装了 Apache HTTP 服务器,并创建了几个简单的 HTML 页面,这些页面陈述了一些关于我的业务的重要事项,并提供了一些信息,如我的产品概述和如何联系我。这是一个静态网站,因为内容很少改变。维护很简单,因为我的网站是不变的。

静态内容

静态内容很容易,而且仍然很常见。让我们快速地看一下两个示例静态网页。你不需要一个工作网站来执行这些小实验。只需将文件放在您的主目录中,然后用浏览器打开它们。如果文件通过网络服务器提供给你的浏览器,你会看到完全一样的效果。

web 服务器的唯一功能是将创建网页的文本数据从服务器发送到浏览器。在本章的实验中,我们将简单地在/home/~目录中创建文本数据流作为文件。

在静态网站上,我们首先需要的是 index.html 文件,它通常位于/var/www/html 目录中。这个文件可以简单到像“Hello world”这样的文本短语,根本不需要任何 HTML 标记。这将简单地显示没有任何格式的文本字符串。

实验 15-1

本章中的所有实验都可以作为学生用户进行。

在您的主目录中创建 index.html,并添加文本“Hello world ”,没有引号或任何 HTML 标记,因为它是唯一的内容。

使用以下 URL 在浏览器中打开 index.html。

file:///home/<yourhomedirectory>/index.html

结果相当平淡无奇。只是浏览器窗口上的一点文字。

所以 HTML 标记不是必需的,但是如果你有大量需要格式化的文本,没有 HTML 编码的网页的结果将是不可理解的,因为所有的东西都在一起运行。

所以下一步是通过使用一些 HTML 编码来提供一些格式,使内容更具可读性。

实验 15-2

下面的数据创建了一个页面,它具有 HTML 静态网页所需的绝对最小标记。将 H1 标记添加到 index.html 文件的文本中。

<h1>Hello World</h1>

现在看看 index.html,看看不同之处。

当然,您可以在实际的内容行周围放置许多额外的 HTML 来制作一个更加完整和标准的网页。如下所示的更完整的版本仍然会在浏览器中显示相同的结果。它还为更标准化的网站奠定了基础。继续将此内容用于您的 index.html 文件,并在您的浏览器中显示。

<!DOCTYPE HTML PUBLIC "-//w3c//DD HTML 4.0//EN">
<html>
<head>
<title>My Web Page</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>

使用更复杂的表单的结果变化不大,但它构成了一个完整的 HTML 编码的网页。上面的 HTML 代码唯一改变的是我们现在有了一个标题“我的网页”,它出现在浏览器标签或标题栏中。

我用这些技术建立了几个静态网站,但是我的生活即将改变。

新工作的动态网页

我曾经接受了一份新工作,我的主要任务是为一个非常动态的网站创建和维护 CGI(公共网关接口)代码。在这种情况下,动态意味着在浏览器上生成网页所需的 HTML 是从每次访问页面时都可能不同的数据中生成的。这包括用户在用于在数据库中查找数据的 web 表单上的输入。结果数据被适当的 HTML 包围并显示在请求浏览器上。但是不需要那么复杂。

为网站使用 CGI 脚本允许您创建简单或复杂的交互式程序,运行这些程序可以提供动态网页,该网页可以根据输入、计算、服务器中的当前条件等进行更改。有许多语言可以用于 CGI 脚本。我们将研究其中的两个,Perl 和 BASH。其他流行的 CGI 语言是 PHP 和 Python。

本章不包括 Apache 或任何其他 web 服务器的安装和设置。如果您可以访问可以试验的 web 服务器,则可以直接查看浏览器中显示的结果。否则,您仍然可以从命令行运行程序,并查看将创建的 HTML。您还可以将 HTML 输出重定向到一个文件,然后在浏览器中显示结果文件。

使用 Perl

Perl 是一种非常流行的 CGI 脚本语言。它的优势在于它是一种非常强大的文本处理语言。

要执行 CGI 脚本,您需要在您正在使用的网站的 httpd.conf 中使用下面一行。

ScriptAlias /cgi-bin/ "/var/www/cgi-bin/"

这将告诉 web 服务器可执行 CGI 文件的位置。对于这个实验,我们不要担心服务器端的事情。没有网络服务器,我们仍然可以做任何我们需要的事情。

实验 15-3

创建一个新文件 index.cgi,并向其中添加以下 Perl 代码。对于这个实验,这个文件也应该位于您的主目录中。

#!/usr/bin/perl
print "Content-type: text/html\n\n";
print "<html><body>\n";
print "<h1>Hello World</h1>\n";
print "Using Perl<p>\n";
print "</body></html>\n";

将 index.cgi 的权限设置为 755,因为它必须是可执行的。

[student@testvm1 ~]$ chmod 755 index.cgi

从命令行运行该程序并查看结果。它应该显示它将生成的 HTML 代码。

[student@testvm1 ~]$ ./index.cgi
Content-type: text/html

<html><body>
<h1>Hello World</h1>
Using Perl<p>
</body></html>
[student@testvm1 ~]$

我们现在有了一个 Perl 程序,可以生成 HTML 以便在 web 浏览器中查看。

当使用 web 服务器时,可以将文件的所有权设置为 apache.apache。

现在在浏览器中查看 index.cgi。你从这里得到的只是文件的内容。浏览器真的需要将它作为 CGI 内容来发布。除非服务器告诉它程序所在的目录是按照上面 httpd.conf 中显示的那样指定的,否则它并不知道如何做。

要查看它在浏览器中的样子,请再次运行该程序,并将输出重定向到一个新文件 test1.html。

[student@testvm1 ~]$ ./index.cgi > test1.html
[student@testvm1 ~]$ cat test1.html
Content-type: text/html

<html><body>
<h1>Hello World</h1>
Using Perl<p>
</body></html>
[student@testvm1 ~]$

现在使用您的浏览器查看您刚刚创建的包含生成内容的文件。您应该会看到一个格式良好的网页。

实验 15-3 中的 CGI 程序仍然生成静态内容,因为它总是显示相同的输出。在实验 15-4 中,我们使用 Perl“system”命令在系统 Shell 中执行跟在它后面的 Linux 命令。结果被返回给程序。在这种情况下,我们简单地从 free 命令的结果中提取当前的 RAM 使用情况。

实验 15-4

将下面一行添加到 index.cgi 程序中。

system "free | grep Mem\n";

您的程序现在应该看起来像这样。

#!/usr/bin/perl
print "Content-type: text/html\n\n";
print "<html><body>\n";
print "<h1>Hello World</h1>\n";
print "Using Perl<p>\n";
system "free | grep Mem\n";
print "</body></html>\n";

从命令行运行该程序两三次,可以看到 free 命令几乎每次都返回不同的数字。

[student@testvm1 ~]$ ./index.cgi
Content-type: text/html

<html><body>
<h1>Hello World</h1>
Using Perl<p>
Mem:       4042112     300892      637628        1040     3103592     3396832
</body></html>
[student@testvm1 ~]$ ./index.cgi
Content-type: text/html

<html><body>
<h1>Hello World</h1>
Using Perl<p>
Mem:       4042112     300712      637784        1040     3103616     3396996
</body></html>
[student@testvm1 ~]$ ./index.cgi
Content-type: text/html

<html><body>
<h1>Hello World</h1>
Using Perl<p>
Mem:       4042112     300960      637528        1040     3103624     3396756
</body></html>
[student@testvm1 ~]$

再次运行程序,并将输出重定向到结果文件。

[student@testvm1 ~]$ ./index.cgi > test1.html

在浏览器中重新加载~/test1.html 文件。您应该会看到显示系统内存统计信息的附加行。运行程序,同时将输出重定向到该文件,并多次刷新浏览器,注意内存使用情况会偶尔发生变化。

使用 BASH

Bash 可能是 CGI 脚本中使用的最简单的语言。它在 CGI 编程方面的主要优势在于它可以直接访问所有标准的 GNU 实用程序和系统程序,并且系统管理员应该熟悉它。

实验 15-5

将现有的 index.cgi 重命名为 Perl.index.cgi,并使用以下内容创建一个新的 index.cgi。

#!/bin/bash
echo "Content-type: text/html"
echo ""
echo '<html>'
echo '<head>'
echo '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">'
echo '<title>Hello World</title>'
echo '</head>'
echo '<body>'
echo '<h1>Hello World</h1><p>'
echo 'Using BASH<p>'
free | grep Mem
echo '</body>'
echo '</html>'
exit 0

记得将权限设置为可执行。从命令行运行该程序并查看输出。

[student@testvm1 ~]$ chmod 755 index.cgi
[student@testvm1 ~]$ ./index.cgi
Content-type: text/html

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello World</title>
</head>
<body>
Hello World</h1><p>
Using BASH<p>
Mem:       4042112     290076     647716       1040    3104320     3407516
</body>
</html>

再次运行该程序,并将输出重定向到您之前创建的临时结果文件。然后刷新浏览器,查看它显示为网页的样子。结果应该是相同的,除了一些内存数字会有一点不同。

CGI–开放和便携

从这些实验中可以看出,创建开放的、可移植的 CGI 程序是很容易的,这些程序可以用来生成各种各样的动态网页。这些都是微不足道的例子,但你现在应该看到一些可能性。

虽然我们认为脚本最常见的方式是从命令行运行它们,但是它们也可以与其他软件一起使用来执行一些非常有趣的任务。用通用语言编写的 CGI 脚本就是一个很好的例子。

因为用于创建我们的 CGI 程序的语言在许多操作系统上都受支持,所以这些程序是可移植的。您可能需要在 Windows web 服务器上安装 bash,但这也是可能的。例如,像 Python 和 PHP 这样的其他语言也可以用来生成动态网页,并且与 Perl 一起,在大多数平台上都很容易获得,包括操作系统和硬件。

博客

WordPress 15 是一个强大的开源程序,允许创建和管理网页。这是用脚本语言编写完整程序来生成和交付基于 web 的动态内容的一个很好的例子。WordPress 本身只是生成网页的代码;仍然需要像 Apache HTTP 服务器这样的 web 服务器将数据从服务器传送到客户机 web 浏览器。

WordPress 是用 PHP 编写的,所以很容易移植到任何运行 PHP 的平台上。PHP 是一种特别适合编写动态网页的编程语言。我有时会忘记安装 PHP,因为它并不总是默认安装的。我将 PHP 安装添加到我的安装后脚本中,因此它将一直存在。但是如果你在运行 WordPress 时遇到问题,请检查 PHP 是否已经安装。

WordPress 非常灵活,因为它使用主题来生成网站的外观和感觉。通过改变主题,只需点击几下鼠标,就可以在几秒钟内改变网站的外观。我在我所有的网站上都使用 WordPress,因为它非常简单和灵活。我甚至教过非技术人员如何使用类似文字处理的界面来创建新的网页和帖子。

虽然 WordPress 主题的许多方面可以通过它自己的基于网络的管理界面来改变,但是有些事情需要直接使用 CSS 样式表,以及特定于主题的 PHP 代码,来定义每个主题的外观和感觉。通过 WordPress 界面使用 CSS 是可能的,但是我发现在终端会话中使用 Vi 或 Vim 对我来说效果最好。

然而,在我修改任何东西之前,我总是会做一个新的副本,并保持原来的原样。我通常会将副本重新命名为“我的主题”,以区别于原始版本。然后我使用 WordPress 管理界面切换到我的新主题。现在,我可以修改新的主题,而不需要担心对原始主题的更新会消除我的更改。

当然,我可以修改 CSS 来改变颜色和字体。我还可以修改主题的 PHP 代码,以便稍微改变一下页面结构。当主题需要一点调整时,我已经这样做了几次。在安装 WordPress 可用的插件时,我还修改了主题的 PHP 代码。

所有这些成为可能的唯一原因——就可移植性和改变它的能力而言——是 WordPress 和它可用的主题都是开放和可访问的。组成这个应用的文件都存储为 ASCII 文本文件。而且是开源的,也就是说 WordPress 所发布的 GPLv2 17 许可证允许这一切。

最后的想法

我打算在这一章中尝试定义可移植性。然而,随着我写作的进展,我开始意识到可移植性是一系列的值,而不仅仅是二元的反应——是的,它是可移植的,或者不是,它不是可移植的。尽管它们是可移植的,但是 shell 脚本可能仍然需要调整,以便在不同的操作系统和硬件平台上运行时产生期望的结果。

便携性是减少我们工作量的一个关键因素。编写可移植的代码——或者至少是尽可能可移植的——是只需要做一次工作的极好方法。当可以用 shell 脚本在所有这些平台上运行一次时,为什么要为几个不同的平台编写代码呢?

作为系统管理员,命令行脚本是我们花费大部分时间的地方,让这些脚本可移植是很重要的。幸运的是,大多数 shell 脚本,尤其是那些用 bash 编写的脚本,都具有很高的可移植性。为我们管理的网站编写可移植的 CGI 代码是另一个很好的步骤,如果它适用的话。

使用开源代码可以节省更多的时间,这些代码是可移植的,并且已经为许多环境进行了测试和创建。我们把 WordPress 作为一个例子。仅仅因为我们可以编写自己令人惊叹的 CGI 脚本来驱动一个网站,并不意味着这样做是有效的。WordPress 已经写好了,它是开源的,而且做得很好。如果你不喜欢 WordPress,还有很多其他的选择。

便携性太棒了!

十六、使用开源软件

这个原则可能并不完全是你所想的那样。大多数时候,我们认为开源软件就像 Linux 内核、LibreOffice 或成千上万个开源软件包中的任何一个,它们构成了我们最喜欢的发行版。在系统管理的上下文中,开源意味着我们编写的自动化工作的脚本。

开源软件是任何人都可以检查、修改和增强源代码的软件。 1

— Opensource.com

引用上述内容的网页包含了对开源软件的精彩讨论,包括开源的一些优势。我建议你阅读那篇文章,并考虑它如何应用于我们编写的代码——我们的脚本。如果我们去寻找,其中的含义就在那里。这一章希望能帮助你获得一些洞察力,就像写它启发了我一样。

开源的定义

开源的官方定义非常简洁。opensource.org 的开源定义 2 的注释版本包含十个部分,明确简洁地定义了软件被认为是真正开源所必须满足的条件。

这个定义对于 SysAdmins 的 Linux 哲学非常重要,所以我在这里包含了注释定义的文本。您不必阅读这个定义,但是我建议您这样做,以便更完整地理解术语“开源”的真正含义。

注意

开源定义不是一个许可。它描述了任何许可证要被认为是开源许可证所必须满足的条件。

开源定义(带注释)

版本 1.9

下面缩进的斜体部分显示为开源定义(OSD)的注释,并不是 OSD 的一部分。没有注释的普通版本的 OSD 可以在这里找到。

介绍

开源不仅仅意味着获得源代码。开源软件的发布条款必须符合以下标准:

1.自由再分配

许可证不应限制任何一方将软件作为包含多个不同来源的程序的聚合软件分发的一个组件进行销售或赠送。此类销售不需要版税或其他费用。

基本原理: 通过约束许可要求免费再分发,我们消除了许可方为了获得短期收益而放弃许多长期收益的诱惑。如果我们不这样做,合作者将面临巨大的背叛压力。

2.源代码

程序必须包含源代码,并且必须允许以源代码和编译形式发布。当某种形式的产品不与源代码一起发布时,必须有一种广为人知的方法以不超过合理的复制成本获得源代码,最好是通过互联网免费下载。源代码必须是程序员修改程序的首选形式。不允许故意混淆源代码。不允许使用中间形式,如预处理程序或翻译程序的输出。

基本原理: 我们需要访问未混淆的源代码,因为你不能在不修改它们的情况下进化程序。因为我们的目的是使进化变得容易,所以我们要求修改变得容易。

3.衍生作品

许可证必须允许修改和衍生作品,并且必须允许它们在与原始软件许可证相同的条款下分发。

基本原理: 仅仅阅读源代码的能力不足以支持独立的同行评审和快速进化选择。为了实现快速进化,人们需要能够试验和重新发布修改。

4.作者源代码的完整性

如果许可证允许出于在构建时修改程序的目的分发带有源代码的“补丁文件”,则许可证可以限制源代码以修改后的形式分发。许可证必须明确允许分发由修改后的源代码构建的软件。许可证可能要求衍生作品带有与原始软件不同的名称或版本号。

理由: 鼓励大量改进是好事,但用户有权知道谁对他们使用的软件负责。作者和维护者有相互的权利知道他们被要求支持和保护他们的名誉。

因此,一个开源 许可 必须保证源代码随时可用,但可能要求它作为原始基础源代码和补丁来分发。通过这种方式,可以得到“非官方的”改变,但是容易与基本源区分开来。

5.不歧视个人或群体

许可证不得歧视任何个人或群体。

基本原理: 为了从过程中获得最大的利益,最大的 多样性 的个人和团体应该同样有资格为开源做出贡献。因此,我们禁止任何开源 许可 将任何人锁在进程之外。

包括美国在内的一些国家对某些类型的软件有出口限制。符合 OSD 的许可证可以警告被许可人适用的限制,并提醒他们必须遵守法律;但是,它本身可能不包含此类限制。

6.没有对努力领域的歧视

许可证不得限制任何人在特定领域使用程序。例如,它可能不限制程序用于商业,或用于基因研究。

理由: 该条款的主要意图是禁止那些阻止开源被商业使用的许可陷阱。我们希望商业用户加入我们的社区,而不是感觉被排除在外。

7.许可证的分发

程序附带的权利必须适用于程序被重新分发到的所有人,而不需要这些人执行额外的许可。

理由: 该条款旨在禁止通过要求签署保密协议等间接方式关闭软件。

8.许可证不能特定于某个产品

附加在程序上的权利不得依赖于程序是某一特定软件分发的一部分。如果程序是从该分发中提取出来的,并在程序许可证的条款范围内使用或分发,那么程序被再分发到的所有各方都应该拥有与最初的软件分发所授予的权利相同的权利。

基本原理: 该条款排除了另一类许可证陷阱。

9.许可证不得限制其他软件

许可证不得对与许可软件一起分发的其他软件施加限制。例如,许可证不得坚持在同一媒体上发布的所有其他程序必须是开源软件。

理由: 开源软件的经销商有权对自己的软件做出自己的选择。

是的,GPL v2 和 v3 符合这个 要求 。与 GPLed 库链接的软件只继承 GPL,如果它形成一个单一的作品,而不是任何软件,它们只是被分发。

10.许可证必须是技术中立的

许可证的任何条款均不得基于任何单独的技术或界面风格。

理由: 该条款专门针对需要明确表示同意才能在许可人和被许可人之间建立合同的许可。要求所谓“点击包装”的规定可能与 FTP 下载、CD-ROM 选集和网络镜像等重要的软件分发方法相冲突;这样的规定也可能阻碍代码的重用。一致性许可必须考虑到以下可能性:软件的 (a) 再分发将通过不支持下载的点击包装的非网络渠道进行,并且【b】涵盖代码(或重复使用的部分涵盖代码)可能会在不支持弹出对话框的非 GUI 环境中运行。

开源的定义最初来自于 Debian 自由软件指南 (DFSG)。

Opensource.org 网站内容以知识共享署名 4 获得许可。0 国际许可证

为什么这很重要

开源的定义对我们这些系统管理员来说很重要,原因有几个。首先,这个定义为我们提供了一个评估现有的许多许可证的框架。其中一些是真正的开源许可证,而另一些只是假装开源。

真正的开源许可让我们可以轻松合法地找到、下载和使用开源代码。如果不能保证我们使用的代码是开源的,我们将无法使用大量已经满足我们许多需求的现有代码。在任何被认为是开放源代码的许可下发布的代码没有任何产权负担。理解真正的开源许可的要求可以让我们确保我们使用的代码得到适当的许可。经过适当授权的开源代码是免费提供的,我们可以在任意多的计算机上使用它,并复制给其他人。对我们如何使用或分享它没有任何限制。有许多好的但不同的开源许可证。

开放源码倡议是公认的批准开放源码许可的权威。他们的网站上有一个最新的被批准的开源许可列表。当我们将代码开源时,我们应该应用这些许可中的一个。我们还应确保我们从他人处获得的供自己使用的软件是在这些经批准的许可证之一下分发的。

创造了这个术语

我喜欢了解 Unix、Linux 和开源的历史,所以我认为承认克里斯汀·皮特森 4 创造了“开源”这个术语是很重要的 1998 年 2 月,Peterson 与 Eric S. Raymond、Jon "maddog" Hall 和许多其他领导人举行了一系列会议,讨论 Netscape 作为自由软件的许可问题。许多人,尤其是 Peterson,不认为“自由软件”恰当地定义了他们想要完成的目标。

她提出了“开源”这个术语,并向其他一些与会者提出了这个想法。在 2 月 5 日的一次会议上,一些与会者开始使用“开放源代码”来描述不受限制且源代码容易获得的软件。这个故事确实是她的,所以请阅读她在 Opensource.com 的文章 5 。在关于 Opensource.com 的文章结尾的评论中,Eric Raymond 证实并支持 Peterson 对这个现在无处不在的术语的诞生的解释。

许可我们自己的代码

我所知道的回报开源社区的最好方法之一就是通过适当的许可开源我们自己的程序和脚本,开源社区为我们提供了所有这些不可思议的程序,比如 GNU 实用程序、Linux 内核、LibreOffice、WordPress 等等。

仅仅因为我们写了一个程序,我们相信开源并同意我们的程序应该是开源代码,并不意味着它是开源的。作为系统管理员,我们确实写了很多代码,但是我们中有多少人考虑过授权我们自己的代码的问题呢?我们必须做出选择,并明确声明代码是开源的,以及在何种许可下发布。如果没有这一关键步骤,我们创建的代码就会受到专有许可证的束缚,这样社区就不能利用我们的成果。

还记得我们在第十章创建的 bash shell 模板吗?我们在代码中包含了 GPL V2 许可证头声明作为注释,我们甚至提供了一个命令行选项,可以在终端上打印许可证头。在分发代码时,我还建议我们在代码中包含整个许可证的文本副本。

我发现非常有趣的是,在我读过的所有书籍和参加过的所有课程中,没有一次有人告诉我,作为一名系统管理员,一定要对我在任务中编写的任何代码进行许可。所有这些资料都完全忽略了系统管理员也编写代码的事实。即使在我参加的关于许可的会议中,焦点也是在应用代码、内核代码,甚至是 GNU 类型的实用程序上。没有一个演示甚至暗示了这样一个事实,即我们系统管理员编写了大量的代码来自动化我们的工作,或者我们甚至应该考虑以任何方式许可它。也许你有过不同的经历,但这是我的经历。至少,这让我很沮丧;它至多激怒我。

当我们忽视许可时,我们就贬低了代码的价值。我们大多数系统管理员甚至不考虑许可,但是如果我们希望我们的代码对整个社区可用,这是很重要的。这既不是信用的问题,也不是钱的问题。这是为了确保我们的代码现在和将来都可以以最好的自由和开源的方式提供给其他人。

Eric Raymond 写道,在计算机编程的早期,尤其是在 Unix 的早期,共享代码是一种生活方式。起初,这只是简单地重用现有的代码。随着 Linux 和开源许可的出现,这变得容易多了。它满足了系统管理员合法共享和重用开源代码的需求。

Raymond 说,“软件开发人员希望他们的代码是透明的,此外,当他们换工作时,他们不想失去他们的工具包和专业知识。他们厌倦了成为受害者,受够了被生硬的工具和知识产权壁垒挫败,以及不得不重复发明轮子。” 7 这句话同样适用于系统管理员。

这让我们想到了与组织代码共享和开源相关的问题。

组织代码共享

作为系统管理员,我们自然倾向于共享代码。我们喜欢帮助人们,这也是我们成为系统管理员的首要原因。是的,我们中的一些人更喜欢计算机而不是人,但是我们都喜欢分享我们的代码。

许多组织不知道如何共享代码,也不知道这样做的好处。其他人已经发现了这一点,有些人甚至付钱给员工来编写开源代码。

筒仓很烂

作为一名系统管理员,我在许多不同的组织中工作过,我发现许多人不擅长在内部和外部共享代码。我工作过的大多数地方甚至从未想过内部共享代码,更不用说外部了。每个开发项目都是相互独立的。部门就像筒仓,又高又窄,里面有很多青贮饲料,自我封闭的领地避免与外界接触。在许多方面,他们表现得像竞争对手,而不是为同一组织工作的团队。

我总是发现很难从这些组织的其他部门获得代码。其他内部组织的 PHB 似乎总是认为我们在与他们进行某种竞争,共享代码是一场零和游戏,共享代码的人是输家。至少,这需要数周的讨论,有时还需要某种包括保密协议在内的书面法律形式。我不是说两个部门编写的商业代码可能会在外部市场中以某种方式重叠或竞争;例如,我说的是两个内部实验室组织,它们每天执行基本相同的任务。共享代码会有很大的意义,节省很多工作,而且很容易做到。

在某些情况下,编写我们自己的代码比通过官僚主义的废话获取我们已经知道可以解决问题的代码更容易。真是浪费时间!

开放组织和代码共享

那种导致沟通不畅的小仓库的内部组织需要被一种开放的组织所取代,这种开放的组织将至少在内部鼓励代码共享。红帽公司的首席执行官吉姆·怀特赫斯特写了一本书,书名为《开放式组织9 ,书中讨论了开放式组织的优势和品质以及如何进行转变。对于 Opensource.com,怀特赫斯特也写了一篇非常有趣的文章“欣赏开放的全部力量”,他在文章中讨论了共享的概念,“共享往往会增加它的价值,因为共享让越来越多聪明、有创造力的人得到它。如果你能和尽可能多的人分享尽可能多的东西,那么当你消除分享的限制时,这个价值实际上会增加。正如开源倡议组织所说,这意味着分享你的指令、配方、源代码,并向所有人开放,而不是限制某些人、团体或“努力的领域”

2005 年,Karl Fogel 写了一本有趣的书,《生产开源软件——如何运行一个成功的自由软件 项目12 ,并在 2017 年推出了第二版。Fogel 详细介绍了创建开源软件的技巧、技术、法律问题以及社会和政治基础设施。这是一本有趣的书,详细介绍了创建真正开源软件的许多实际方面。它讨论了使用开源许可在内部和外部共享代码的优点。

一些组织付钱给他们的员工来写开源代码。例如,许多公司雇佣一些员工为内核编写代码,如果得到 Linus Torvalds 的批准,这些代码最终将与全世界的程序员共享。这并不总是纯粹的利他主义,因为许多这样做的公司希望让内核更好地为他们自己的软件工作。在许多情况下,这种新的或修改过的代码将使 Linux 更好地为每个人工作,Torvalds 可能会接受它进入内核源代码树。

除了内核之外,许多开源项目都得到组织的支持,这些组织理解在金钱和代码上支持开源软件的价值主张。

要避免的事情

这一章是关于使用开源软件的,但这意味着我们也需要区分真正的开源软件和那些有隐藏限制或根本不符合他们声称他们的软件被分发的许可的软件。令人难过的是,我们需要讨论那些错误地宣称他们的软件是开源的公司。

这也是我在本章中加入开源定义的原因之一。理解开源的目标可以帮助你理解什么时候一个许可不能满足需求。但是还有其他的事情需要小心。

如果软件供应商声明他们的软件是开源的,那么源代码应该很容易从互联网上下载。在某些情况下,我对软件很感兴趣,在浏览网站时没有发现源代码可用的迹象。在这些情况下,没有人回答我关于这个问题的询问。

如果为了下载某些软件,您需要提供您的姓名、电子邮件地址和其他识别信息,那么该软件肯定不是来自一家声誉良好的公司,即使他们声称使用开源许可证。我见过许多所谓的“免费白皮书”下载,如果它们不需要某种形式的“注册”,我会很感兴趣。我建议避开这些公司。他们可能利用开源软件的虚假或误导性承诺来建立垃圾邮件发送者的电子邮件地址列表。

代码可用性

用开源许可证许可代码是一回事;把它提供给其他人则完全是另一回事。我在本章开始引用的开源代码的定义意味着我们的代码必须以某种方式提供,这样任何人都可以下载和查看源代码。在本章的前一节中,我提到了像填写注册表单这样的要求表明,如果代码确实是在开源许可下发布的,那么它就是被非法限制的。

我们如何让我们的代码在开源许可下自由使用?有许多分享我们代码的好方法。我们来看一些。

如何共享我的代码?

既然我们的代码可以在经过批准的开源许可下发布,那么我们如何实际发布它并让其他人可以使用它呢?请注意,本章开头的开源定义并没有规定开源软件应该如何交付。

我所读到的任何东西都没有定义一个被认可的发布开源软件的机制。我读过的许可证以及我读过的关于开源软件分发的法律意见都是关于让源代码和可执行文件一起可用的。对于脚本来说,可执行文件是源

分享我们的开源代码非常容易。对我来说,当我安装了一些我写的脚本,以减轻我为客户和朋友建造或维修的计算机上的系统管理任务时,它就开始了。然后我开始把我的一些脚本放到 u 盘上,这样我就可以把它们给别人了。并不是说我做了很多。我有一些客户,但没有多少人对一些 bash 脚本感兴趣。比起我的脚本,更多的人对 Fedora 的现场 DVD 或 USB 驱动器感兴趣。

下一步是让脚本可以从互联网上下载,我已经用我的完成了。为了让这些脚本更广泛地可用,我已经将它们发布在我的技术网站上,Linux 的数据手册, 13 at http://www.linux-databook.info/?page_id=5245 。您可以下载它们,并在您认为合适的时候使用它们。代码全部在 GPL V2 下发布,PDF 文档在知识共享署名-共享许可下发布。

使用 SourceForge 14 和 GitHub 等开发者协作网站也是合适的。这些网站允许其他人轻松下载你的代码,以便参与开发。它们提供版本管理,并允许您作为主要开发人员只合并您认为合适的代码。

我曾经在我的一个项目中使用过 SourceForge,但是那个项目已经死了很久,并且已经被另一个项目取代了。像 SourceForge 和 GitHub 这样的网站的优势之一是,当当前的首席开发人员决定离开时,他们可以让其他人轻松接管项目。这就是我领导的项目所发生的事情。我从另一个需要把时间花在其他项目上的开发人员那里接管了它。

代码共享注意事项

关于共享您的代码,有几件重要的事情需要考虑。我将在这里简单地谈一谈。重要的是你意识到了它们,并能得到更多你需要的信息。

机密

保密是许多人关心的问题,这是理所当然的。意图保密的数据或代码可能会在开源软件中暴露。

当然,隐藏据称是机密或商业秘密的代码会使整个程序成为专有程序。如果你从一个程序中删除你的代码并隐藏它,那么从开源的角度来看,整个程序都是无用的。要成为真正的开源,你必须坚持到底。所有代码要么开放,要么不开放。

数据完全是另一种野兽。Eric Raymond 的分离法则 16 论述了政策与执行的分离。这意味着程序的用户界面,也就是实现策略的地方,应该与程序中实现机制的部分分开。这使得使用文本模式或 GUI 界面以及在不改变程序底层逻辑的情况下改变这些界面成为可能。

我们也可以将这种分离规则应用于程序所使用的数据。数据永远不应该作为程序的一部分存储,尽管我看到过这样做。我自己也犯了这个错误。

作为一种良好的编程形式,程序使用的数据必须与程序代码分开。这确保了当数据所涉及的外部事物发生变化时,数据本身很容易被改变。即使使用脚本,配置数据也应该与组成程序逻辑的代码分开维护。使用单独的配置文件使不熟练的用户可以进行修改,而不用担心损坏代码本身。

数据与程序的分离也意味着没有必要分发任何可能与代码本身一起保密的数据。

提供支持

当我分发我的代码时,有人发现了一个 bug,会发生什么?我有义务修理它吗?我有必要回答使用我的软件的人的问题吗?

这些问题的答案是“不”。我们没有任何义务支持我们提供的开源代码。为什么呢?因为开源许可——至少我使用的 GPL V2 17 明确声明没有保修。

img/462716_1_En_16_Fig1_HTML.png

图 16-1

我使我的脚本可用的 GPL V3 包含了这样一个条款,它清楚地表明代码没有隐含或明确的保证(原文是大写的。)

如果你在 opensource.org 阅读各种批准的许可证,你会发现他们都有类似的措辞。然而,尽管许可证中有声明,我们大多数人都希望我们的代码能为任何获得它的人工作——也包括我们自己。所以我们修复任何出现的问题,因为这对我们和我们的用户都有好处。

像 Red Hat 这样的公司,以及像 Document Foundation、 18 这样负责 LibreOffice 办公程序套件的实体,都有支持结构、bug 和问题报告程序,以及帮助解决使用问题并提供指导和支持的志愿者。

离别的思绪

使用其他人创造的开源软件是重要的,但是我并不是建议我们完全放弃使用专有软件,当它满足了一个无法满足的需求时。我的意思是,在广泛的谷歌搜索未能找到合适的开源软件后,我们考虑专有软件,然后探索编写一个脚本来执行该任务的可能性。

如果你选择写一个脚本来解决问题,开源你自己的代码。将它提供给其他人,因为如果您需要执行此任务,其他人也需要这样做。您将为他人节省您在创建脚本时投入的工作。

远离总是在已经有可用的东西时编写自己的代码的陷阱。我们可以编写自己的基于网络的内容管理或博客软件,但是已经有很多这样的软件了。WordPress、Drupal、Joomla、Plone、OpenCms、Mambo 以及更多的工具已经可以使用,这比你自己编写要简单得多。如果您需要的某个部分还不可用,那么为您选择的任何一个编写一个插件。

可以的话用别人写的开源软件;剩下的时间写别人能用的开源软件。

十七、力求优雅

优雅是很难定义的事物之一。当我看到它的时候我就知道了,但是把我看到的用一个简洁的定义表达出来是一个挑战。使用 Linux dict命令,Wordnet 给优雅下了一个定义:“在解决一个问题(特别是在科学或数学方面)时整洁和巧妙简单的品质”;他的发明的简单和优雅。"

在本书的上下文中,我认为优雅是硬件和软件的设计和工作中的一种美丽和简单的状态。当一个设计是优雅的,软件和硬件工作得更好,效率更高。简单、高效、易懂的工具有助于用户。

在技术环境中创造优雅很难。也是必要的。优雅的解决方案产生优雅的结果,并且易于维护和修复。优雅不是偶然发生的;你必须为之努力。

简单的品质是技术优雅的很大一部分。如此之大,事实上它应该有自己的一章,第十八章,“寻找简单”,但我们在这里不忽略它。本章讨论了硬件和软件优雅的含义。

硬件优雅

是的,硬件可以是优雅的——甚至是漂亮的,赏心悦目的。设计良好的硬件也更可靠。优雅的硬件解决方案提高了可靠性。

我们许多系统管理员既负责软件,也负责硬件。对于我们这些在小型组织中工作的人来说尤其如此,但在大型环境中也是如此。在我为思科工作的五年中,我的系统管理员工作需要我给新服务器装架和布线,识别和修复硬件问题,帮助设计机架布局和电源要求,以及更多与硬件相关的任务。

理解硬件的优雅与理解软件和操作系统的优雅同样重要。

印刷电路板

在谷歌上搜索“pcb 1 可靠性”,可以找到许多关于 pcb 设计和可靠性的文章和论文。Darvin Edwards 的一篇文章“PCB 设计及其对器件可靠性的影响”, 2 讨论了影响可靠性的 PCB 设计的四大领域。爱德华兹讨论的一个因素是热机械可靠性。反复的功率循环会导致快速的热变化,进而导致元件、走线(电导体)和焊点的膨胀和收缩。随着时间的推移,这些重复的热应力循环会导致 PCB 出现各种类型的故障。

在我大学的一门技术制图课上,我的作业之一是为印刷电路板绘制一组图纸。布局已经提供了,我真正需要做的就是使用我们刚刚学到的一些新技术重新绘制它。

当我查看图中的元件布局、走线和焊盘图形(元件焊接在 PCB 上的位置)时,我有了一点感悟。我们在课堂上学到的一件事是,每个焊点都是潜在的故障点,PCB 上使用的每个跳线(用于“跳过”其他走线的短导线)都会增加两个故障点,每个焊点一个。在这个板上有两个跳线,我决定看看我是否可以改变设计来消除它们。我把几条线路改到了不同的位置,消除了跳线。我把这个给老师看了,他的回答是这是一个更好的解决方案。他在那个项目上给了我一个好分数。

底板

硬件优雅可能意味着主板的简单和布局良好的设计,这是一个相当大的 PCB。如前所述,良好的主板设计可以提高可靠性。

在我看来,一个布局良好的主板,看起来很好,表面有光滑的导线和焊盘图案(元件焊接的安装焊盘),是优雅的。主板上放置的组件不会相互干扰,也不会与以后可能添加的其他组件(如功能强大但较长的视频适配器)相互干扰,CPU 插槽位于主板上,以便 RAM 内存和其他主板组件不会干扰添加高容量空气冷却风扇或液体冷却设备,这是一种优雅的设计。坦率地说,我一直很欣赏设计良好的主板的外观。这些主板是真正的艺术品。

计算机

设计良好的电脑是优雅的。这包括一个 Shell,其设计便于接触内部组件,提供大量不受限制的气流,大量安装风扇和液冷散热器的位置,以及大量便于电缆布线的选项。

这与主板上的大量 LED 照明、风扇、LED 灯条和花哨的照明控制器无关。这种东西是用来表演和娱乐的。我为我的主工作站购买的最新主板是华硕 TUF X299,它满足了我对这台工作站的所有要求。这也是唯一一个满足我所有需求的。它的背面恰好有一串发光二极管,可以产生简单的滚动颜色的光显示。我没有在 BIOS 中关闭它们,因为它们有点好玩,但我不会特意在我的需求中添加 LED 显示。

当外部电源和硬盘活动指示灯容易看到,并且亮度适中,因此在任何照明条件下都能看到时,计算机就是优雅的。电源和复位开关也很容易操作,但不要太显眼,否则可能会被意外碰到,导致计算机复位或关机

我可以继续说下去,但你应该明白。

数据中心

硬件优雅还意味着精心规划和建造的机房或数据中心。机架式盘柜的布局方式使得多个电源可以轻松接入,并且前后通道畅通无阻。布线整洁有序——不像图 17-1 中那样杂乱无章;它被切割成一定长度,在整个计算机房的电缆通道和电缆槽中无扭结或缠结..

img/462716_1_En_17_Fig1_HTML.jpg

图 17-1

它可能会完成工作,但这种布线工作肯定不是优雅的。知识共享 CC0

应使用不间断电源来维持所有设备的电力供应,直到发电机可以联机,并且在电源故障的情况下,电力可以切换到该发电机以实现长期稳定。电源和接地导线本身的布线应既安全又方便。

电源和接地

很难想象计算机的电源和接地应该有一个像“优雅”这样的术语。但是这里有一些事情需要考虑。

大约在 1976 年,我在俄亥俄州利马的 IBM 公司担任客户工程师。除了修理坏掉的电脑和安装新电脑,我的职责之一是协助客户规划新电脑的安装。这包括规划合适的电源和接地。在这种特殊情况下,我与客户就电源和良好接地的要求进行了长时间的讨论。良好的接地对于正确的电子操作和计算机的稳定性至关重要。

那么什么是好的理由呢?这是一根带有绿色绝缘(绿色地线)的大规格电线,从受保护的设备(计算机)连接到一个埋在潮湿土壤中至少 10 英尺深的铜桩。绿线接地不得连接任何其他接地线,也不得连接到它所经过的任何配电箱中的任何接地或中性母线。请注意,这是 IBM 对计算机逻辑操作完整性以及人类安全的定义。

经过我们的讨论后,客户说他们有一口旧井,内衬铜,至少有 80 英尺深,水深超过 60 英尺。这是一个很好的接地,我同意将计算机的绿线接地连接到井套管是合适的接地。使用这个预先存在的非常好的地面点是一个优雅的解决方案。

安装后,我们除了问题什么也没有。这些看似随机但经常发生的问题,某一天可能是内存故障,第二天可能是磁盘问题,第三天可能是处理器问题,等等。在大约两周的时间里,我们更换了内存、CPU 板和几乎所有我们能想到的东西,客户感到不安是可以理解的。

我现在相当确定这是接地问题。我和工厂的电工讨论了接地问题,他说他已经按照我们讨论的那样接好了地线。但我必须亲眼看看才能确定。

我拿出示波器,用一个感应钳夹住地线,这样我就能看到地线的任何电噪声。当我把这一切都搞定后,IT 副总裁过来了,我告诉他我在做什么。至少可以说,他有点怀疑。

就在我们结束讨论的时候,我们都听到了一个电机启动的声音,在示波器上出现了一大串电噪声。不一会儿,有人从机房跑出来,大喊电脑坏了。我不可能找到比心怀不满的副总统更好的听众了。特别是因为我们听到启动的马达是我们旁边的大型软饮料机上的压缩机马达。

我们让电工开始拆除所有配电箱的前面板,以便查看和验证绿线接地的完整性。在我们看的第一个盒子里,我立刻发现了问题。有一根又大又丑、看起来很脏、很旧的电线被嫁接到我们曾经干净的绿色电线地上。

大约就在这个时候,有人走进我们一直站着的那个小房间,把几封信放进了邮资机。地线产生的噪音说明了一切,因为这两个设备都插入了同一个电源插座。我们让电工剪掉了我漂亮的新的干净的绿色地线的丑陋的旧地线,客户再也没有因为接地问题而引起的其他问题。

有时候,优雅是一个原始的绿色电线地面。

软件优雅

在这里,我回到专门讨论 shell 脚本,这是系统管理员通常做的编码类型。对于一个系统管理员来说,用像 C 这样的语言编写代码是很不寻常的,这需要更多的开发工作,并且需要编译。对于系统管理员来说,这是对时间的浪费。

关于软件优雅有很多观点。是什么让软件变得优雅,在软件世界中“优雅”是什么意思?以下是我的一些观点,并对每一个观点做了一些解释。

一般来说,优雅是看起来很好,甚至很漂亮的代码,并且遵循本书中概述的原则。在我看来,当你使用这些指导方针时,软件是优雅的。这是我列出的特征,我敢打赌,其他系统管理员对于什么是软件优雅有他们自己的想法。在任何情况下,这些都不是硬性规定,只是指导方针。任何软件最重要的方面是它应该执行你写它要做的任务。使用这些指导方针可以让其他人——和你——更容易理解你做了什么,并维护你写的代码。

  1. 使用一致的缩进——代码应该在过程和流程控制结构的缩进中保持一致。这有助于在各种情况下更容易可视化程序的结构和执行流程。

    • 我知道一些开发人员使用制表符缩进,其他人使用空格。人们使用的制表符或空格的数量也各不相同。只要代码能被任何没有编写它的人容易地阅读——也能被那些确实编写了它的人容易地阅读,那么这基本上是无关紧要的。
  2. 布局清晰的设计 -代码应该很好地布局和排序,以便在各种条件下容易看到执行的流程。

    • 最有效的代码是以直通方式执行的代码,它不会来回跳转,也不会有不必要的流控制结构来降低速度。

    • 使用过程有很好的理由,例如防止在多个地方复制相同的代码。然而,在可能的情况下,程序的主体应该以简单明了的方式流动。

  3. 使用****STDIO——我们已经看到 STDIO 是一个强大的使能器;它允许我们将许多小程序链接在一起,以执行单个程序无法完成的复杂任务。具有强制用户界面(CUI)的程序(如菜单)不提供 STDIO。这种程序仅限于独立存在,不能在数据流中工作。应该避免使用强制用户界面,因为它们的局限性太大,不能很好地与命令行管道和重定向配合使用。

    • fdisk 程序是使用菜单界面的有用且强大的实用程序的一个例子。这样做的问题是 fdisk 不能在脚本中使用。有人编写了一个单独的程序,从脚本中执行 fdisk 函数。目前的工具是 sfdisk。
  4. 添加有意义的注释 -程序被很好地注释了有意义的信息。这有助于让维护人员清楚代码的目的,并确保可以快速定位和修复问题。

  5. 每个程序都应该做好一件事——这个指导原则一直是 Unix 和 Linux 哲学的宗旨,并产生了核心实用程序,以及其他小型的、针对单一任务并能很好地执行该任务的核心实用程序。这就产生了强大而灵活的命令行程序,可以组合成管道来执行单个程序无法完成的复杂任务。

    • 这样做的一个副作用是做一件事的程序往往很小。这使得它们易于理解,并在必要时进行修改。

    • 这个原则的推论是,给这些小程序增加更多的功能通常不是一个好主意。对所谓“新特性”的需求实际上应该被看作是对新程序的需求,新程序也应该遵循这些指导原则。大多数新特性,当修补到现有程序上时,只会造成代码膨胀,使程序更难使用和维护。

  6. 沉默是金——Linux 命令行工具通常不会向系统管理员显示一切正常的消息。这可以防止不需要的消息进入 STDOUT 数据流,进入管道并给后面的程序造成混乱。

  7. 总是使用最少的必要代码 -使用执行所需任务所需的最少代码。其他的都是糟粕,应该淘汰。这就是简单的症结所在,也是复杂的对立面。

    • 一些程序员喜欢炫耀错综复杂的代码,这些代码无法确定入口和出口。这种类型的代码不太实用,容易出现错误。

    • 在另一个极端,有一种游戏,一种竞赛,真的,叫做“代码高尔夫” 3 目标是用最小可能的结果可执行二进制实现指定的算法。这绝对是而不是我们在这个特定的指导方针中所做的。诸如此类的竞争是好的,只要它们不被带入系统管理的实际实践中。在 SysAdmin 的上下文中,使用最少的必要代码意味着尽可能地满足这些准则的其余部分。Code golf 则不然,因为它在追求最小化的过程中忽略了其他一切。

  8. 输出易于阅读和理解 -当需要任何输出时,用户应易于理解。对于许多程序来说,输出是它们存在的理由。

    • 充斥着与程序目的没有什么关系的消息和其他信息的输出混淆了重要的数据。输出的实际结构并不重要,只要它清楚地服务于预期的目的。
  9. 使用有意义的变量名**——我喜欢在我的命令行和 shell 编程中使用有意义的变量名。对于几年后需要调试代码的人来说,随机变量名或像$X 这样的名字没有什么意义;这包括最初编写代码的人。

    • \(AccountTotal 和\)NumberOfUsers 这样的名字远比\(A1、\)B3 更有意义。它们使得阅读代码更加容易。它们也是第二十章中“记录一切”原则的良好开端。命名良好的变量告诉程序维护人员,在调试程序时,变量如何适应程序的逻辑以及预期的值的种类。

    • 回到我负责清理的 Perl 程序,变量名是如此的随机,以至于其中几个变量名指向同一个东西。我重命名了程序中的所有变量,然后能够用一个单独的变量名代替同一个变量的其他不同名称。仅仅这一小步就在清理该特定程序方面向前迈出了一大步。

    **
    *** 遵循埃里克 S. 雷蒙的 17 Unix 规则4-这是包括系统管理员在内的所有开发人员都应该阅读和理解的 17 条规则。Raymond 在他的书《Unix 编程的艺术》中详细阐述了这些规则。 5 维基百科对这些规则有一个很好的总结(见脚注 3)。

    • 如果你认为我的列表中缺少一个重要的指导方针,它可能在雷蒙德的规则列表中。请务必阅读这些规则,因为它们适用于系统管理员和开发人员。

      * 测试一切 -这难道不明显吗?!显然不是,因为我遇到过很多显然没有经过很好测试的软件。

    • 我在思科的工作是双重的。有一段时间我是实验室经理的助理,测试部门的测试就是在那里进行的。其余时间,我是测试人员之一,负责测试 Linux 驱动的设备。

    • 测试不仅仅是运行一系列测试程序来验证被测软件可以执行其设计任务,它还确保软件在遇到意外输入时不会失败。黑客用来获得对由软件运行的计算机和其他设备的未授权访问的最常见的漏洞之一是软件不能处理意外输入。

    • 我做的其他测试是简单地阅读文档和代码,以确定代码是否符合文档中概述的规范。如果没有,我不得不使它失败,或者开发团队不得不获得一个异常,这是很少见的。

    • 在审查代码和文档时,我所做的一部分工作是确保代码的设计支持 Linux 理念和文档完善的标准,如文件系统分层标准,以及为确保所有 Linux 发行版使用的一致性而创建的标准。

      * 清除 cruft - Cruft 是程序中从未使用过的所有旧代码。许多程序随着时间的推移而发展,有时曾经有用的代码不再需要。当我修改自己的脚本或添加新的特性或选项时,我有时会发现自己的代码和变量不再被使用,需要清理掉。

    **

**遵循这些准则将有助于确保您编写的代码易于阅读和修改。它看起来很好,运行也很好。会很优雅的。

修复我的网站

现在你已经知道我使用 WordPress 来托管我自己的网站和其他网站。我使用它是因为它是免费的开源软件,能够很好地完成任务并提供很大的灵活性。然而,事情确实会出错。你可以指望它。在这个特殊的例子中,我在不同的网站上遇到过两次这个问题,现在已经修复了两次。

这个问题的症状是令人费解的,直到我确定了来源。在博客页面上看起来一切正常,这是我的 both.org 网站的主页。这个问题只在我试图显示这个网站的任何静态页面时才出现。

静态页面显示主题元素,如顶部横幅和网站名称。每一页都显示了正确的标题,但没有显示内容。这是一个没有内容的页面。我试图改变主题,但没有成功,这意味着问题不在于主题本身。我安装了另一个 WordPress 实例,并将其指向 both.org 网站现有的 MySQL 数据库。症状没有改变或消失。

唯一需要检查的地方是 MySQL 数据库。太糟糕了,我忘记了mysqlcheck工具;我可能会很容易地解决这个问题。幸好我忘记了mysqlcheck工具,因为我学到了比其他方式更多的东西。

这个问题很容易解决。我将该网站的 mysql 数据库文件从我的日常备份复制到/var/lib/mysql/wordpress 目录,并重新启动 MySQL。

WordPress 可以为每个网站使用不同的 MySQL 数据库,或者为每个网站使用一个带有不同表格的 MySQL 数据库。每个网站的表都有不同的表名前缀。这个前缀在 WordPress wp-config.php 文件中为每个站点定义。

我在/var/lib/mysql/wordpress 目录中找到了正确的数据库文件集,并把它们保存到另一个位置以防万一。然后我去了我的一个几天前的备份,因为我不确定这个问题是什么时候开始的。我把备份文件复制到/var/lib/mysql/wordpress 目录,重启 mysql。在这一点上一切都很好。我可能不需要重新启动 MySQL,但是我想我可能还需要一个干净的重新启动来刷新可能在某个地方的缓存中的任何东西。

唯一可行的方法是 WordPress 和 MySQL 都打开,这样我就可以查看 WordPress 的代码以及 WordPress 和 MySQL 的配置和数据文件。我本来可以下载 MySQL 的源代码,但是不需要。对于 WordPress 我也不需要这么做,但是因为它是用 PHP 编写的,所以它是完全开放的。

MySQL 的数据文件存储在 Linux FHS 定义的位置/var,这是数据库文件!我能够找到它们,确定哪些文件属于我的网站,并轻松地用以前的备份替换它们。

另一个促成因素是,我编写的备份脚本创建了以正常格式和目录结构存储文件的备份。它不会将它们压缩成 tarballs、zip 文件,或者——更糟糕的是——某种专有的备份格式。我可以用 cd 和 cp 之类的命令行工具访问我的文件;午夜指挥官(mc)文本模式文件管理器;或者诸如 Krusader、Dolphin 等 GUI 文件管理器。当我找到我想要的备份文件时,我可以简单地将它们复制到所需的位置来替换损坏的文件。

也可以使用mysqldump命令将数据导出到一个文件中,该文件是将重建数据库的 SQL 命令的脚本。我过去曾经尝试过这种方法,发现效果相当好。两种方法都可以,但是我更喜欢我自己的方法。

WordPress、MySQL 和我的备份解决方案的优雅结合在一起,可以轻松解决手头的问题。我从解决这个问题中学到的一件事是,对于 MySQL 数据库来说,花哨的备份解决方案是不必要的。

拆卸脚

创造优雅是一项艰苦的工作。维护它会更加困难。Cruft 是程序中多余的程序和代码,旧的数据文件,以及包含已删除程序中剩余文件的目录。

作为系统管理员,清除 cruft 是我们工作的一个重要部分。我们可以搜索 cruft 的东西包括旧的或未使用的软件、我们自己脚本中的旧代码以及旧的配置和数据文件。幸运的是,我们有一些工具可以帮助我们完成这项任务。

旧的或未使用的程序

我刚刚删除了一些我不用的程序。我正在使用 KDE 应用启动器,注意到列表中有几个 Calligra 办公套件程序。我从来不用 Calligra,更喜欢 LibreOffice。它被默认安装在 Fedora 上,我在几个月前测试的时候用了它。因为我永远不会在我的生产工作中使用它,所以我决定删除它。

但是我们如何主动搜索不用的程序呢?有一种方法可以找到所谓的孤儿——任何其他程序都不需要的程序。这种情况通常很少,所以使用我们必须尝试找到它们的工具不会有什么坏处。对于此任务,我们使用rpmorphan实用程序列出 RPM 软件包,这些软件包不依赖于主机上安装的任何其他软件包。

实验 17-1

如果 rpmorphan 包还没有安装,请安装它。

[root@testvm1 ~] dnf -y install rpmorphan

列出孤立的软件包。

[root@testvm1 ~]# rpmorphan
liberation-sans-fonts
liberation-serif-fonts
libertas-usb8388-firmware
libkolab
libsss_autofs
libsss_sudo
libyui-mga-gtk
libyui-mga-qt
libyui-qt-graph
[root@testvm1 ~]#

您的孤立包列表将与我的不同。我的测试虚拟机上的一些“孤立”包可能可以删除,但我真的需要额外的字体。我也不能说在没有很好的研究的情况下是否可以安全地删除其他包,但是我确实查看了 libkolab 包,它看起来可以从我的 VM host 上安全地删除。如果已经安装了它,请将其删除,然后重新安装;如果还没有安装,请安装它,这样我们可以看到另一个选项。

[root@testvm1 ~]# dnf -y remove libkolab ; dnf -y install libkolab

让我们使用 rpmorphan 的时间函数来识别最新的包。首先让我们看看一天前安装的孤儿。

[root@testvm1 ~]# rpmorphan -install-time +1
liberation-sans-fonts
liberation-serif-fonts
libertas-usb8388-firmware
libsss_autofs
libsss_sudo
libyui-mga-gtk
libyui-mga-qt
libyui-qt-graph

注意 libkolab 不在这个列表中。现在找到不到一天前安装的孤儿。

[root@testvm1 ~]# rpmorphan -install-time -1
libkolab

我们看到的唯一孤儿是 libkolab。如果在过去的一天里安装了其他软件包,它们也会被发现并被删除。

rpmorphan 工具有许多有趣的选项,使我们能够做一些事情,比如定位那些超过某个日期的孤立包。它还可以查找比特定日期更新的包。后一个选项允许我们删除为了测试而添加的包。

阅读手册页,了解更多关于一些有趣选项的信息。您会看到它也有一个 GUI 选项,但我更喜欢命令行界面。这个程序说明了许多程序员喜欢遵循的一个重要考虑。它确实与埃里克·雷蒙德的分离法则有关。 6 在这种情况下,程序员将程序的逻辑和功能方面从用户界面中分离出来。这种逻辑与用户界面的分离允许他们创建一个命令行界面和两个图形界面,一个基于 tk,另一个基于 curses。

小心!

不要乱拆孤儿包。这可能会导致删除所需的包。他们是孤儿并不意味着不需要他们。删除孤立包时要谨慎。rpmorphan 工具只允许我们识别应该进一步调查的包,以便我们可以确定删除它们是否真正安全。

deborphan工具可以用于 Debian 发行版。事实上,rpmorphan 是基于 deborphan。rpmorphan 工具仅定位孤立项,而不会删除它们。如果您决定删除任何孤立包,您可以使用包管理器,如 yum 或 dnf。

这些工具无法发现我们希望从系统中删除的所有软件包。找到孤儿是一回事,但是许多没有显示为孤儿的包也可能被删除。例如,我在本章前面提到的 Calligra office 套件不会显示为孤儿,LibreOffice 或许多其他用户级应用也不会。有时删除这些大程序可以恢复大部分磁盘空间。

您可以使用桌面的应用启动器来定位您从不使用的用户级包。这也可以帮助你找到一些你从来不使用的 GUI 管理工具。

小心!

不要试图使用软件包管理器的-y 选项删除软件包。这可能会导致删除许多您不想删除的包。当您的软件包管理器显示如果您回复“y”它将删除的软件包列表时,请务必仔细检查该列表。检查列表后,您可以选择“y”或“n”。这样安全多了。

如果您决定删除找到的软件包,请注意不要删除其他需要的软件。我总是使用不带-y 选项的 package removal 命令,该命令会不停地删除所有依赖于我要删除的包的包,以及我要删除的包所依赖的包。请务必检查您的软件包管理员准备删除的软件包列表,如果有您认为不应该删除的软件包,请回答“否”。

我曾经试图移除一个我认为不需要的包。作为依赖项被删除的软件包有数百个,它们会把 KDE 桌面从我的系统中完全删除。那绝对不是我想做的。

脚本中的旧代码

在脚本中寻找 cruft 代码也是系统管理员至少应该偶尔承担的任务。清除未使用的代码和定位语法错误可能具有挑战性,但是有一些工具可以帮助我们。

shellcheck实用程序就像 C 和其他语言的 lint 7 。它扫描为 bash 和 bash 类 shells、sh、dash 和 ksh 编写的脚本,寻找可以改进的语法和语法。一如既往,你可以选择是否做出建议的改变。

让我们看看这个工具是如何工作的。

实验 17-2

让我们从安装 ShellCheck 包开始——是的,用所示的大写字母。

[root@testvm1 student]# dnf -y install ShellCheck

现在让我们使用 shellcheck 检查 shell 脚本模板。

[student@testvm1 ~]$ shellcheck script.template.sh | less

我收到了许多类似这样的 SC2086 错误。

In script.template.sh line 92:
   if [ $verbose = 1 ]
        ^-- SC2086: Double quote to prevent globbing and word splitting.

shellcheck 实用工具有点过分热心于让我们在变量周围加上双引号。有一些边缘情况 8 时,这可能是一个问题,但我自己从来没有遇到过。因此,在检查以确保我们没有这些边缘情况之一后,我们可以在下一个命令中排除这些错误。

[student@testvm1 ~]$ shellcheck --exclude SC2086 script.template.sh

In script.template.sh line 152:
RC=0
^-- SC2034: RC appears unused. Verify it or export it.

In script.template.sh line 153:
Test=0
^-- SC2034: Test appears unused. Verify it or export it.

In script.template.sh line 160:
if [ `id -u` != 0 ]
     ^-- SC2046: Quote this to prevent word splitting.
     ^-- SC2006: Use $(..) instead of legacy `..`.

现在更容易看到一些可以从代码中删除的未使用的变量。我们还看到了一些关于 if 语句的语法建议。

现在您可以看到 shellcheck 突出显示的几个问题。使用此信息进行您想要的任何更改。

除了shellcheck所能告诉你的关于语法、孤儿变量和其他事情之外,有时你只需要浏览代码。例如,shellcheck没有发现的一种 cruft 是多余的过程,这些过程不会在脚本中的任何地方被调用。如果不需要,也可以删除。

您是否在脚本模板中看到了一个未使用的过程?有一个SelectPkgMgr()没有在模板中使用,并且shellcheck没有发现它是多余的。

旧文件

有时旧软件不再需要时就会被删除。在许多情况下,软件包删除过程会留下它们的用户级配置文件。这些通常是我们在主目录中找到的隐藏的“点”文件。

这些遗留下来的配置文件的好处是,如果软件包被重新安装,我们将不会丢失我们的个人配置。糟糕的是,经过很长一段时间,这些文件会大量积累。

例如,我最近删除的 Calligra 的个人配置文件仍然位于我的主目录中。

旧的数据文件也留在我们的硬盘上,它们可能已经没有任何用处了。这通常是因为我们很少花时间评估我们拥有的所有文件,以确定它们是否可以删除、归档或保留。找到旧文件的一个简单方法是使用find命令来确定文件被访问的最后时间。

实验 17-3

首先,让我们创建几个旧文件。我们用触摸命令来做。如果没有参数,touch 会将 atime、mtime 和 ctime 全部设置为当前系统时间。首先让我们使用stat命令来查看您在之前的实验中创建的文件之一 file0.txt 的属性。如果您没有这个文件,现在就创建它。

[student@testvm1 ~]$ stat file0.txt
  File: file0.txt
  Size: 15            Blocks: 8          IO Block: 4096   regular file
Device: fd03h/64771d    Inode: 393236      Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1001/ student)  Gid: ( 1001/ student)
Context: system_u:object_r:user_home_t:s0
Access: 2018-02-02 15:39:56.415630341 -0500
Modify: 2018-01-27 11:41:36.056367865 -0500
Change: 2018-01-28 12:15:03.176000000 -0500
 Birth: -

这显示了文件的当前访问、修改和更改时间(atime、mtime 和 ctime)。它们可能相同,但也可能不同,除非您刚刚创建了该文件。现在不使用任何选项来触摸文件,将这三个属性设置为当前时间。然后再次检查时间。

[student@testvm1 ~]$ touch file0.txt
[student@testvm1 ~]$ stat file0.txt
  File: file0.txt
  Size: 15            Blocks: 8          IO Block: 4096   regular file
Device: fd03h/64771d    Inode: 393236      Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1001/ student)  Gid: ( 1001/ student)
Context: system_u:object_r:user_home_t:s0
Access: 2018-02-23 10:28:25.794938943 -0500
Modify: 2018-02-23 10:28:25.794938943 -0500
Change: 2018-02-23 10:28:25.794938943 -0500
 Birth: -
[student@testvm1 ~]$

请注意,这三个时间现在完全相同。这里我们使用touch来设置 atime——上次访问文件的时间——更早。下面命令中的-a 选项告诉 touch 命令只设置 atime。t 选项使用以下时间戳将日期和时间设置为 2013 年 7 月 15 日 16:45:23。

[student@testvm1 ~]$ touch -a -t 1307151645.23 file0.txt
[student@testvm1 ~]$ stat file0.txt
  File: file0.txt
  Size: 15            Blocks: 8          IO Block: 4096   regular file
Device: fd03h/64771d    Inode: 393236      Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1001/ student)  Gid: ( 1001/ student)
Context: system_u:object_r:user_home_t:s0
Access: 2013-07-15 16:45:23.000000000 -0400
Modify: 2018-02-23 10:28:25.794938943 -0500
Change: 2018-02-23 10:48:13.781669926 -0500
 Birth: -

请注意,ctime 也已更改。ctime 是最后一次更改文件索引节点,并且是在我们设置 atime 时发生的。

到目前为止,我们所做的只是为实验设置了条件。现在,我们可以使用 find 命令根据 atime 查找旧文件。使用如下所示的 find 命令查找超过两年的文件。find 命令上的 atime 选项使用以天为单位的年龄,实际上是以“现在”开始的 24 小时周期。因此,我们需要使用 365*2 = 730 天作为我们的时间段。我们将 atime 设置为五年前,因此测试文件应该在这个测试中出现。

[student@testvm1 ~]$ find . -atime +730
./file0.txt

file0.txt 文件按预期显示。您还可以显示最近超过 730 天被访问的文件。通过 sort 实用程序传输结果,以便更容易看到 file0.txt 不在列出的列表中。

[student@testvm1 ~]$ find . -atime -730 | sort
.
./.bash_history
./.bash_logout
./.bash_profile
./.bashrc
./.cache
./.cache/mc
./.cache/mc/Tree
./.config
./.config/mc
./.config/mc/ini
./error.txt
./file1.txt
./file2.txt
./file3.txt
./file4.txt
./file5.txt
./file6.txt
./file7.txt
./file8.txt
./file9.txt
./good.txt
./index.cgi
./.lesshst
./.local
./.local/share
./.local/share/mc
./.local/share/mc/history
./.mozilla
./.mozilla/extensions
./.mozilla/plugins
./mymotd
./perl.index.cgi
./script.template.sh
./test1.html
./test1.txt
./.viminfo

find命令可以根据大小、权限、名称和其他标准来定位文件。但是,它所能做的只是找到值得进一步研究的文件。这一调查是以任何程度的把握知道应该如何处理找到的文件的唯一途径。这通常意味着调查内容,但有时也可以根据文件名或文件位置来确定处理方式。

使用 find 命令的一个潜在问题是,最近从备份中恢复的文件没有保留它们的属性。这可能会使旧文件看起来比实际更新,并妨碍轻松识别最旧的文件。在这种情况下,再次需要使用基本工具,如ls命令或您最喜欢的文件管理器来搜索文件,打开它们检查内容,如果不再需要,就删除它们。

另一个可用于定位可能被归档或删除的文件的标准是大小。有两种方法可以做到这一点。我们可以使用find命令或du命令。find 命令让我们对结果有了更多的控制,因为我们可以组合参数并做一些有趣的事情,例如查找所有大于 15MB、最后一次访问是在五年多以前,并且属于特定用户的文件。在下一个实验中,我们将首先查看du命令,然后查看find命令。

实验 17-4

以学生用户的身份执行此实验。

我们再次需要做一些设置,以便使这个实验比学生用户的主目录中只有几个小文件的实验更有趣。首先,我们将创建~/Documents 目录(如果它不存在的话),然后我们将向其中添加一些大小不断增加的文件。

[student@testvm1 ~]$ mkdir Documents

下一个命令应该在一行中输入。它会在~/Documents 目录中创建 100 个数据量不断增加的文件。

[student@testvm1 ~]$ count=0;while [ $count -lt 100000 ]; do count=$((count+1000)); echo $count;dd if=/dev/urandom of=~/Documents/file-$count.txt bs=256 count=$count ;done

make ~/记录 PWD 并列出内容。为了简洁起见,我在这里只显示了前 20 个文件。如果你愿意,你可以去掉 head 实用程序,这样你就可以看到它们了。

[student@testvm1 Documents]$ ls -l | head -20
total 1262600
-rw-rw-r--. 1 student student 25600000 Feb 23 15:32 file-100000.txt
-rw-rw-r--. 1 student student  2560000 Feb 23 15:31 file-10000.txt
-rw-rw-r--. 1 student student   256000 Feb 23 15:31 file-1000.txt
-rw-rw-r--. 1 student student  2816000 Feb 23 15:31 file-11000.txt
-rw-rw-r--. 1 student student  3072000 Feb 23 15:31 file-12000.txt
-rw-rw-r--. 1 student student  3328000 Feb 23 15:31 file-13000.txt
-rw-rw-r--. 1 student student  3584000 Feb 23 15:31 file-14000.txt
-rw-rw-r--. 1 student student  3840000 Feb 23 15:31 file-15000.txt
-rw-rw-r--. 1 student student  4096000 Feb 23 15:31 file-16000.txt
-rw-rw-r--. 1 student student  4352000 Feb 23 15:31 file-17000.txt
-rw-rw-r--. 1 student student  4608000 Feb 23 15:31 file-18000.txt
-rw-rw-r--. 1 student student  4864000 Feb 23 15:31 file-19000.txt
-rw-rw-r--. 1 student student  5120000 Feb 23 15:31 file-20000.txt
-rw-rw-r--. 1 student student   512000 Feb 23 15:31 file-2000.txt
-rw-rw-r--. 1 student student  5376000 Feb 23 15:31 file-21000.txt
-rw-rw-r--. 1 student student  5632000 Feb 23 15:31 file-22000.txt
-rw-rw-r--. 1 student student  5888000 Feb 23 15:31 file-23000.txt
-rw-rw-r--. 1 student student  6144000 Feb 23 15:31 file-24000.txt
-rw-rw-r--. 1 student student  6400000 Feb 23 15:31 file-25000.txt

du -a命令简单地列出了文件及其大小,以及每个目录中所有文件的累积大小。我们可以使用它轻松快速地找到最大的文件和包含最大数据量的目录。我们通过sort实用程序运行结果,得到一个按数字排序的列表,最大的文件和目录在最后。在这种情况下,我只显示列表中的最后 20 项。

[student@testvm1 ~]$ du . -a | sort -n | tail -20
20752   ./Documents/file-83000.txt
21000   ./Documents/file-84000.txt
21252   ./Documents/file-85000.txt
21500   ./Documents/file-86000.txt
21752   ./Documents/file-87000.txt
22000   ./Documents/file-88000.txt
22252   ./Documents/file-89000.txt
22500   ./Documents/file-90000.txt
22752   ./Documents/file-91000.txt
23000   ./Documents/file-92000.txt
23252   ./Documents/file-93000.txt
23500   ./Documents/file-94000.txt
23752   ./Documents/file-95000.txt
24000   ./Documents/file-96000.txt
24252   ./Documents/file-97000.txt
24500   ./Documents/file-98000.txt
24752   ./Documents/file-99000.txt
25000   ./Documents/file-100000.txt
1262604 ./Documents
1262780 .

结果以千字节为单位。请注意,由于目录中包含的文件,这些目录在底部附近排序。使用du时,很难将目录与文件分开。

find命令可以更具体一点。让我们找到所有大于 20MB 的文件。

[student@testvm1 ~]$ find . -size +20M
./Documents/file-93000.txt
./Documents/file-94000.txt
./Documents/file-90000.txt
./Documents/file-92000.txt
./Documents/file-89000.txt
./Documents/file-88000.txt
./Documents/file-91000.txt
./Documents/file-98000.txt
./Documents/file-84000.txt
./Documents/file-85000.txt
./Documents/file-83000.txt
./Documents/file-97000.txt
./Documents/file-100000.txt
./Documents/file-96000.txt
./Documents/file-95000.txt
./Documents/file-82000.txt
./Documents/file-87000.txt
./Documents/file-86000.txt
./Documents/file-99000.txt
[student@testvm1 ~]$

注意,find命令没有列出文件大小。我们可以在find命令中添加一些代码来实现这一点。

[student@testvm1 ~]$ find . -size +20M -exec ls -l {} \;
-rw-rw-r--. 1 student student 23808000 Feb 23 15:32 ./Documents/file-93000.txt
-rw-rw-r--. 1 student student 24064000 Feb 23 15:32 ./Documents/file-94000.txt
-rw-rw-r--. 1 student student 23040000 Feb 23 15:32 ./Documents/file-90000.txt
-rw-rw-r--. 1 student student 23552000 Feb 23 15:32 ./Documents/file-92000.txt
-rw-rw-r--. 1 student student 22784000 Feb 23 15:32 ./Documents/file-89000.txt
-rw-rw-r--. 1 student student 22528000 Feb 23 15:32 ./Documents/file-88000.txt
-rw-rw-r--. 1 student student 23296000 Feb 23 15:32 ./Documents/file-91000.txt
-rw-rw-r--. 1 student student 25088000 Feb 23 15:32 ./Documents/file-98000.txt
-rw-rw-r--. 1 student student 21504000 Feb 23 15:32 ./Documents/file-84000.txt
-rw-rw-r--. 1 student student 21760000 Feb 23 15:32 ./Documents/file-85000.txt
-rw-rw-r--. 1 student student 21248000 Feb 23 15:32 ./Documents/file-83000.txt
-rw-rw-r--. 1 student student 24832000 Feb 23 15:32 ./Documents/file-97000.txt
-rw-rw-r--. 1 student student 25600000 Feb 23 15:32 ./Documents/file-100000.txt
-rw-rw-r--. 1 student student 24576000 Feb 23 15:32 ./Documents/file-96000.txt
-rw-rw-r--. 1 student student 24320000 Feb 23 15:32 ./Documents/file-95000.txt
-rw-rw-r--. 1 student student 20992000 Feb 23 15:32 ./Documents/file-82000.txt
-rw-rw-r--. 1 student student 22272000 Feb 23 15:32 ./Documents/file-87000.txt
-rw-rw-r--. 1 student student 22016000 Feb 23 15:32 ./Documents/file-86000.txt
-rw-rw-r--. 1 student student 25344000 Feb 23 15:32 ./Documents/file-99000.txt
[student@testvm1 ~]$

我们现在有了主目录中最大文件的列表。在这种情况下,它们都在~/Documents 目录中。

我们又一次拥有了可以帮助我们识别主目录中最大文件的工具。仍然需要一些判断来决定这些文件中的哪些(如果有的话)可以被删除或存档。

最后一句话

要做到本章和我给你指出的参考文献中讨论的每一件事并不总是可能的。如果我们能做到,那就太好了,但在现实生活中,我们不可能总是这样做。我们的脚本永远不会完全摆脱 cruft,它们永远不会达到最高水平的优雅。

这一章的标题应该暗示了这一点。优雅是我们努力追求的目标,但我们可能永远也不会达到这样的巅峰:所有的 cruft 都被删除,所有的代码都尽可能地高效,为我们的代码添加了准确数量的清晰简洁的完美注释,所有的编程规则和建议都被遵循。

由于多种原因,这是不可能的。我最常遇到的两个问题是,PHB 不关心我们,也不允许我们有时间,以及这些指导方针中的一些——至少在某种程度上——是冲突的。

我们确实有一些工具可以帮助定位脚本中的 cruft 和硬盘上的文件。虽然这些工具可能有所帮助,但它们并不完善,只能做这么多。作为系统管理员,在我们的代码和目录中搜索 cruft 真的取决于我们;有时,这意味着手动浏览,看看有什么可以消除。这很费时间,我不喜欢这样做,但确实需要这样做。

使用这些工具在我们的系统上找到我们的主目录(或其他非 root 用户的主目录)中最大和最老的文件,这可能是清理 cruft 的第一步。它给了我们一个起点,让我们可以用最少的努力获得最好的结果。删除最大和最早的文件后,继续寻找较小和较新的文件来删除或移动到归档存储变得不太有效。

**

十八、力求简单

UNIX 基本上是一个简单的操作系统,但是你必须是一个天才才能理解这种简单性。 1

—丹尼斯·里奇

我绝不会屈尊不同意 Unix 的创造者之一。然而,自从我开始使用 Unix 和 Linux 以来,我自己的观点已经发生了变化。Linux 哲学的原则帮助我巩固了我对这样一个事实的理解,即 Linux 是简单的,并且这种简单是由哲学所阐明的。

本书中的许多原则相互交叉并相互加强。我毫不怀疑你已经开始自己看到了这一点。在第十七章中,我讨论了优雅,但有一件事我没有列出,那就是简单,尽管它在书中顺便提到了。我相信简单的概念应该在系统 管理员的 Linux 哲学中有自己的一章。

在这一章中,我们寻找 Linux 的简单性。

数字的复杂性

是的,GNU/Linux 表面上很复杂。据我所知的一本书, Linux 简而言之、、2 、包含了 372 个 Linux 命令的列表。是的,我数过了。另一本书,我最喜欢的初学者的书,Linux、命令、编辑器和 Shell 编程实用指南3 涵盖了“… 98 个实用程序…”。

但是这些数字与我想出的另一个数字相比是微不足道的。实验 1 展示了一种估计 Linux 计算机上命令总数的方法。大多数作为命令行命令的可执行文件都位于/usr/bin 目录中,因此计算该目录中文件的数量是一个非常好的估计。

实验 18-1

以学生用户的身份执行此实验。确定/usr/bin 中有多少可执行文件。

[student@testvm1 ~]$ ls  /usr/bin | wc -w
2635

是的,这是一大堆命令。当然你看到的数字会不一样。我的技术审查人员本·科顿告诉我,他的笔记本电脑上的/usr/bin 中有 1,992 个文件。您可以看到,根据您拥有的发行版和已安装的软件包,会有一个范围。

我用来创建和测试这些实验的测试虚拟机是一个非常基本的安装,带有 KDE 和 MATE 桌面以及一些应用,如 LibreOffice。该虚拟机有 2,633 个可执行 Linux 文件,其中大部分是 CLI 命令。对于刚刚学习 Linux 的人来说,这些数字看起来太多了。我刚开始做系统管理员的时候,他们就是这么对我的。

大约在 1996 年或 1997 年,当我刚开始学习 Linux 的时候,我拿起了几本关于 Linux 的书——当时并没有那么多可用的——并且发现了当时对我来说难以想象的大量命令。我认为我不可能学会所有这些命令。

当我看到标题为“您将实际使用的 77 个 Linux 命令和实用程序”, 4 和“50 个最常用的 UNIX / Linux 命令(带示例)”的文章时,我感到很害怕这些标题暗示着你必须记住一些命令,或者知道大量的命令是很重要的。

我确实读过许多这样的文章,但是我通常寻找新的和有趣的命令:可以帮助我解决问题或简化命令行程序的命令。

简单的基础

虽然我妈妈认为我是个天才,但我真的不是。但是我很执着。我从来没有尝试去学习所有的 Linux 命令,不管你会想出多少个“所有”的总数。

我只是开始学习在任何给定的时刻,无论手头有什么项目,我都需要的命令。我开始学习更多的命令,因为我接受个人项目和工作项目,这些项目将我的知识延伸到极限,并迫使我找到以前我不知道的命令,以完成这些项目。随着时间的推移,我的命令越来越多,在解决问题时,我越来越熟练地应用这些命令。我开始寻找报酬越来越高的工作,让我玩我最喜欢的玩具 Linux。

随着我对管道和重定向、标准流和标准 I/O 的了解,以及对 Unix 哲学和 Linux 哲学的了解,我开始理解命令行是如何以及为什么让 Linux 和核心实用程序变得如此强大。我学到了编写命令行程序的优雅之处,这些程序以惊人的方式操纵数据流。

我还发现,有些命令即使没有完全过时,也很少使用,而且只在不寻常的情况下使用。仅仅因为这个原因,找到一个 Linux 命令列表并记住它们是没有意义的。作为一名系统管理员,学习许多可能永远都不需要的命令并不是对时间的有效利用。

这里的简单性是了解你需要什么来完成手头的任务。将来会有大量的任务需要您学习其他命令。当你需要的时候,总有办法发现和学习这些命令。我发现在需要的时候发现和学习新的命令对我来说非常有效。几乎任何新项目,包括写这本书,都会导致寻找新的命令来学习。

永无止境的简化过程

然而,仅仅因为一个解决方案有效并不意味着你应该停止寻找更好的方法。系统管理员的一个共同特点是,我们总是在寻找更好的方法来做我们已经在做的事情。有时我发现一个我以前不知道的命令,我意识到它比我已经用来完成任务的一个、两个或更多的命令更合适。

在我十多年前编写的一个程序中,我在一个管道中使用了一系列以dmidecode开头的命令来确定系统的硬件架构是 32 位还是 64 位。这很麻烦,但大部分工作。我后来发现了一个 Linux 命令,arch,它可以完成以前需要几个命令才能完成的工作。我修改了我的剧本;结果没有改变,但是程序更简单、更高效、更优雅。

简单与性能或效率无关——至少没有直接关系——它更关乎优雅。通过简化,我的程序变得更有效率,性能也提高了。这就是优雅。

简单是一个永无止境的过程。它从未停止,因为我总是在学习新的东西和新的方法来应用我已经知道的东西。

简单的程序做一件事

我们大多数直接与计算机打交道的人确实喜欢找乐子。早期的计算机程序员也不例外。他们写了大量的程序,让我们都有一些严肃的乐趣。我们极客也只是想找乐子!

大约在 1970 年,我是俄亥俄州托莱多一家小公司的夜间电脑操作员之一。在所有真正的工作完成之后,我们将在 IBM 1401 大型机上获得一些乐趣。我们会玩像井字游戏这样的游戏,或者打印不应该在这里复制的 ASCII 艺术页面。井字游戏很有趣,但在那台旧电脑上玩也很有趣,也很有挑战性。计算机总是将第一步棋作为“X ”,并在一张计算机纸上打印出最终的 3×3 矩阵。人类玩家必须打开前面板上的一个感应开关来指示他们想要放置“O”的方块的号码,然后按下一个按钮来告诉计算机继续运行程序。那些是美好的旧时光。

早期的 Unix 程序员给了我们一些有趣的东西,比如 adventure、fortune 和 cowsay。最后两个可以用来说明简单性。这种简单性是因为这两个程序都被设计为只做一件事。fortune 程序将随机的运气打印到 STDOUT,cowsay 从 STDIN 获取文本字符串,并将它们显示在一只卡通牛的语音气球中。

使用您的软件包管理器安装 fortune 和 cowsay,因为它们不太可能已经安装在您的计算机上。在 Fedora 的当前版本上,它们是“fortune-mod”和“cowsay”。对于 Fedora 和其他发行版的早期版本,您可能需要使用“fortune”作为包名。

实验 18-2

安装 fortune-mod 和 cowsay,以防它们还没有安装。以 root 身份做这部分实验。

[root@testvm1 ~]# dnf -y install fortune-mod cowsay

本实验的其余部分应该以学生用户的身份进行。现在运行几次 fortune 命令来查看结果。

[student@testvm1 ~]$ fortune
Vulcans believe peace should not depend on force.
-- Amanda, "Journey to Babel", stardate 3842.3

我承认在这个特殊的结果显示出来之前,我尝试了几次。如果您想检查,现在/usr/bin 中可能还有一两个文件。

没关系——继续玩一会儿财富程序吧。

完成了吗?那么让我们继续和考赛玩吧。cowsay 程序需要一个文本串作为输入,所以做实验 18-3 中所示的事情。cowsay 获取文本字符串并将其放入奶牛的语音气球中。看起来很傻,但它会让人上瘾。只是当你使用它的时候,要小心谁在你身后看着你。

实验 18-3

让我们单独尝试一下 cowsay 程序。

[root@testvm1 ~]# cowsay hello world!
 ______________
< hello world! >
 --------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

这个玩一会儿也是可以的。我可以看到有人可能在 shell 程序中使用 cowsay 而不是 echo 来打印消息,但是 cowsay 不维护原始消息文本的列格式;它只是把所有东西混在一起。

我们有两个小程序,每个程序只做一件事。让我们把它们放在一起,利用 cowsay 程序在 STDIN 上接受输入这一事实。实验 18-4 展示了如何做和结果。在这里,我不得不再次承认,为了得到这个结果,我运行了几次程序,但这是其中一次运行的真实输出。

实验 18-4

fortunecowsay的管道输出。

[student@testvm1 ~]$ fortune | cowsay
 ________________________________________
/ But I have a holy crusade. I dislike   \
| waste. I dislike over-engineering. I   |
| absolutely detest the "because we can" |
| mentality. I think small is beautiful, |
| and the guildeline should always be    |
| that performance and size are more     |
| important than features.               |
|                                        |
\ - Linus Torvalds on linux-kernel       /
 ----------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

将两个简单的程序结合起来,每个程序都很小,每个程序都做一件事,这样就可以创建更复杂的程序。我也花了一段时间才得到这个结果。拼写错误在原文中。

fortune 和 cowsay 都有简单的接口,他们都执行一个单一的任务,他们做得很好,他们使用 STDIO。它们都有一些命令行选项,可以用来稍微修改它们的行为,但是一旦你像实验 18-4 中所示的那样一起使用它们,就没有什么其他的东西可以学习了。如果您想了解他们为数不多的命令行选项,可以查看他们的手册页。

简单的程序很小

为了看看这两个程序有多小,请运行实验 18-5 中的命令来查找信息。两个都不是很大。小程序易于理解和维护。

实验 18-5

这个命令让我们找到cowsayfortune程序的大小。

[student@testvm1 ~]$ ls -l `which cowsay` `which fortune`
-rwxr-xr-x 1 root root  4460 Nov 20 11:20 /usr/bin/cowsay
-rwxr-xr-x 1 root root 28576 Aug  2 19:54 /usr/bin/fortune

这些程序之所以小,是因为它们都只做一件事。向这些程序中的任何一个添加更多的功能都会显著增加它们的大小,并使它们更难维护。此外,这有什么意义呢?这两个程序是完美的,因为它们都符合为它们设置的要求。

现在以同样的方式考虑其余的 GNU/Unix/Linux 实用程序。这个ls程序应该完成什么?它唯一的功能是列出一个目录中包含的文件,记住目录本身就是文件。它可以通过使用一个或多个选项,或者根本不使用选项,以多种不同的方式完成这项任务。

如果没有选项,ls命令只列出当前目录(PWD)中的非隐藏文件名,并且在每行输出中列出尽可能多的文件名。l 选项是一个很长的列表,在一个易于阅读的漂亮的柱状列表中显示了文件的权限、大小和其他数据。-a 选项显示所有文件,包括隐藏的文件。r 选项列出文件,递归遍历每个子目录,并列出每个子目录中的文件。如果没有参数,ls命令会列出 PWD 中的文件。使用不同的目录路径作为参数,它可以列出其他目录中的文件。该参数的其他变体允许您列出特定的文件。

ls实用程序有许多其他有趣的选项和参数变体,可以与它一起使用。阅读ls的手册页,查看所有的可能性。

注意,文件 globbing 是由 shell 而不是由ls命令处理的。因为 shell 为所有以文件名作为参数的程序和脚本处理文件 globbing,所以这些程序都不需要这样做。shell 将与 globs 匹配的文件名扩展到程序和脚本操作的文件列表中。这也是简单。为什么要在每个程序中包含文件打包功能,而它只需要在一个地方,即 shell 中。

关于ls实用程序,您应该注意的是,每个选项、每个参数变化都有助于生成文件列表。就是这样——这就是它所做的一切,列出文件。这就是它的简单之处,它只做一件事,而且做得很好。给这个程序增加更多的功能是没有意义的,因为它不需要这些功能。

简单和哲学

起初我希望这样一个技术上不健全的项目会失败,但我很快意识到它注定会成功。只要有足够的决心,软件中的几乎任何东西都可以实现、出售甚至使用。一个单纯的科学家说什么都无法抵挡一亿美元的洪流。但是有一种品质不能用这种方式买到,那就是可靠性。 可靠性的代价是追求极致的简单性。 这是非常富有的人最难支付的价格。 6

— C. A. R. Hoare, 7 写关于编程语言 PL/I 8 (重点是我的。)

我遇到的许多更有趣的软件问题都涉及到现有代码的简化——尤其是我自己的代码。给程序增加新的功能会增加它的复杂性。一个快速的新特性被添加到现有的代码中,并被用来满足最后期限,这增加了复杂性。

最难做的事情之一是降低代码的复杂性。但从长远来看,这是有回报的。

简化我自己的程序

我自己编写的一个程序,一个 bash shell 脚本,在一个基本的 Fedora 安装程序不止一次失控后,我编写它来执行许多任务。我已经在第九章“自动化一切”中提到了这个后安装程序但现在我需要讨论它的阴暗面。

由于 Fedora 版本之间的变化,程序的需求也发生了变化。需要修改程序来安装一些在默认安装过程中不再安装的软件包。有时我需要添加代码来删除自动安装的包,因为我不想要或不需要它们。

添加新代码来做这些事情增加了程序的复杂性。在某些情况下,当程序初始化时,我添加了更多的选项进行评估,以便我的选项保持开放——可以说——关于我的程序所需的更改。经过几年的时间,这个项目变得很大,资金也很充足。我最近花了一些时间使用shellcheck实用程序和我自己对代码的观察来删除 cruft——大部分是未使用和不再需要的过程——这将代码的大小减少了几百行。

简化他人的程序

谈论我如何修复别人的代码总是更有趣。我过去的一项咨询工作涉及到对一组现有的 Perl 程序进行几乎完全的重写。在一台小型英特尔服务器上运行着大约 25 个这样的程序。由于杂乱的代码和缺乏注释,维护这些程序来添加新功能、定位和修复错误变得不可能了。我被分配的任务是修复这些程序的缺陷,并给它们增加一些额外的功能。

当我开始尝试理解复杂得吓人的代码时,很明显我的首要任务是简化代码。在对代码进行了大量的注释并修复了一些错误之后,我开始收集一些已经被插入到两个或更多程序中的代码,并将它们收集到 Perl 库中。这使得修复问题变得更加容易,因为它们只需要在一个地方修复——库。我理顺了其他代码,简化了常见的执行路径。

修改后的程序更快,更小,更容易维护。问题可以在几小时和几分钟内找到并解决,而不是几天。

未注释的代码

我翻遍了我的个人档案,找到了图 18-1 中的代码。我根本不知道它是从哪里来的。我不知道为什么我还留着它。它没有任何评论。少数几个变量名的长度不仅仅是几个字符,但是它们几乎没有告诉我们程序的目的或者它应该如何工作。

即使是图 18-1 中的“用法”程序——显然是“帮助”功能——也不是特别有用,因为它只显示了一点关于用法语法的内容,实际上仍然没有说明程序的目的。嗯——除了程序名。这表明它可能与 USB 库有关。这是可以理解的吗?我不这么认为。我花了不少时间想弄明白。

img/462716_1_En_18_Fig1a_HTML.png img/462716_1_En_18_Fig1b_HTML.png

图 18-1

这段代码是做什么的?

我看到的几个变量在第二个 case 语句中被赋值,然后用于确定通过底部的一系列 if 语句的流程。事实上,可以通过删除所有 if 语句并将 echo 语句移到 case 语句的匹配部分来重构这段代码,使之更简单。这将消除代码中对这些变量的需求。

我将这个脚本复制到一个虚拟机上,用不同的选项组合尝试了几次。结果如图 18-2 所示,并没有太多启发性。

img/462716_1_En_18_Fig2_HTML.png

图 18-2

运行这个程序的结果也没有帮助

一点也不像蹩脚的代码。在不知道这段代码应该做什么的情况下,我该如何修复它呢?看起来这段代码可能只是一个测试,或者是某个更大的脚本的开始,打算做些什么。这段代码的真正问题在于,它需要花费宝贵的时间来发现它显然没有做任何有用的事情。

**我最终使用了下面实验 18-6 中显示的dnf命令,发现这个脚本是 USB 开发库的一部分。我不知道它是如何出现在我的个人~/bin 目录中的。

实验 18-6

可以使用dnf命令定位到为其配置主机的某个存储库中的 RPM 包中的文件。

[root@david ~]# dnf whatprovides *libusb-config
Last metadata expiration check: 2:10:49 ago on Sat 24 Feb 2018 01:50:16 PM EST.
libusb-devel-1:0.1.5-10.fc27.i686 : Development files for libusb
Repo        : fedora
Matched from:
Other       : *libusb-config

libusb-devel-1:0.1.5-10.fc27.x86_64 : Development files for libusb
Repo        : fedora
Matched from:
Other       : *libusb-config

我们现在知道哪个 RPM 包提供了这个文件,所以让我们看看这个包是否安装在我们的主机上。

[root@david ~]# dnf list libusb-devel
Last metadata expiration check: 2:11:35 ago on Sat 24 Feb 2018 01:50:16 PM EST.
Available Packages
libusb-devel.i686         1:0.1.5-10.fc27                 fedora
libusb-devel.x86_64       1:0.1.5-10.fc27                 fedora

这些 rpm 是可用的,这意味着它们尚未安装。

在这种情况下,结果表明该脚本来自一个没有安装在我的主机上的 RPM。因为它是一个开发包,所以我也不太可能自己安装它。底线是我可以删除这个脚本,因为——至少对我来说——它是 cruft。

当然,这也是系统管理员工作的一部分。在执行一些需要的任务的脚本中找到这些无用的脚本,并去掉它们。它还包括在有用的脚本中找到无用的变量、永远不会被执行的代码行和其他垃圾,并将其删除。识别和清除积垢需要时间和一定程度的投入。

五金器具

我们已经在第十七章“追求优雅”中谈到了一些硬件在讨论简单性时,这也是一个合适的话题。毕竟,硬件是软件运行的引擎。

如今硬件并不是特别复杂。有标准的主板尺寸,ATX,迷你 ATX,微型 ATX,和扩展 ATX。大多数台式和塔式计算机机箱都是标准化的,可以接受这些尺寸中的任何一种,除了扩展 ATX。

稍加研究,就有可能买到与市场上任何标准主板兼容的 CPU 和 RAM 内存 DIMMs。GPU、SATA 和 USB 插入式适配器等其他适配器通过标准主板通用的标准化 PCI Express 总线实现。

电源是标准化的,并且都适合专门分配给它们的空间。唯一真正的区别是它们能够提供的总功率瓦数。电源连接器及其提供的电压早已标准化。

USB 和 SATA 连接器使得将设备从硬盘连接到鼠标变得非常简单和快速。在当今情况下,硬盘等设备都是标准尺寸,很容易放入为其设计的空间中。

我确实说过如今硬件并不特别复杂,但严格来说这并不正确。在主板、机箱、适配器、电源等等宏观层面上,确实如此。但是这些装置在微米和纳米水平上都变得更加复杂。随着芯片变得越来越小、越来越复杂,它们包含了越来越多的必要逻辑,使最终用户的生活变得更简单。

也许在 80 年代早期,当最初的 IBM 个人电脑首次发布时,你还不在。集成电路(IC)可能只包含现在的一小部分组件,而且它们的运行速度也只是我们现在认为理所当然的速度的一小部分,更不用说那些超频人群可以达到的速度了。

1981 年,英特尔 8088 单核 CPU 在 33 平方毫米的面积上容纳了 29,000 个晶体管。1010 核酷睿 i7 Broadwell-E 是维基百科页面脚注 10 中列出的最新英特尔 I 系列处理器,在 246 平方毫米中包含 32 亿个晶体管。这是仅 7.5 倍面积内晶体管数量的 11 万多倍。所有这些额外的能力使得在 CPU 内部完成过去手工完成的复杂任务成为可能。

在早期,集成电路比较简单,晶体管也少得多。跳线插针和 DIP 开关是配置硬件的常见和令人困惑的方式。现在,我可以将计算机引导至 BIOS 配置模式,并在 GUI 环境中进行更改。但是在大多数情况下,甚至这也不是必需的,因为硬件和操作系统几乎都是自己配置的。

Linux 和硬件

今天的 Linux 为配置硬件带来了惊人的简单性。大多数时候不需要用户干预。过去,Linux 用户经常需要为某些硬件安装设备驱动程序。现在,Linux 几乎总是为我们做所有的工作。

在第五章中,我们看了 Udev 守护进程及其机制,它使 Linux 能够在引导时识别硬件,并在引导后的任意时间热插拔硬件。让我们看一下当新设备连接到主机时会发生什么的简化版本。我在这里规定主机系统已经在 multi-user.target(运行级别 3)或 graphical.target(运行级别 5)上启动并运行。

  1. 用户插入新设备,通常是外部 USB、SATA 或 eSATA 连接器。

  2. 内核检测到这一点,并向 Udev 发送一条消息来宣布新设备。

  3. 基于设备属性及其在硬件总线树中的位置,Udev 为新设备创建一个名称(如果还不存在的话)。

  4. Udev 系统在/dev 中创建设备专用文件。

  5. 如果需要新的设备驱动程序,它会被加载。

  6. 设备已初始化。

  7. Udev 可以向桌面发送通知,使得桌面可以向用户显示新设备的通知。

将新硬件设备热插拔到正在运行的 Linux 系统并使其准备就绪的整个过程非常复杂——对于操作系统而言。对于只想插入新设备并让它工作的用户来说,这非常简单。这极大地简化了最终用户的工作。对于 USB 和 SATA 硬盘、USB 拇指驱动器、键盘、鼠标、打印机、显示器和几乎任何其他东西,作为用户,我需要做的只是将设备插入适当的 USB 或 SATA 端口,它就可以工作了。

困境

对我来说,最终目标是让最终用户尽可能地简单。我们不能忘记,我们系统管理员也是最终用户。我更喜欢完成实际工作,而不是花几个小时摆弄一个新设备,只是为了让它工作。那是旧的做事方式。但是这种新的做事方式将复杂性从等式的人的一面转移到了软件的一面。并且硬件复杂性的成倍增加也增加了软件的复杂性。

所以我们的困惑是,一方面我们被告知我们的程序应该简单,但另一方面我们应该将复杂性转移到软件中或者完全摆脱它。希望用户不需要处理它。

调和复杂性和简单性之间的矛盾是开发人员和系统管理员的任务。我们为“自动化一切”而创建的程序和脚本确实需要尽可能简单。但是他们还需要能够执行手头的任务,以便尽可能简化最终用户的任务。

计算机不可靠,但人类更不可靠。

—吉尔布不可靠性定律

当您作为系统管理员工作了一段时间后,前面那句话的真实性就显而易见了。在某些时候,我们的用户总会找到一种方法去做一些意想不到的事情,这将比我们在程序和脚本中可能做的任何事情造成更大的破坏和混乱。这意味着我们的目标必须是遵循编写小程序的基本原则,每个小程序都做好一件事,并使用 STDIO 进行交互。

让我们不要忘记最大的讽刺——我们系统管理员也是人,至少现在是这样——这使我们成为自己脚本的用户。我发现作为一名系统管理员,作为一名用户,我是自己最大的噩梦。如果我写脚本来处理我知道我会犯的粗心错误,它们将是相当可靠的。我确保我的脚本尽可能的可靠,我尽可能的简化它们,并继续努力进一步简化它们。

最后一句话

愚者忽视复杂性;实用主义者深受其害;专家回避;天才移除它。

—艾伦·珀利斯11

**

十九、使用您最喜欢的编辑器

为什么这是系统 管理员的 Linux 理念的宗旨?因为关于编辑的争论会导致大量的精力浪费。每个人都有自己最喜欢的编辑器,可能和我的编辑器不一样。那又怎样?

我使用 vim 作为我的编辑器。我用了好几年了,非常喜欢。我已经习惯了。它比我尝试过的任何其他编辑器都更符合我的需求。如果你可以这样说你的编辑——不管是哪一个——那么你就进入了编辑天堂。

二十多年前,当我开始学习 Solaris 时,我就开始使用 vi。我的导师建议我开始学习用 vi 编辑,因为它会一直存在于每个系统中。事实证明,无论操作系统是 Solaris 还是 Linux,都是如此。vi 编辑器总是在那里,所以我可以依靠它。对我来说,这行得通。

vi 编辑器也可以用作 bash 命令行编辑的编辑器。虽然命令编辑的默认选项是 emacs,但是我使用 vi 选项,因为我已经知道 vi 击键。bash 中使用 vi 样式编辑的选项可以通过在~/中添加行“set -o vi”来设置。bashrc 文件仅供您自己使用。为了全局设置 vi 选项,使用了/etc/profile.d/中的一个配置文件,这样所有用户,无论是根用户还是非特权用户,都可以将它作为 bash 配置的一部分。

其他使用 vi 编辑的工具有 crontab 和 visudo 命令;这两个都是 vi 的包装器。懒惰的系统管理员使用已经存在的代码,尤其是当它是开源的时候。为这些工具使用 vi 编辑器就是一个很好的例子。

还有许多其他的编辑器也很棒,很强大,很神奇。我还是更喜欢 vi 或者 vim。你应该使用你想要的,不要担心其他人都在使用什么。仅仅因为我使用 vim 并不意味着你也必须使用它。使用最好的编辑器对你的生产力很重要。一旦您学会了在编辑器中最常用的击键组合和命令,您就可以非常高效地编辑所有类型的文件。

不仅仅是编辑

这一章不仅仅是编辑。这实际上是关于使用为你工作的工具,关于最好的编辑器的讨论是关于所有类型工具的讨论的原型。

关于使用哪些工具的讨论,无论是关于编辑器、桌面、shells、编程语言还是其他任何东西,都是正常的,并且会非常有帮助。这些讨论提供了新事物的知识或关于已知事物如何工作以及如何使它们更好工作的新信息。作为一名系统管理员,深思熟虑和尊重他人的话语对提高我的知识和技能很有帮助,甚至是至关重要的。我希望这对你也有用。

当这些讨论退化为不尊重和无用的火焰战争,只会在参与者之间制造愤怒和不和谐时,问题就出现了。我总是试图退出这些讨论,以便为更有成效的活动保存精力。让我们看一些例子。

Linux 启动

SystemV 和 systemd 是执行 Linux 启动序列的两种不同方法。SystemV 启动脚本和 init 程序是旧方法,systemd 使用目标是新方法。

为了确保我们都在同一页上,Linux 启动序列在内核加载 init 或 systemd 之后开始,这取决于发行版分别使用新的还是旧的启动。init 和 systemd 程序启动并管理所有其他进程,也就是程序,它们都是各自系统中所有进程的母进程。

尽管许多现代 Linux 发行版使用较新的 systemd 进行启动、关闭和进程管理,但仍有一些发行版不使用。其中一个原因是一些发行版维护者和一些系统管理员更喜欢旧的 SystemV 方法而不是新的 systemd 方法。

我认为两者各有优点,所以让我解释一下我的理由。

为什么我更喜欢系统

我更喜欢 SystemV 的主要原因是它更开放,因为启动是使用 bash 脚本完成的。内核启动 init 程序(一个编译后的二进制文件)后,init 启动 rc.sysinit 脚本,该脚本执行许多系统初始化任务。rc.sysinit 完成后,init 启动/etc/rc.d/rc 脚本,该脚本依次启动/etc/rc.d/rcX.d 中 SystemV 启动脚本定义的各种服务,其中“X”是正在启动的运行级别的编号。

所有这些程序都是开放的,容易理解的脚本。可以通读这些脚本,并准确了解整个启动过程中发生了什么。每个脚本都进行了编号,这样它就可以按特定的顺序启动预期的服务。服务按顺序启动,一次只能启动一个服务。

Systemd 是一个单一的大型编译二进制可执行文件,不访问源代码就无法理解。它代表了对 Linux 哲学的多重原则的一个重要反驳。作为一个二进制文件,SysAdmin 不能直接查看或修改 systemd。

为什么我更喜欢系统

我更喜欢使用 systemd 作为我的启动机制,因为根据启动过程的当前阶段,它可以并行启动尽可能多的服务。这加快了整体启动速度,并使主机系统以比 SystemV 更快的速度进入登录屏幕。

systemd 启动机制是开放的,因为所有的配置文件都是 ASCII 文本文件。可以通过各种 GUI 和命令行工具修改启动配置,也可以添加或修改各种配置文件。

我们中有多少人真正看过 rc.sysinit 或 rc 程序,更不用说对它们进行修改了?我确实看了它们,但我绝不会以任何方式改变它们。这两个脚本的代码之外有一些配置文件,可以根据需要修改启动过程。

真正的问题是

你认为我不能同时喜欢两个启动系统吗?我知道,我可以和任何一个一起工作。

SystemV 与 systemd 的真正问题是在 SysAdmin 级别上没有选择。各种发行版的开发人员、维护人员和打包人员已经做出了是使用 SystemV 还是 systemd 的选择。

尽管这个特殊的选择已经为我们做出,我们的 Linux 主机启动并工作,这是我通常最关心的。作为一个最终用户,甚至作为一个系统管理员,我最关心的是我是否能完成我的工作:比如写这本书,安装更新,写脚本来自动化一切。只要我能做我的工作,我真的不在乎我的发行版上使用的启动序列。

然而,当启动过程中出现问题时,我确实很关心。不管任何主机上使用的是哪种启动系统,我都知道得足够多,并且能够按照事件的顺序找到故障并修复它。这才是最重要的。

桌面

我的首选台式机是 KDE 等离子。几年前,大约在 2008 年,随着 Fedora 9 的发布,KDE 从 V3.x 迁移到了 V4,这一重大变化导致了一些严重的问题。我最喜欢的一些 KDE 应用不再工作了,因为它们还没有更新,无法与 KDE 的新版本兼容。我经历了频繁的桌面崩溃,这使得我无法完成任何真正的工作。有时候,KDE 一小时会崩溃几次。这对生产力没有好处。

幸运的是,我能够切换到不同的桌面,我使用 GNOME 2 一年,直到 KDE 再次可用。

然后在 2016 年末,KDE 经历了另一系列的变化,导致了更多的不稳定。这一次,我优先考虑学习更多关于其他几种可用的桌面环境的知识。从 2016 年 12 月开始,我使用了三种不同的台式机一个月,以便真正了解它们的工作方式。仅仅试用几个小时并不能让你真正了解台式机是如何工作的,也不能让你知道如何配置它才能更符合你自己的风格。

我尝试了 Cinnamon、LXDE 和 GNOME 3,并学会了喜欢它们各自的优点。作为这些试验的结果,我分别写了一篇文章,“使用 Cinnamon 作为 Linux 桌面环境的 10 个理由”, 2 “使用 LXDE 的 8 个理由”, 3 和“使用 GNOME 3 Linux 桌面环境的 11 个理由”, 4 来匹配我之前写的关于 KDE 的文章,“使用 KDE 的 9 个理由” 5

我能够将一个问题转化为尝试新事物的机会:在这种情况下是台式机。每一款台式机都有很多优点,但我在使用时发现它们都有一些缺点。

甚至我最喜欢的台式机 KDE 也有一些问题。它确实会经历多次循环而变得不可用。它很大,占用大量内存。它安装的一些默认应用,当 KDE 登录时启动,会消耗 CPU 周期。我的安装后脚本代码删除了更有问题的 KDE 应用,并关闭了其他应用的后台守护进程,这样我的系统就不会受到它们的影响。因此,当它可用时,我会继续使用它。

出汗还是不出汗

我认为,作为一名系统管理员和使用您最喜欢的工具的一部分是正确使用我们拥有的工具,并让它们不受任何限制地可用。在这种情况下,我发现 sudo 命令被用在了一个它从来没有想过的地方。我特别不喜欢 sudo 工具在某些发行版中的使用方式,尤其是因为它被用来限制从事系统管理工作的人访问他们履行职责所需的工具。

【系统管理员】不要用 sudo。

— 保罗·威尼斯 6

Venezia 在他的 InfoWorld 文章中解释道,sudo 被用作系统管理员的拐杖。他没有花太多时间来捍卫或解释这一立场。他只是陈述了这一事实。我同意他的观点——对于系统管理员来说。我们不需要辅助轮来完成我们的工作。事实上,他们碍手碍脚。

一些发行版,比如 Ubuntu,使用sudo命令的方式是为了让需要提升(root)权限的命令的使用变得更加困难。在这些发行版中,不能以 root 用户身份直接登录,所以 sudo 命令用于允许非 root 用户临时访问 root 权限。这应该使人们在发出需要提升权限的命令时更加小心,例如添加和删除用户、删除不属于他们的文件、安装新软件,以及管理现代 Linux 主机所需的所有任务。强制系统管理员使用 sudo 命令作为其他命令的序言,应该会使使用 Linux 更加安全。

在我看来,这些发行版以这种方式使用 sudo 是给新手系统管理员提供一种错误的安全感的可怕而无效的尝试。它完全不能提供任何程度的保护。使用 sudo 时,我可以发出与不使用它时一样不正确或有害的命令。使用 sudo 来麻醉我们可能发出错误命令的恐惧感的发行版对系统管理员造成了极大的伤害。这些发行版对 sudo 工具可以使用的命令没有任何限制。没有人试图通过保护系统免受用户的伤害来限制可能造成的损害,也不应该有这样的可能性。

所以让我们明确这一点——这些发行版期望用户执行所有的系统管理任务。他们哄骗用户——如果你还记得我在第一章列出的清单,他们实际上是系统管理员——认为他们在某种程度上免受做坏事的影响,因为他们必须采取这个限制性的额外步骤来输入自己的密码,以便运行命令。

sudo 旁路移植

像这样工作的发行版通常为根用户锁定密码,Ubuntu 就是其中之一。这样,没有人可以登录到 root 并不受阻碍地开始工作。我已经安装了一个装有 Ubuntu 16.04 LTS(长期支持)的虚拟机,所以我可以向你展示如何设置密码来避免使用 sudo。

注意

实验 19-1 是可选的。它旨在指导您使用 sudo 通过设置密码来解锁 root 帐户。如果您使用的发行版不强制您使用 sudo,那么您应该跳过这个实验。

实验 19-1

让我规定一下这里的设置,这样如果你愿意,你就可以复制它。我安装了 Ubuntu 16.04 LTS 7 ,用 VirtualBox 安装在一个 VM 里。在安装过程中,我创建了一个非 root 用户 student,并为这个实验提供了一个简单的密码。

以用户 student 的身份登录,并打开一个终端会话。让我们看看/etc/shadow 文件中的 root 条目,加密的密码就存储在这里。

student@ubuntu1:~$ cat /etc/shadow
cat: /etc/shadow: Permission denied

权限被拒绝,因此我们无法查看/etc/shadow 文件。这在所有发行版中都很常见,因此非特权用户无法看到和访问加密的密码。这种访问将使使用普通黑客工具破解这些密码成为可能,因此允许这样做是不安全的。

现在,让我们尝试 su–to root。

student@ubuntu1:~$ su -
Password:
su: Authentication failure

这将失败,因为 root 帐户没有密码并且被锁定。让我们使用 sudo 来查看/etc /shadow 文件。

student@ubuntu1:~$ sudo cat /etc/shadow
[sudo] password for student: <enter the password>
root:!:17595:0:99999:7:::
<snip>
student:$6$tUB/y2dt$A5ML1UEdcL4tsGMiq3KOwfMkbtk3WecMroKN/:17597:0:99999:7:::
<snip>

我已经截断了结果,只显示了根用户和学生用户的条目。我还缩短了加密密码,这样条目就可以放在一行中。

这些字段用冒号(:)分隔,第二个字段是密码。请注意,root 的密码字段是一个“bang”,世界上的其他人都知道它是一个感叹号(!).这表明该帐户已被锁定,无法使用。

现在,我们需要做的就是为 root 帐户设置一个密码。

student@ubuntu1:~$ sudo su -
[sudo] password for student: <Enter password for student>
root@ubuntu1:~# passwd root
Enter new UNIX password: <Enter new root password>
Retype new UNIX password: <Re-enter new root password>
passwd: password updated successfully
root@ubuntu1:~#

现在,我们可以以 root 或 su 身份直接登录到控制台——直接登录到 root,而不必对每个命令都使用 sudo。当然,我们可以只使用 sudo su——每当我们想以 root 身份登录时——但是为什么要这么麻烦呢?

请不要误解我。像 Ubuntu 这样的发行版以及它们的上游和下游版本都非常好,这些年来我已经使用了好几个。当使用 Ubuntu 和相关发行版时,我做的第一件事就是设置一个 root 密码,这样我就可以以 root 身份直接登录。

sudo 的有效用法

sudo 设备确实有它的用处。sudo 的真正意图是让根用户能够委托给一个或两个非根用户,访问他们经常需要的一个或两个特定的特权命令。这背后的原因是懒惰的系统管理员;允许用户访问一个或两个命令,这需要提升权限,并且他们每天会多次不断地使用这些命令,这为系统管理员节省了大量来自用户的请求,并且消除了用户可能会经历的等待时间。但是大多数非 root 用户不应该拥有完全的 root 访问权限,只能访问他们需要的几个命令。

我有时需要非 root 用户来运行需要 root 权限的程序。在这种情况下,我设置一两个非 root 用户,并授权他们运行这个命令。sudo 工具还保存了使用它的每个用户的用户 ID 日志。这可能使我能够追查出是谁犯了错误。这就是它所做的一切;它不是魔法保护者。

sudo 工具从来就不是用来作为 SysAdmin 发出的命令的网关的。它无法检查命令的有效性。它不会检查用户是否在做傻事。它并不能使系统免受那些可以访问系统上所有命令的用户的攻击,即使是通过一个迫使他们说“请”的网关——这从来都不是它的预期目的。

?? 从来不说请。

—抢长枪 8

这句关于 Unix 的话对于 Linux 和 Unix 都是正确的。当我们需要以 root 用户身份工作时,我们系统管理员以 root 用户身份登录,当我们完成工作时,我们注销我们的 root 会话。有些日子我们整天都以 root 身份登录,但是我们总是在需要的时候以 root 身份工作。我们从不使用sudo,因为为了运行我们工作所需的命令,它会迫使我们输入不必要的内容。Unix 和 Linux 都不会问我们是否真的想做某件事,也就是说,它不会说“请验证您想这样做。”

是的,我不喜欢一些发行版使用sudo命令的方式。

几句结束语

你用什么工具对我来说并不重要,对其他人来说也不重要。真正重要的是完成任务。无论你是 vim 还是 EMACS,systemd 还是 SystemV,RPM 还是 DEB,这有什么区别吗?这里的底线是,你应该使用那些你觉得最舒服、最适合你的工具。

最重要的是,我们选择使用的工具不以任何方式受到限制或阻碍。误用完美的工具来帮助和助长这种障碍是不合理的,并且与 Linux 和开源所代表的所有自由相抵触。无论何时遇到都要抵制和规避。

一般来说,Unix、Linux 和开放源码的最大优势之一是,对于我们需要完成的每项任务,通常都有许多选择。我们有更多的开源文字处理器,比我记忆中专有 PC 软件时代最鼎盛时期的三个左右的处理器还要多。

二十、记录一切

真正的程序员不会评论他们的代码,如果代码很难写,就应该很难理解,更难修改。

—未知

如果是我写的,我也想匿名。它甚至可能意味着讽刺或讽刺。不管怎样,这似乎是许多开发人员和系统管理员的态度。在一些开发人员和系统管理员中有一种很难掩饰的风气,即一个人必须自己搞清楚所有事情才能加入这个俱乐部——不管是什么俱乐部。他们暗示,如果你想不出来,你应该去做些别的事情,因为你不属于这里。

首先,这不是真的。其次,我认识的大多数开发人员、程序员和系统管理员肯定不同意这种观点。事实上,最优秀的人,其中一些多年来一直是我的导师,恰恰相反。精英中的精英让文档——好的文档——成为他们所做的每件事情的重中之重。

我用过很多软件,它们的创建者都认同这样一种理念,即所有代码都是不言自明的。我还被要求修复大量完全没有注释或者没有文档记录的代码。似乎许多开发人员和系统管理员认为如果程序对他们有用,就不需要文档化。

有很多类似上面的引用。他们都倾向于支持文档既不需要也不应该存在的观点。然而在我的职业生涯中。我已经看到了这种缺乏文件的灾难性后果。我已经不止一次被指派为修复未注释代码的系统管理员。这是我做过的最不愉快的任务之一。

问题的一部分是许多 PHB 没有将文档视为高优先级。我参与了 IT 行业的许多方面,幸运的是,我工作过的大多数公司都认为,文档不仅重要,而且对于手头的任务至关重要,不管这个任务是什么。

我想我从来没有听到任何人说,“这个文档太棒了。”大多数情况下,我听到一些特定的文档有多糟糕。我自己也多次重复这句话。

然而,有很多非常好的文档。例如,LibreOffice 的文档非常优秀。它包括多种格式的几个文档,包括 HTML 和 PDF,从“入门”到每个 LibreOffice 应用的非常完整的用户指南。

RHEL 和 CentOS 的文档,以及 Fedora 的文档——它们都是非常相关的发行版——也是我在 IT 行业工作的四十多年中所见过的最好的文档之一。

好的文档并不容易,而且需要时间。它还需要了解读者——不仅与文档的目的有关,还需要了解目标读者的专业技术以及读者的语言和文化。Rich Bowen 在 Opensource.com 的一篇优秀文章《RTFM?如何写出一本值得一读的手册。” 1

还有一个问题是,对于一个系统管理员来说,什么是好的文档。我们在这一章中探索这些事情,这主要是关于记录我们写的脚本。

红色男爵

在我作为客户工程师的 IBM 职业生涯中,最令人沮丧的一件事是在一家炼油厂协助解决 IBM 1800 2 过程控制计算机上的一些问题。

这台特殊的计算机与炼油厂外的许多传感器相连,它被用来对生产过程中的各个部分进行调整。根据传感器读数,该计算机将调整温度和流速等事项,以确保过程的产品是正确的和高质量的。但是当事情出错时,它可能是灾难性的。我是说,拜托!这是一个该死的炼油厂的过程控制!

似乎写代码的程序员没有很好地注释他的代码——或者据我所知根本没有——并不是说我可以直接访问他的专有源代码。这位开发人员显然也不喜欢信息性的错误消息。

我不得不说代码很擅长检测错误。它似乎还擅长在炼油厂的地面上关闭受影响的流程。毕竟没有爆炸。然而,说程序在传达错误方面有缺陷是一种保守的说法。不管是什么错误,不管出了什么问题,控制台上打印的唯一信息是,“诅咒你,红色男爵,”以及一个数字错误信息,我们必须在一个很长的错误代码列表中查找。从列表中得到的信息也没有多大帮助。

为 IBM 说句公道话,这位程序员并没有为 IBM 工作。

我的文档哲学

我的哲学是多年来我最好的导师灌输给我的,“直到文档完成,工作才算完成。”这意味着一切都必须记录在案。文档也绝对不是节省打字时间的地方。尽管如此,好的文档对系统管理员的意义不同于对最终用户的意义。

在针对系统 管理员Linux 理念的背景下,我们将考虑针对我们代码的目标受众——我们自己和其他系统管理员——的文档。我们系统管理员需要两种主要类型的文档。某种形式的体面的命令行帮助选项和注释良好的 shell 代码。

帮助选项

在寻找帮助我理解 shell 脚本的文档时,我首先去的地方是 help 工具,因为我最常见的需求是理解启动程序的命令的语法以及命令的可用选项和必需或可选参数。这种类型的信息通常可以通过对所需命令使用-h 选项来获得。

我们在第十章“总是使用 Shell 脚本”中创建的 bash 脚本模板包含代码清单 20-1 中所示的模板帮助工具。你以前见过这个。注意,这只是一个模板,就像脚本模板的其余部分一样。必要时,需要在此过程中添加和修改为脚本提供有用帮助所需的所有细节。添加新选项或功能时,该信息也应记录在帮助工具中。

代码清单 20-1

##########################################################################
# Help                                                                   #
##########################################################################
Help()
{
   # Display Help
   echo "Add description of the script functions here."
   echo
   echo "Syntax: template <option list here>"
   echo "options:"
   echo "g     Print the GPL license notification."
   echo "h     Print this Help."
   echo "v     Verbose mode."
   echo "V     Print software version and exit."
   echo
}

像这样的简单帮助工具可以回答我关于脚本做什么的大部分问题,以及可以用来修改其行为的各种可用选项。在脚本功能描述、语法图、选项列表以及每个选项的简短描述之间,运行时问题很容易回答。

好的帮助是我们作为系统管理员编写的脚本的第一行文档。所有操作文档必须包含在帮助程序中。这也意味着脚本的用户界面应该非常明显和简单,这样就可以最大限度地减少任何形式的帮助。

宽松地注释代码

代码中的注释是文档的一种形式。事实上,它们应该是系统管理员文档的第一和主要形式。

作为我自己记录一切的需要的一部分,我在我的脚本中添加了许多注释。当试图减少注释时,我回想起当我必须解释和修复别人写的没有注释和没有文档记录的代码时的感觉。

我知道许多系统管理员和其他开发人员认为他们的代码是自解释的,甚至没有注释。不管我们的代码有多好,即使有大量的、写得很好的注释,代码也永远不会是不言自明的。我们思考问题的方式不同,编写代码的方式不同,解决问题的方式也不同。因为我们理解代码及其结构的方式不同,代码的目的对你来说可能是显而易见的,即使没有注释,对我来说可能是难以理解的。

在本书的前面,我们首先创建了一个 bash 脚本模板,然后使用该模板创建了一个简短的脚本。模板和脚本都得到了好评。这样做的目的是让我在构建代码时记得注释自己的代码。我在脚本模板中包含的注释是一个良好的开端。

我认为前三个部分特别重要。这些是程序描述、变更历史和许可声明。为了方便访问,我在代码清单 20-2 中再次包含了这些。

代码清单 20-2

#!/bin/bash
##########################################################################
#                              scriptTtemplate                           #
#                                                                        #
# Use this template as the beginning of a new program. Place a short     #
# description of the script here.                                        #
#                                                                        #
# Change History                                                         #
# 04/12/2017  David Both    Original code. This is a template for creating  #
#                          new Bash shell scripts.                       #
# 01/30/2018  David Both   Add an option for setting test mode.          #
#                                                                        #
#                          Add new history entries as needed.            #
#                                                                        #
#                                                                        #
##########################################################################
##########################################################################
##########################################################################
#                                                                        #
#  Copyright (C) 2007, 2018 David Both                                   #
#  LinuxGeek46@both.org                                                  #
#                                                                        #
#  This program is free software; you can redistribute it and/or modify  #
#  it under the terms of the GNU General Public License as published by  #
#  the Free Software Foundation; either version 2 of the License, or     #
#  (at your option) any later version.                                   #
#                                                                        #
#  This program is distributed in the hope that it will be useful,       #
#  but WITHOUT ANY WARRANTY; without even the implied warranty of        #
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         #
#  GNU General Public License for more details.                          #
#                                                                        #
#  You should have received a copy of the GNU General Public License     #
#  along with this program; if not, write to the Free Software           #
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA   #
#                                                                        #
##########################################################################

程序描述定义了程序的目的和一些主要的功能和选项。变更历史告诉未来的系统管理员可能需要对脚本进行维护,添加或删除了什么特性,修复了哪些错误,谁做了哪些工作,以及这些事情发生的时间。

许可证声明用于记录许可证,在该许可证下,脚本被分发并可供其他用户使用。这一点很重要,这样就不会对脚本的使用、修改和分发条件产生疑问。

代码中嵌入的注释应该描述它们所引用的代码段的功能。它们还应该包含为什么事情要以某种方式完成的信息,以及在不明显的地方对逻辑的解释。例如,下面的代码清单 20-3 中的代码片段有关于它的功能的注释,我做的一些假设,一个使用了新方法的指示器,旧代码被保留为注释,以便可以评估差异。

代码清单 20-3

###########################################################################
# Processing Intel CPU data
###########################################################################
# NOTE :This assumes certain data to be constant in /proc/cpuinfo based on
#       data from the chipsets.
if [ $verbose == 1 ]
then
   echo "This is an Intel box"
fi
CPUtype="Intel"
# Get number of CPU cores  
# CPUs=`cat /proc/cpuinfo | grep "^processor" | wc -l`
# New method below
CPUs=`cat /proc/cpuinfo | grep "cpu cores" | uniq | awk -F: '{print $2}' | sed -e "s/^ //"`

此外,代码清单 20-2 中的代码部分有一个标题,有助于在视觉上将其与代码的其他部分区分开来。这使得可视化代码的整体结构和功能流变得容易。

我的代码文档过程

哪个先出现?程序或文档。理想情况下,文档应该放在首位。然后可以开发代码来满足文档中概述的规范。在编写代码之前,您确实创建了规范,不是吗?这是我遇到的另一个常见问题:缺乏清晰的脚本规范。

如前所述,我喜欢通过使用注释创建我提议的代码的大纲来开始编码。这让我可以看到程序的结构,并确定它是否干净和优雅,允许我在编写任何代码之前,在必要时更改结构。无论我是在写新代码还是维护现有代码,注释是我添加的第一件事。这些评论成为我正在编写或维护的脚本的规范。然后,我可以编写代码来实现注释中描述的操作。

但是我并不总是一开始就做所有的评论。我首先创建一个基本大纲,包含描述程序逻辑的注释框架。我尽可能地创建了程序主体的轮廓。如果我设想使用额外的过程,我会创建并命名空过程,然后添加注释来描述其内部功能。

然后我创建代码来实现这个基本框架。我通常从程序的主体开始,在必要时添加新的注释,然后填充代码来实现这些注释。当我到达一个分支到一个不完整函数的调用时,我编写该函数并添加任何可能仍然需要的注释,然后编写代码来实现该过程。

这是我在本节开始时提出的问题的答案。至少对我来说,文档是第一位的。我可以听到敏捷支持者的键盘已经打出了他们相反的意见。但是从一个非常真实的意义上来说,我所做的是敏捷的,因为我只写我需要的文档,正好赶上写代码。然后评论也变成了文档。

并不是每个人都想这样工作,或者像我一样发现这很适合他们的工作方式。有多少人在做,就有多少种创建代码和记录代码的方法。做最适合你的事情,但是一定要做!

手册页

手册页在什么地方适合这种记录一切的哲学?坦率地说,对于系统管理员编写的脚本来说不是很好。

在早期,我们讨论了我们系统管理员工作的时间限制,以及我们编写的大多数脚本都倾向于作为操作问题的最小化解决方案的事实。在这种环境中,我们几乎没有时间来创建手册页。底线是我不会花时间去创建手册页。

系统文档

这种类型的文档不是关于记录脚本或程序的。它是关于记录网络的状态,连接的主机,以及我在它们上面执行的任何工作。这份文件对我以前咨询公司的客户,以及我作为全职员工或承包商工作过的任何雇主来说都是至关重要的。

我曾经拥有一个小的有限责任公司,通过它我可以做一些关于 Linux 和开源的咨询。我仍然为我的教会和几个朋友做一些咨询。

当与客户一起工作时,我总是记录我与他们的互动以及我所做的工作。像这样的文件为我服务,就像医生对我的就诊记录为她服务一样。这是客户环境的永久记录,我可以在与他们打电话或进行电子邮件交谈时参考。它为我提供了对我发现的问题以及我采取的解决措施的持续评论。

在某些情况下,我有多年的文档,涵盖了从我第一次与他们联系到我在为他们工作时发现的关于他们网络的信息,我为他们安装的硬件的细节,我在项目中工作的细节,以及我每次安装更新的记录。我在这些记录中加入了数据,如网络图、网络 IP 和 MAC 地址表以及每个节点的功能说明。我还保留了我编写的脚本的输出,该脚本列出了我工作的每台 Linux 主机的硬件和一些配置细节。

这些信息有多种用途。它给了我一个记录,以便我可以回去回忆我做了什么以及我的客户环境的结构,这对我来说是一个记忆辅助工具。需要时,我可以用它来支持我对额外工作的建议。在与客户发生纠纷时,保留详细的记录也很有用。

在为客户工作之前,我总是会创建一个任务列表,这样我就不会忘记任何需要完成的事情。我在清单上做笔记,然后,在工作结束时,任务清单成为我已经完成的工作的文档的一部分,并由我在工作过程中所做的笔记进行补充。对于我的一些客户来说,我已经完成了超过 40 页的这种类型的文档。

对于这种类型的文档,我通常使用 LibreOffice Writer。Writer 使用开放文档文本(ODT)格式,这是一种开放的、众所周知的格式,被许多文字处理程序所使用。即使是 Microsoft Word 也可以使用 odt 格式。

对这种类型的文档使用文字处理程序可以让我把它做得很漂亮,这样当我把它的副本给我的客户时,它看起来就很好。

系统文档模板

我创建了一个模板——实际上是一个模板的大纲——它帮助我记录我过去工作过的组织的系统信息。下面的简化大纲对我来说很有用,如果你还没有这种类型文档的规范或模板,我建议你把它作为一个起点。请随意使用和修改它,以满足您自己的独特需求。

  1. 标题页。

  2. 目录。

  3. 表格索引。

  4. 插图索引。

  5. 代码列表的索引。

  6. 简介–对文件和组织的简要描述。

  7. 管理员–当前系统管理员及其联系信息的列表。

  8. 互联网连接–对互联网连接和提供互联网连接的 ISP 的描述。这可能包括有关合同日期和成本的信息。

    • 电缆线路——描述从 ISP 的街道连接到分界点(通常是 ISP 的调制解调器/路由器/交换机)的物理电缆位置。

    • 外部 IP 地址–外部 IP 地址(如果是静态的)和通用 IP 地址范围(如果是 DHCP)的列表。

  9. 内部网络–内部网络的描述。

    • 所有内部网络的内部 IP 地址空间。

    • 防火墙–对属于组织而非 ISP 的防火墙的描述。

    • 物理描述–包括文本描述、网络图和地址映射,其中列出了每个网络节点、其名称、MAC 地址、IP 地址、网络配置是静态还是 DHCP,以及对其功能的简短描述。

  10. 硬件–每个网络节点的列表。
    * 硬件的描述。这可以使用本书前面创建的 mymotd 程序来创建。

*   操作系统。对于 Linux,这包括分发和发布。

*   网络节点提供的功能描述。
  1. 操作系统和软件
    * 所有操作系统及其在哪些主机上运行的列表。
*   每台主机上特定软件的列表。这并不意味着所有的软件,如没有线索的 PHP 可能会要求,但主要的软件,该主机打算。例如,对于一个简单的桌面,你可以说“桌面软件”。对于服务器,这可能是,“DHCPD,HTTPD,命名”,等等。

*   许可证–可能相关的软件许可证信息,如续订信息和费用。拥有专有许可的软件应列出许可证 id 或编号,以便在需要进行许可证合规性审核时参考。
  1. 主机配置–常见的主机配置项目,如 DNS 和 DHCP 服务器、默认网关、电子邮件服务器等方面的网络配置。

  2. 管理任务–各种管理任务的列表,以及负责执行这些任务或监控这些任务(如果它们是自动执行的)的系统管理员或用户。

  3. 联系人列表–包括内部系统管理员和管理联系人及其职责,以及所有供应商的联系人,包括 ISP、硬件和软件供应商、HVAC、数据中心冷却、UPS、内部安全、外部安全公司、外部紧急联系人(如消防和警察)以及您可能想到的任何其他人。

  4. 活动日志–这是我与客户联系的日志以及我为客户完成的工作。在描述问题及其解决方案时,这一部分应该尽可能清晰明了。

这个模板是一个很好的起点。拥有这种类型的文档作为记忆辅助是很重要的——我总是很高兴不用问客户我为他们做了什么,因为我可以很容易地找到它。在最坏的情况下,当客户或 PHB 质疑你的行为时,你可能会发现有必要使用一份维护良好的文件作为证据。我很幸运,从未发现自己处于最坏的情况。

记录现有代码

为现有代码创建文档需要不同于任何其他类型的方法。

我做的第一件事是阅读源代码,对我来说几乎总是 Perl 或 bash 脚本。然后,我可以使用这些评论作为创建外部文档的起点——如果有任何评论,如果这些评论有任何意义的话。

很多年前,我接手了一份工作,负责维护和修复大量预先存在的 bash 脚本。这些脚本是该公司使用的一系列复杂内部应用的一部分。代码可以工作——大多数情况下——但是过于复杂,缺乏任何可用的注释和文档。

我的第一个任务是修复几个脚本中的一些错误。我开始阅读这些脚本,以确定它们实际上应该做什么。当我确定了代码的每一部分是做什么的时候,我添加了注释来描述我刚刚阅读和解释的代码。就在这个过程中,我能够确定一些错误的原因并纠正它们。

在这个初始阶段,通过阅读 bash 脚本和询问 IT 人员,我确定这些脚本最初是由几个不同的承包商编写的,并且由一系列其他承包商维护了多年。每个承包商都添加了一些代码,这些代码显然是为了规避他们遇到的问题。这些附加的代码没有一个试图修复根本原因。每个承包商都有自己的做事方式,比如变量的命名方案、缩进、编码风格和注释。那些剧本完全是一场灾难。

那个项目简直是一场噩梦。我花了几周时间来分析代码,并给代码添加适当的、可理解的、有用的注释。这项任务很繁琐,而且由于变量的命名明显是随机的,因此变得更加困难。这是这么多不同的人在没有任何类型的指导或监督的情况下在项目目标或编程风格方面工作的不可避免的结果之一。

在我完成了注释这些脚本的任务之后,尽可能多地重命名变量,解决剩下的问题就变得容易多了。

当然,其他人写的代码并不是唯一有这些问题的代码。我自己的代码,尤其是我的大部分旧代码,也面临着同样的问题。这是因为我还没有了解 Unix 或 Linux 的哲学。随着时间的推移,我的代码确实有所改进,当重新访问我的一些旧代码来修改它或修复一个问题变得有用时,我会修改它,以遵循从我还是一个系统管理员时就学到的更好的编程实践。

保持文档更新

我自己的文档有一些问题。首先是忽视了及时或完整地更新文档。当我需要的信息没有被正确记录时,这就造成了问题。

当我发现我在文档方面有所松懈时,我会尽快回去改正它。这通常意味着纠正和更新我的脚本中嵌入的注释。这还意味着修复帮助过程,使其与对代码所做的更改保持一致。

更新我的客户文档也是我需要跟上的一项任务。我有时会忘记这样做,因为我似乎总是急于下一个任务。

让我的文档保持最新需要自律。如果没有持续的维护,文档可能会过时。

文件兼容性

文件兼容性也可能是外部文档的一个问题——即我的代码之外的文档,如客户文档。几年来,我使用了一些开源软件,这些软件以非纯文本的格式维护我的数据,从某种意义上说,这些数据是专有的,没有文档记录,其他软件也无法访问。这至少部分是因为我不知道数据格式,这是我自己的错。这也是该程序开发者的错误,因为他们应该使用开放格式的数据。

在第十三章“以开放格式文件存储数据”中,我们探讨了使用开放格式的一些原因。重点是程序本身使用的数据。现在我们来看看系统管理员用来维护各种类型的文档的数据,比如客户访问记录和维修历史记录。这些是重要的文件,因为它们使我们能够回过头来回顾已经完成的工作,并感受我们在确定当前问题方面取得的进展。

因此,当有问题的程序升级未能正确升级存储数据的数据库时,我无法访问几年来的客户记录。即使回到程序的前一个版本也不能恢复我的数据,因为它已经被破坏了。不幸的是,我的备份不像现在这样广泛,所以我无法回溯足够远的时间来获得未损坏的副本。

我现在以开放文档格式(ODF)保存我的笔记。ODF 是一种众所周知的、开放的、文档化的格式,有许多应用可以使用它。尽管这一原则具体指的是程序数据,但我认为一个必然的结果应该是文档应该以一种开放的格式保存,比如 ODF。

一些想法

文档对系统管理员来说非常重要。在履行我们的日常职责时,我们依赖他人留给我们的文件。我们工作的质量和速度直接受到文件质量的影响。这里有一些记录我们脚本的指导方针。

  1. 脚本应该用清晰而有意义的注释来记录。

  2. 脚本应该易于阅读。这是自我记录的一种形式。

  3. 脚本应该有一个有用和简洁的帮助功能。

  4. 遵循这些原则会产生优雅的脚本。

作为与客户互动记录或内部记录保存的系统文件应始终保持最新。工作完成后应尽快进行记录,以确保尽可能准确地回忆信息。

无论您做什么,无论您选择什么工作方式,请记住,在文档完成之前,工作还没有完成。

二十一、经常备份所有东西

我的电脑不会出任何问题,我也不会丢失我的数据。

我经历过各种原因造成的数据丢失,其中很多是我自己的错。保持良好的备份总是能让我在中断最少的情况下继续工作。本章讨论了数据丢失的一些常见原因,以及防止数据丢失和便于恢复的方法。

数据丢失

不去细说我自己的愚蠢,这里有一些我们可能在不恰当的时候丢失数据的原因。当然,没有丢失数据的合适时机。

自己造成的数据丢失有多种形式。最常见的形式是擦除一个或多个重要的文件或目录。

有时删除需要的文件是偶然的。我只是删除了一个目录中的一堆旧文件,后来发现还需要一两个。更多的时候,至少对我来说,我实际上是看着这些文件,然后决定不再需要它们。在我删除它们的一天、两天或一周后,我发现我至少还需要一些刚刚删除的文件。我还对一个文件进行了重大修改并保存了它。再一次,我发现在后来的某个时候,我做了一些不该做的修改,尤其是删除了一些不该删除的内容。

显然,在删除文件或对其进行更改时,有必要注意。这仍然不能阻止我们删除以后可能需要的数据。

电源故障可能因多种原因而发生。这包括像长时间断电一样无法挽回地关闭计算机的瞬间断电。不管断电的原因是什么,都有丢失数据的危险,尤其是尚未保存的文档。现代硬盘驱动器和文件系统采用有助于最小化数据丢失概率的策略,但它仍然会发生。

我也经历过停电。回到现代日志文件系统如 EXT3 和 EXT4 之前,我确实经历过一些严重的数据丢失。有助于防止因电源故障而导致数据丢失的一种方法是投资购买不间断电源(UPS ),它可以在足够长的时间内保持主机供电,以执行手动关机或由电源故障本身触发的关机。

电磁干扰,EMI ,是来自许多不同来源的各种类型的电磁辐射。这种辐射会干扰包括计算机在内的任何电子设备的正常运行。

当我在佐治亚州亚特兰大的 IBM 个人电脑客户支持中心工作时,我们的第一个办公室距离多宾斯空军基地跑道中心线大约一英里,就在跑道中心线上。各种类型的军用飞机一天 24 小时都在进进出出。大功率军用雷达有时会导致多个系统同时崩溃。在那种环境下,这就是生活的现实。

闪电、静电、微波、旧的阴极射线管显示器、地面线路上的无线电频率脉冲,所有这些以及更多的因素都会引起问题。正如我们在第十七章中看到的,良好的接地可以降低所有这些类型的电磁干扰的影响。但这并不能使我们的电脑完全免受电磁干扰的影响。

硬盘故障也会导致数据丢失。当今计算机中最常见的故障是带有移动机械部件的设备。在频率列表中领先的是冷却风扇,硬盘紧随其后。现代硬盘具有智能功能,可以进行预测性故障分析。Linux 可以监控这些驱动器,并向 root 用户发送一封电子邮件,表明故障即将发生。不要忽视这些电子邮件,因为在硬盘出现故障之前更换硬盘比在硬盘出现故障后更换硬盘并希望备份是最新的要简单得多。

心怀不满的员工可以恶意破坏数据。适当的安全程序可以减轻这种类型的威胁,但是备份仍然很方便。

盗窃也是丢失数据的一种方式。1993 年,我们搬到北卡罗莱纳州的罗利后不久,当地报纸和电视上刊登了一系列文章,报道了我们一所著名大学的一位科学家的苦难经历。这位科学家把他所有的数据都保存在一台电脑上。他确实有备份——在同一台电脑的另一个硬盘上。当他办公室的电脑被盗时,他所有的实验数据也丢失了,而且再也没有找到。

这是将良好的备份与正在备份的主机分开的一个非常好的理由。

自然灾害发生。火灾、洪水、飓风、龙卷风、泥石流、海啸以及更多种类的灾难会毁坏计算机和本地存储的备份。我可以保证,即使我有一个很好的备份,我也不会在火灾、龙卷风或自然灾害将我置于迫在眉睫的危险时花时间去保存备份。

恶意软件是可用于各种恶意目的的软件,包括破坏或删除您的数据。

勒索软件是一种特定形式的恶意软件,它会加密你的数据并以此勒索赎金。如果你付了赎金,你可能会得到可以解密你的数据的密钥——如果你够幸运的话。

如你所见,丢失数据的方式有很多种。我列出这些可能导致数据损坏或丢失的方式的目的是为了吓唬您进行备份。成功了吗?

救援的备份

最近,就在最近——实际上,当我在写这本书的时候——我遇到了一个硬盘崩溃的问题,这个问题破坏了我主目录中的数据。我已经期待了一段时间,所以这并不奇怪。

问题

我发现有问题的第一个迹象是来自 S.M.A.R.T(自我监控、分析和报告技术)支持的硬盘驱动器的一系列电子邮件,我的主目录就在这个硬盘上。 1 这些电子邮件中的每一封都表明一个或多个扇区已经变得有缺陷,并且有缺陷的扇区已经离线,并且预留扇区被分配到它们的位置。这是正常操作;正是出于这个原因,硬盘特意设计了保留扇区。

我们将在第二十二章“追随你的好奇心”中详细讨论好奇心,但几个月前当这些错误信息开始进入我的电子邮箱时,我开始使用我的好奇心。我首先使用smartctl命令查看有问题的硬盘驱动器的内部统计数据。原来有缺陷的硬盘已经被替换了,但是——是的,我保留了一些旧的有缺陷的设备,以备像这样的教学时刻使用。我将这个损坏的硬盘安装在我的扩展坞中,以展示有缺陷的硬盘的后果。

你可以和我一起做这个实验,但是你的结果会不同——希望比我有缺陷的驱动器更健康。

实验 21-1 中使用的智能报告可能有点混乱。网页“了解智能报表, 2 ”对此有所帮助。维基百科也有一个关于这项技术的有趣页面。 3 我建议在尝试解释 SMART 结果之前先阅读那些文档;它们可能非常令人困惑。

注意

请确保在不用于生产的物理主机上执行此实验。虚拟硬盘的硬件状态无关紧要。

实验 21-1

这个实验必须以 root 用户身份进行。

在扩展坞中安装驱动器并将其打开后,dmesg 命令显示该驱动器被指定为设备专用文件/dev/sdi。确保为您的硬盘驱动器使用正确的设备专用文件。您可以使用主机中安装的任何物理硬盘,即使它正在使用中。

为了便于讨论,我将命令的结果分成了几个部分,并删除了大量不相关的数据。

[root@david ~]# smartctl -x /dev/sdi | less
smartctl 6.5 2016-05-07 r4318 [x86_64-linux-4.15.6-300.fc27.x86_64] (local build)
Copyright (C) 2002-16, Bruce Allen, Christian Franke, www.smartmontools.org

=== START OF INFORMATION SECTION ===
Model Family:     Seagate Barracuda 7200.11
Device Model:     ST31500341AS
Serial Number:    9VS2F303
LU WWN Device Id: 5 000c50 01572aacc
Firmware Version: CC1H
User Capacity:    1,500,301,910,016 bytes [1.50 TB]
Sector Size:      512 bytes logical/physical
Rotation Rate:    7200 rpm
Device is:        In smartctl database [for details use: -P show]
ATA Version is:   ATA8-ACS T13/1699-D revision 4
SATA Version is:  SATA 2.6, 3.0 Gb/s
Local Time is:    Wed Mar 14 14:19:03 2018 EDT
SMART support is: Available - device has SMART capability.
SMART support is: Enabled
AAM level is:     0 (vendor specific), recommended: 254
APM feature is:   Unavailable
Rd look-ahead is: Enabled
Write cache is:   Enabled
ATA Security is:  Disabled, NOT FROZEN [SEC1]
Wt Cache Reorder: Unknown
=== START OF READ SMART DATA SECTION ===
SMART Status not supported: Incomplete response, ATA output registers missing
SMART overall-health self-assessment test result: PASSED
Warning: This result is based on an Attribute check.

上面显示的第一部分结果提供了有关硬盘容量和属性的基本信息,如品牌、型号和序列号。这是一个有趣且有用的信息。然而,本节显示,必须带着一点怀疑的态度来看待这份智能数据报告。请注意,我的已知故障驱动器已通过自我评估测试。这似乎意味着驱动器不会发生灾难性故障,尽管它已经发生了。

我们目前最感兴趣的数据在下面两节。请注意,我删掉了大量对这个实验不重要的信息。

=== START OF READ SMART DATA SECTION ===
<snip – removed list of SMART capabilities.>

SMART Attributes Data Structure revision number: 10
Vendor Specific SMART Attributes with Thresholds:
ID# ATTRIBUTE_NAME          FLAGS    VALUE WORST THRESH FAIL RAW_VALUE
 1 Raw_Read_Error_Rate      POSR--   116   086   006    -    107067871
 3 Spin_Up_Time             PO----   099   099   000    -    0
 4 Start_Stop_Count         -O--CK   100   100   020    -    279
 5 Reallocated_Sector_Ct    PO--CK   048   048   036    -    2143
 7 Seek_Error_Rate          POSR--   085   060   030    -    365075805
 9 Power_On_Hours           -O--CK   019   019   000    -    71783
10 Spin_Retry_Count         PO--C-   100   100   097    -    0
12 Power_Cycle_Count        -O--CK   100   100   020    -    279
184 End-to-End_Error        -O--CK   100   100   099    -    0
187 Reported_Uncorrect      -O--CK   001   001   000    -    1358
188 Command_Timeout         -O--CK   100   098   000    -    12885622796
189 High_Fly_Writes         -O-RCK   001   001   000    -    154
190 Airflow_Temperature_Cel -O---K   071   052   045    -    29 (Min/Max 22/29)
194 Temperature_Celsius     -O---K   029   048   000    -    29 (0 22 0 0 0)
195 Hardware_ECC_Recovered  -O-RC-   039   014   000    -    107067871
197 Current_Pending_Sector  -O--C-   100   100   000    -    0
198 Offline_Uncorrectable   ----C-   100   100   000    -    0
199 UDMA_CRC_Error_Count    -OSRCK   200   200   000    -    20
240 Head_Flying_Hours       ------   100   253   000    -    71781 (50 96 0)
241 Total_LBAs_Written      ------   100   253   000    -    2059064490
242 Total_LBAs_Read         ------   100   253   000    -    260980229
                            ||||||_ K auto-keep
                            |||||__ C event count
                            ||||___ R error rate
                            |||____ S speed/performance
                            ||_____ O updated online
                            |______ P prefailure warning

smartctl 命令结果的前一部分显示了硬盘上硬件寄存器中累积的原始数据。原始值对某些错误率没有特别的帮助;如你所见,有些数字显然是伪造的。“值”栏通常更有帮助。阅读参考网页,了解一点为什么。一般来说,数值栏中像 100 这样的数字意味着 100%好,像 001 这样的低数值意味着接近失败——大约 99%的使用寿命都用完了。这真的很奇怪。

在这种情况下,Reallocated_Sector_Ct 的值列中的 048——重新分配的扇区计数——可能意味着大约一半的重新分配扇区已经用完。

数字 001 表示 Reported_Uncorrect(报告的不可纠正的缺陷扇区)和 High_Fly_Writes(磁头飞出硬盘记录表面的距离超过最佳值的写入),这意味着该硬盘的寿命实际上已经结束。经验证据已经证明了这一点。

下一节实际上列出了错误以及错误发生时的相关信息。这是输出中最有帮助的部分。我不试图分析每一个错误;我只是看看是否有多个错误。下面第一行中的数字 1350 是在此硬盘上检测到的错误总数。

<Snip>

Error 1350 [9] occurred at disk power-on lifetime: 2257 hours (94 days + 1 hours)
 When the command that caused the error occurred, the device was active or idle.

 After command completion occurred, registers were:
 ER -- ST COUNT  LBA_48  LH LM LL DV DC
 -- -- -- == -- == == == -- -- -- -- --
 40 -- 51 00 00 00 04 ed 00 14 59 00 00  Error: UNC at LBA = 0x4ed001459 = 21156074585

 Commands leading to the command that caused the error were:
 CR FEATR COUNT  LBA_48  LH LM LL DV DC  Powered_Up_Time  Command/Feature_Name
 -- == -- == -- == == == -- -- -- -- --  ---------------  ------------------
 60 00 00 00 08 00 04 ed 00 14 58 40 00 11d+10:44:56.878  READ FPDMA QUEUED
 27 00 00 00 00 00 00 00 00 00 00 e0 00 11d+10:44:56.851  READ NATIVE MAX ADDRESS EXT [OBS-ACS-3]
 ec 00 00 00 00 00 00 00 00 00 00 a0 00 11d+10:44:56.849  IDENTIFY DEVICE
 ef 00 03 00 46 00 00 00 00 00 00 a0 00 11d+10:44:56.836  SET FEATURES [Set transfer mode]
 27 00 00 00 00 00 00 00 00 00 00 e0 00 11d+10:44:56.809  READ NATIVE MAX ADDRESS EXT [OBS-ACS-3]

Error 1349 [8] occurred at disk power-on lifetime: 2257 hours (94 days + 1 hours)
 When the command that caused the error occurred, the device was active or idle.

 After command completion occurred, registers were:
 ER -- ST COUNT  LBA_48  LH LM LL DV DC
 -- -- -- == -- == == == -- -- -- -- --
 40 -- 51 00 00 00 04 ed 00 14 59 00 00  Error: UNC at LBA = 0x4ed001459 = 21156074585

 Commands leading to the command that caused the error were:
 CR FEATR COUNT  LBA_48  LH LM LL DV DC  Powered_Up_Time  Command/Feature_Name
 -- == -- == -- == == == -- -- -- -- --  ---------------  ------------------
 60 00 00 00 08 00 04 ed 00 14 58 40 00 11d+10:44:53.953  READ FPDMA QUEUED
 60 00 00 00 08 00 04 f4 00 14 10 40 00 11d+10:44:53.890  READ FPDMA QUEUED
 60 00 00 00 10 00 04 f4 00 14 00 40 00 11d+10:44:53.887  READ FPDMA QUEUED
 60 00 00 00 10 00 04 f3 00 14 f0 40 00 11d+10:44:53.886  READ FPDMA QUEUED
 60 00 00 00 10 00 04 f3 00 14 e0 40 00 11d+10:44:53.886  READ FPDMA QUEUED

Error 1348 [7] occurred at disk power-on lifetime: 2257 hours (94 days + 1 hours)
 When the command that caused the error occurred, the device was active or idle.

 After command completion occurred, registers were:
 ER -- ST COUNT  LBA_48  LH LM LL DV DC
 -- -- -- == -- == == == -- -- -- -- --
 40 -- 51 00 00 00 04 ed 00 14 59 00 00  Error: UNC at LBA = 0x4ed001459 = 21156074585

 Commands leading to the command that caused the error were:
 CR FEATR COUNT  LBA_48  LH LM LL DV DC  Powered_Up_Time  Command/Feature_Name
 -- == -- == -- == == == -- -- -- -- --  ---------------  ------------------
 60 00 00 00 08 00 04 ed 00 14 58 40 00 11d+10:44:50.892  READ FPDMA QUEUED
 27 00 00 00 00 00 00 00 00 00 00 e0 00 11d+10:44:50.865  READ NATIVE MAX ADDRESS EXT [OBS-ACS-3]
 ec 00 00 00 00 00 00 00 00 00 00 a0 00 11d+10:44:50.863  IDENTIFY DEVICE
 ef 00 03 00 46 00 00 00 00 00 00 a0 00 11d+10:44:50.850  SET FEATURES [Set transfer mode]
 27 00 00 00 00 00 00 00 00 00 00 e0 00 11d+10:44:50.823  READ NATIVE MAX ADDRESS EXT [OBS-ACS-3]

Error 1347 [6] occurred at disk power-on lifetime: 2257 hours (94 days + 1 hours)
 When the command that caused the error occurred, the device was active or idle.

<Snip – removed many redundant error listings>

这些错误表明磁盘确实有问题。

我决定在更换硬盘之前,先看看还会发生什么。失败的数字在开始时并没有那么糟糕。在灾难性故障发生时,错误计数上升到 1350。

一家名为 Backblaze 的云公司对超过 67,800 个智能硬盘 4 进行了一些测试,提供了一些基于统计的关于经历了各种报告错误数量的硬盘故障率的见解。这个网页是我发现的第一个展示了报告的 SMART 错误和实际失败率之间的统计相关性的网页。他们的网页也帮助我更好地理解了他们认为应该密切关注的五个智能属性。

在我看来,Backblaze 分析的底线是,在硬盘开始出现他们建议监控的五个统计数据中的任何一个错误报告后,应该尽快更换硬盘。

我的经验似乎证实了这一点,尽管它在统计学上并不显著。在第一次出现问题迹象的几个月内,我的硬盘就失灵了。在无法恢复之前,我的驱动器经历的错误数量非常多,我非常幸运能够从导致/home 文件系统切换到只读(ro)模式的几个错误中恢复过来。只有当 Linux 确定文件系统不稳定并且不可信时,才会出现这种情况。

恢复

所以说包含我的主目录的驱动器发生了灾难性的故障是有道理的。恢复是直截了当的,如果有点费时。

我关闭了电脑,取出了有缺陷的 320GB SATA 驱动器,换上了新的 1TB SATA 驱动器,因为我想稍后将额外的空间用于其他存储,然后重新打开了电脑。我创建了一个占用驱动器上所有空间的物理卷(PV ),然后创建了一个填充 PV 的卷组(VG)。我将其中的 250GB 空间用于逻辑卷(LV ),它将成为/home 文件系统。然后,我在逻辑卷上创建了一个 EXT4 文件系统,并使用e2label命令给它加上标签“home ”,因为我使用标签挂载文件系统。此时,替换驱动器已经准备好了,所以我将它安装在/home 上。

作为我创建备份的方法的结果,我只需要使用一个简单的复制命令,如代码示例 21-1 所示,就可以将整个主目录恢复到新安装的驱动器。

代码示例 21-1

请注意,为了确保正在恢复的数据不会被损坏,我不能以任何在/home 文件系统中拥有文件的非 root 用户身份登录。我以 root 用户身份登录虚拟控制台,并使用以下命令将数据从我的备份恢复到新安装并准备好的替换硬盘。

cp -Rp /media/Backups/Backups/david/2018-03-04-RSBackup/home/ /home

“R”选项遍历整个/home 目录结构,并复制整个目录树中的所有内容。“p”选项保留文件的所有权和权限属性。

在将数据恢复到我的/home 目录之后,我使用我的非特权用户 ID 登录并检查了所有东西。一切都按预期运行,我的所有数据都已正确恢复,包括这本书的文件。

按我的方式做

我的备份 shell 脚本是那些具有精心规划优势的程序之一。这是因为在此之前,我编写、使用并发现了许多我自己的备份脚本的错误。我能够更全面地了解我真正需要的备份系统。

我再次从一组需求开始。我已经考虑了几个月了。我已经有了一个使用 tar 在 tgz 文件中创建备份的备份脚本。但是从 tar 文件中提取单个文件或目录是一项很好的工作,需要花费一些时间。每晚还需要一个多小时来进行备份。尽管有 gzip 压缩,大文件意味着只有几天的历史可以保存在我用来备份的外部 USB 硬盘上,因为所有的东西都被备份了多次。

我有很多多年积累的档案。其中一些文件非常大,尤其是我的虚拟机文件。目前,我有大约 18 台虚拟机,每台都有非常大的虚拟磁盘与之关联。这占据了大量的空间。

因此,我需要一个快速的备份解决方案,能够轻松快速地处理非常大的文件,通过节省空间而无需创建某种类型的压缩归档,允许在单个备份驱动器上保存更多历史记录,并且我或我的客户可以在需要时轻松访问特定文件。

备份选项

执行备份有许多选项。除了像tar这样的老版本,大多数 Linux 发行版都提供了一个或多个额外的开源程序,专门用于执行备份。也有许多商业选择。

这些解决方案都不能完全满足我的需求,我真的想使用另一个我听说过的工具,rsync5 设计和实施可行的备份计划并不一定需要昂贵的备份计划。

我一直在试验rsync命令,它有一些非常有趣的特性,我已经能够很好地利用它们了。我的主要目标是创建备份,用户可以从这些备份中快速定位和恢复文件,而不必从备份 tarball 中提取数据,并减少创建和备份所需的时间。

本节只打算描述我自己在备份场景中对rsync的使用。它并没有介绍rsync的所有功能,也没有介绍它的许多其他有趣的用法。

备份

命令由安德鲁·特里吉和保罗·麦克拉斯编写,于 1996 年首次发布。rsync的主要目的是远程同步一台计算机上的文件和另一台计算机上的文件。你注意到他们做了什么来创造这个名字吗?rsync是一款开源软件,并且提供了我所熟悉的所有发行版。

rsync命令可用于同步两个目录或目录树,无论它们是在同一台计算机上还是在不同的计算机上,但它能做的远不止这些。rsync创建或更新目标目录,使其与源目录相同。所有常用的 Linux 工具都可以自由访问目标目录,因为它不是存储在 tarball 或 zip 文件或任何其他归档文件类型中;它只是一个包含普通文件的普通目录,普通用户可以使用基本的 Linux 工具进行导航。这符合我的主要目标之一。

rsync最重要的特性之一是它用来同步在源目录中已经改变的预先存在的文件的方法。它不是从源文件复制整个文件,而是使用校验和来比较源文件和目标文件的块。如果两个文件中的所有块都相同,则不会传输任何数据。如果数据不同,则仅将源上已更改的数据块传输到目标。这为远程同步节省了大量的时间和网络带宽。例如,当我第一次使用我的rsync bash 脚本将我所有的主机备份到一个大的外部 USB 硬盘上时,花了大约 3 个小时。这是因为所有的数据都必须转移,因为这些数据之前都没有备份过。后续备份实际需要 3 到 8 分钟,具体取决于自上次备份以来更改或创建了多少文件。我使用了time命令来确定这一点,所以这是经验数据。例如,昨晚花了 3 分 12 秒完成了 6 个远程系统和本地工作站的大约 750GB 数据的备份。当然,只有几百兆字节的数据在一天中被更改,需要备份。

代码示例 21-2 中显示的简单的rsync命令可用于同步两个目录及其任何子目录的内容。也就是说,目标目录的内容与源目录的内容同步,以便在同步结束时,目标目录与源目录相同。

代码示例 21-2

这是使用rsync同步两个目录所需的最少命令。

rsync -aH sourcedir targetdir

-a 选项用于存档模式,它保留权限、所有权和符号(软)链接。H 用于保存硬链接,而不是为每个硬链接创建一个新文件。请注意,源目录或目标目录都可以位于远程主机上。

现在让我们假设昨天我们使用了rsync来同步两个目录。今天我们想要重新同步它们,但是我们已经从源目录中删除了一些文件。rsync通常的做法是将所有新的或更改的文件复制到目标位置,并将删除的文件留在目标位置。这可能是您想要的行为,但是如果您希望从源中删除的文件也从目标(即备份)中删除,您可以添加- delete 选项来实现这一点。

另一个有趣的选项是- link-dest 选项,这也是我个人最喜欢的选项,因为它极大地增强了 rsync 的功能和灵活性。- link-dest 选项使用硬链接,67来创建一系列每日备份,这些备份每天占用的额外空间非常少,创建时间也非常短。

使用该选项指定前一天的目标目录,并为今天指定一个新目录。然后,rsync命令创建今天的新目录,在今天的目录中创建昨天目录中每个文件的硬链接。所以我们现在在今天的目录中有一堆指向昨天文件的硬链接。没有创建或复制新文件。只创建了一些昨天文件的硬链接。使用这组指向昨天的目标目录的硬链接创建今天的目标目录后,rsync照常执行其同步,但是当在文件中检测到更改时,目标硬链接被昨天的文件的副本替换,然后文件的更改从源复制到目标。

所以现在我们的命令看起来像代码样本 21-3 中的那样。

代码示例 21-3

这个版本的 rsync 命令首先在今天的备份目录中为昨天的备份目录中的每个文件创建硬链接。然后,将源目录(正在备份的目录)中的文件与刚刚创建的硬链接进行比较。如果源目录中的文件没有更改,则不会采取进一步的操作。

rsync -aH --delete --link-dest=yesterdaystargetdir sourcedir todaystargetdir

如果对源目录中的文件进行了更改,rsync 会删除指向昨天备份目录中的文件的硬链接,并从昨天的备份中制作文件的精确副本。然后,它将对文件所做的更改从源目录复制到今天的目标备份目录。

rsync还删除目标驱动器或目录上已从源目录中删除的文件。

有时也希望将某些目录或文件排除在同步之外。我们通常不关心缓存目录的备份,因为它们可能包含大量数据,与其他数据目录相比,备份它们所需的时间可能会很长。为此,有- exclude 选项。对要排除的文件或目录使用该选项和模式。您可能希望排除浏览器缓存文件,这样您的新命令将类似于代码示例 21-4。

代码示例 21-4

rsync -aH --delete --exclude Cache --link-dest=yesterdaystargetdir sourcedir todaystargetdir

请注意,要排除的每个文件模式都必须有单独的排除选项。

rsync命令可以与作为源或目标的远程主机同步文件。对于下一个示例,我们假设源目录位于主机名为 remote1 的远程计算机上,目标目录位于本地主机上。尽管 ssh 是在与远程主机之间传输数据时使用的默认通信协议,但我总是添加 SSH 选项。该命令现在看起来像这样。

代码示例 21-5

在这段代码中,源目录位于远程主机 remote1 上。

rsync -aH -e ssh --delete --exclude Cache --link-dest=yesterdaystargetdir remote1:sourcedir todaystargetdir

此命令将数据从远程主机上的目录备份到本地主机。

The rsync命令有大量的选项,您可以用来定制同步过程。在很大程度上,我在这里描述的相对简单的命令非常适合我的个人需求。请务必阅读 rsync 的大量手册页,以了解它的更多功能以及这里讨论的选项的详细信息。

执行备份

我自动化了我的备份,因为“自动化一切”我编写了一个 bash 脚本rsbu,它处理使用rsync创建一系列每日备份的细节。这包括确保安装了备份介质,为昨天和今天的备份目录生成名称,在备份介质上创建适当的目录结构(如果它们还不在那里),执行实际的备份,以及卸载介质。

我在脚本中使用rsync命令的方法的最终结果是,我得到了网络中每个主机的备份的日期序列。备份驱动器的结构类似于图 21-1 所示。这使得查找可能需要恢复的特定文件变得容易。

img/462716_1_En_21_Fig1a_HTML.png img/462716_1_En_21_Fig1b_HTML.png

图 21-1

我的备份数据磁盘的目录结构

因此,从 1 月 1 日的一个空磁盘开始,rsbu脚本对我在配置文件中指定的所有文件和目录的每个主机进行了完整的备份。如果您像我一样有很多数据,第一次备份可能需要几个小时。

1 月 2 日,rsync命令使用–link-dest =选项创建一个与 1 月 1 日完全相同的新目录结构,然后它在源目录中查找已更改的文件。如果有任何更改,将在 1 月 2 日的目录中制作 1 月 1 日的原始文件的副本,然后从原始文件更新文件中已更改的部分。

第一次备份到空驱动器后,备份只需很少的时间,因为首先会创建硬链接,然后只需要对自上次备份以来发生更改的文件进行进一步的操作。

图 21-1 还显示了一个文件/home/student/file1.txt 在 1 月 1 日、2 日和 3 日的 host2 系列备份的更多细节。1 月 2 日,该文件自 1 月 1 日以来一直未更改。在这种情况下,rsync 备份不会复制 1 月 1 日以后的原始数据。它只是创建一个目录条目,其中包含 1 月 2 日目录到 1 月 1 日目录的硬链接,这是一个非常快速的过程。我们现在有两个目录条目指向硬盘上的相同数据。1 月 3 日,文件已经更改。在这种情况下,数据../2018-01-02/home/student/file 1 . txt 复制到新目录,../2018-01-03/home/student/file 1 . txt 以及任何已更改的数据块都将复制到 1 月 3 日的备份文件中。这些策略是使用rsync程序的功能实现的,允许备份大量数据,同时节省磁盘空间和复制完全相同的数据文件所需的大量时间。

我的一个过程是每天从一个 cron 作业运行两次备份脚本。第一个迭代执行到内部 4TB 硬盘驱动器的备份。这是始终可用的备份,并且始终是我所有数据的最新版本。如果发生了一些事情,我需要恢复一个文件或所有文件,我可能会失去的最多是几个小时的工作。

第二次备份是在一个旋转的 4TB 外部 USB 硬盘系列中进行的。我每周至少一次从最近的地方开车到我在银行的保险箱。如果我的家庭办公室被破坏,我在那里维护的备份也随之被破坏,我只需从银行获得外部硬盘驱动器,我最多会丢失一周的数据。这种损失很容易恢复。

我用于备份的驱动器,不仅仅是内部硬盘,还有我每周轮换的外部 USB 硬盘,从来没有填满过。这是因为我编写的rsbu脚本会在进行新备份之前检查每个驱动器上备份的天数。如果驱动器上有任何超过指定天数的备份,它们将被删除。该脚本使用find命令来定位这些备份。天数在 rsbu.conf 配置文件中指定。

当然,在一场彻底的灾难之后,我首先必须为我和我的妻子找到一个有办公空间的新住处,购买部件并构建新的计算机,从剩余的备份中恢复,然后重新创建任何丢失的数据。

我的脚本 rsbu 及其配置文件 rsbu.conf 和一个 READ 是可用的。ME 文件来自 https://github.com/Apress/linux-philo-sysadmins/tree/master/Ch21

恢复测试

没有测试,任何备份方案都是不完整的。您应该定期测试随机文件或整个目录结构的恢复,以确保不仅备份正常工作,而且备份中的数据可以在灾难发生后恢复使用。我见过太多这样的例子,由于这样或那样的原因,备份无法恢复,并且由于缺乏测试而无法发现问题,导致宝贵的数据丢失。

只需选择一个文件或目录进行测试,并将其恢复到测试位置,如/tmp,这样就不会覆盖自备份执行以来可能已更新的文件。验证文件的内容是否符合您的预期。从使用上述 rsync 命令创建的备份中恢复文件,只需从备份中找到要恢复的文件,然后将其复制到要恢复的位置。

我遇到过几次不得不恢复单个文件的情况,有时还需要恢复完整的目录结构。正如我在本章前面所讨论的,有几次我不得不恢复硬盘的全部内容。大多数时候,这是我自己造成的,因为我不小心删除了一个文件或目录。至少有几次是因为硬盘崩溃。所以这些备份确实派上了用场。

异地备份

创建良好的备份是备份策略中重要的第一步。将生成的备份介质与原始数据保存在同一物理位置是错误的。

我们已经看到,将所有备份存储在内部驱动器上的计算机被盗会导致重要数据完全丢失且无法恢复。如果原始数据和备份数据存储在同一位置,火灾和其他灾难也会导致原始数据和备份数据丢失。防火保险箱是一种选择,可以减少盗窃和火灾等灾难的威胁。这种保险箱通常在规定的温度下以分钟为单位进行评级,以保护其内容物。我想我个人关心的是,我不知道火会燃烧多长时间或多热。也许保险箱能支撑足够长的时间,但如果不能呢?

我更喜欢像大公司那样为自己做备份。我保留当前的异地备份。对我来说,这个在我银行的保险箱里。对于其他人来说,这可能是“在云”的某个地方。我喜欢我的保险箱解决方案提供的端到端控制。我知道它被保护得很好。如果我小小的家庭办公室被摧毁,银行可能离得足够远,不会受到任何灾难的影响。

对于大公司来说,有一些服务可以将你的备份存储在一个远程、高安全性的地方,有气候控制的保险库。这些服务中的大多数甚至会派装甲卡车到您的设备处,来取走和运输您的备份介质。有些提供高速网络连接,以便可以在远程位置将备份直接备份到自己的存储介质上。

如今,许多人和组织都在进行云备份。我对所谓的“云”持严重保留意见。首先,“云”只是别人电脑的另一种说法。第二,考虑到我读到的所谓安全计算设施遭到黑客攻击的数量,我不太可能将我的数据交给任何维护可从互联网访问的在线备份的外部组织。我更希望我的远程备份数据离线,直到我需要它。

我对云的担心是,除了提供商放在他们网站上的营销信息,我没有办法知道他们的安全措施是否比我自己做的更好。也许他们可以,但作为一名系统管理员,我希望得到一些证明。我毫不怀疑,与许多企业和个人相比,很大一部分云提供商能够更好地管理委托给他们的数据的安全性。我怎么知道那些是什么?请记住,我们谈论的是基于云的备份解决方案,而不是应用或 web 存在解决方案。

我想我可以有一定把握地说,在安全性方面,成熟和公认的云提供商,如 Amazon、Azure、Google 等,肯定比许多中小型组织更值得信赖。我在考虑那些没有全职系统管理员的公司,或者把它外包给那些不太知名的小型本地公司。我还认为,在当今不断遭受网络攻击的世界里,许多经验不足的系统管理员还没有准备好应对互联网所需的高安全级别。

因此,对于许多组织来说,云可能是一个可行的选择。对于其他人来说,一个经验丰富、知识渊博的系统管理员可能是最好的选择。与许多 IT 决策一样,这是一个权衡风险因素并确定您愿意接受多少风险的问题。

灾难恢复服务

更进一步说,我工作过的一些地方与一个或多个灾难恢复服务有合同。这种类型的服务是为了维护一个完整的计算机和网络环境,可以在接到通知后立即替换您自己的计算机和网络环境。这通常包括从大型机到基于 Intel 的服务器和工作站的一切。当然,这还不包括在异地备份存储中保存大量数据。

在我工作的一个地方,我们对灾难恢复计划进行季度评估。我们通过英特尔服务器关闭了主机上的所有计算机。我们通知灾难恢复公司我们正在进行一项测试,他们准备了他们的站点,提供了我们恢复和运行所需的各种计算机。我们让备份存储服务将最新的备份介质从他们的安全设施运送到费城的恢复站点。

我们办公室的一群人来到恢复站点,从我们的备份介质中恢复了所有数据,使所有东西都上线,并进行测试以确保一切都正常工作。

总是有问题。一直都是。但这是整个练习的目的——发现我们的策略和程序中的问题。然后修复它们。

其他选项

不是每个人都需要灾难恢复服务或大量备份数据存储。对于一些只有一台电脑的个人和非常小的企业来说,几个 USB 拇指驱动器和手动备份到其中一个驱动器就足够了。对于其他人来说,一个相对较小的外部 USB 硬盘驱动器就可以了。

这都是你在你的环境中所需要的。

“经常”的部分呢?

这很简单。每天至少做一次备份。无论如何?如果一些文件特别重要,而你刚刚创建或修改了它们,那么马上备份它们。

rsbu脚本会很快完成这项工作,因为它只会备份已经更改的文件。它以一种仍然允许你在你的计算机上继续工作的方式做到这一点。

摘要

作为系统管理员,备份是我们工作中极其重要的一部分。我经历过许多这样的例子:备份实现了我工作过的地方以及我自己的业务和个人数据的快速操作恢复。

执行和维护数据备份有许多选项。我做对我有用的事情,从来没有丢失超过几个小时的数据。

像其他任何东西一样,备份是关于你需要什么的。无论你做什么——做点什么!计算一下,如果你失去了一切——数据、电脑、硬拷贝记录——一切,你会有多痛苦。这一难题包括更换硬件的成本,以及恢复已备份数据和未备份数据所需的时间成本。然后相应地计划和实施您的备份系统和程序。

二十二、跟随你的好奇心

人们谈论终生学习,以及这如何使人保持头脑清醒和年轻。系统管理员也是如此。总是有更多的东西需要学习,我认为这是让我们大多数人保持快乐并随时准备解决下一个问题的原因。持续的学习有助于保持我们的思维和技能敏锐,不管我们的年龄有多大。

我喜欢学习新东西。我很幸运,因为我的好奇心让我一生都在和我最喜欢的玩具——电脑打交道。关于计算机,当然还有很多新的东西要学;行业和技术都在不断变化。地球上和这个宇宙中有许多事情值得好奇。计算机和相关技术似乎是我最喜欢的东西。

我也假设你一定很好奇,因为你正在读这本书。但不是每个人都像我们一样好奇。

傻瓜

让我们乘坐我的时光倒流机到俄亥俄州托莱多的 1970 年旅行。我当时在一家化工厂做一份非常无聊的测试工作,和其他七八个人一起。我们会采用化学家们想出来的化学配方,将它们混合到乙烯树脂中,然后将其压制成各种类型的织物,用于汽车行业的座椅和乙烯树脂车顶。我们的工作是测试由此产生的原始乙烯基和涂层织物,看看它们是否符合订购它们的汽车公司提供的所有规格。

从没见过汽车上的乙烯基车顶?是啊,很久以前了!

我的一个同事查理是一个消极的人。他不停地抱怨。他会抱怨工作条件——我们周围有很多挥发性化学物质,如果你想要的话,很容易兴奋并停留在那里,但这也很危险。他抱怨这份工作有多危险,有多无聊,但我们都做了一些,这是那种工作的一部分。但是查理从他走进来的那一刻起,一直抱怨到下午比赛结束的哨声响起。

有一天,我们进行了一次谈话,大概是这样的。

一天早上八点半左右,查理对我说:“我讨厌这份工作。退出时间不能足够快地到达这里。”

我已经受够了他的消极,所以我说,“查理,如果你这么讨厌这份工作,为什么不另找一份工作呢?”

“我不知道还能做什么。”

所以我说,“那你为什么不学点新东西呢?下学期我将回到大学,拿到学位后我不会在这里呆太久。我打算找一份更好的工作。”

“这对你来说很容易,你还年轻。我老了,你不能教老狗新把戏。”

我问他:“你多大了,查理?”

“三十六岁,”他说。

即使在那时,我二十出头,在我自己的心目中似乎无懈可击,不朽,我知道三十六岁并不老。就在那时,我对自己发誓,我永远不会停止学习——我每天都要学习新的东西。我遵守了那个誓言。当然,在过去四十年的大部分时间里,我的职业和业余爱好都是计算机,所以这个誓言很容易遵守。

好奇心让我选择了 Linux

好奇心首先让我进入了 Linux,但这是一条漫长而曲折的道路。如果你愿意,你可以跳过这一长段,也许对你来说,这一段很无聊。我确实发现,描述我的旅程如何把我带到今天的位置,对一些人来说很有趣,也很有帮助。它确实表明,生活中两点之间的最短距离通常不是一条直线。在任何情况下,我都会尽可能地保持简短,并展示好奇心对我生活的影响。

从我学校成绩的标准意义上来说,我从来都不是一个特别好的学生。我倾向于跟随我的好奇心,而不是课程计划。大多数老师不喜欢这样。我对电学、电子学、数学和化学感兴趣。我很幸运有好的化学和数学老师,但在 1960 年,除非我去托莱多的职业技术高中,否则没有高中的电子学课程。我想去那里,但我的父母说服我,我可以以后学习电子学。

在我十几岁的时候,我对电子学的兴趣受到了我的 HO 轨距模型铁路的帮助和鼓励,它要求我至少学习电学的基础知识。所以我从图书馆借书来学习。我还在托莱多大学(UT)书店找到了我购买并学习的大学水平的练习册。我给我的模型铁路布线,当我学到新的东西时,我完全撕掉现有的布线,从头开始重新布线。

我们的电视坏了的时候,我也经常修理它。最终,邻居开始要求我在他们的电视和收音机坏了的时候帮他们修理。那时候这很容易,因为每个电子设备都有一个原理图,我所要做的就是找出要更换哪个真空管。有一些书用图片展示了各种症状,然后列出了失效时会导致这些问题的管子类型,这很有帮助。几乎每家药店和杂货店都有试管测试器和试管供应,这也很有帮助。这意味着我可以移除我怀疑是故障原因的一根或多根管子,然后走到一个地方进行测试并购买一个替代品。

1968 年夏天,我在洛杉矶的一个姑姑家度过。我的叔叔在航空航天行业担任计算机程序员,我在他们的车库里发现了一堆旧的自学手册。我没有像我计划的那样一直去海滩,而是在暑假的大部分时间里从那些旧课程中学习 IBM 大型机计算。

然后在 1968 年末,我在一份涉及大量数字运算的工作中,我们使用非常古老的机械计算器,做一次乘法需要几分钟。我建议我们买一台四功能电子计算器,当时这种计算器刚开始上市。我的主管认为这是个好主意,所以他让我研究一下这种可能性。我们联系的两个供应商有这些有趣的新设备,桌面可编程计算器。双方都愿意让我使用演示模型,这样我就可以在我们自己的环境中用我知道我们将要解决的问题来测试它们。

我能够说服财务人员,这台 3500 美元的计算器物有所值,所以我们购买了一台 Olivetti Programma 101。我花了几个月的时间编写这个程序,并想学习更多关于编程的知识。

我很快发现大学只开设了一门编程课程,所以我选了一门基础课程。这个班是托莱多大学在阿格分时系统上教的,大概是阿格-6002 系列,位于俄亥俄州哥伦布市。终端访问是通过 ASR 33 电传打字机在 300 波特的拨号电话线上进行的。

然后,我从使用 P-101 的工作岗位晋升到 IBM 1401 的夜班操作员,在那里我第一次直接接触到了大型计算机。这份工作我干了几个月才继续。

我是 1969 年初结婚的。虽然这还没有对我的职业道路产生直接影响,但它在以后将是至关重要的。

在我的下一份工作中,我没有直接接触电脑,但我也在晚上为一个乐队工作,担任他们的音响技术员和唯一的乐队指挥。乐队的鼓手有一个电子邮件课程,他已经支付了费用,但没有时间参加,所以他向我提供了这个课程。我抓住了这个机会,电子知识让我在托莱多的一家音响销售和维修店找到了工作,在那里我学到了更多。

那是在 1972 年左右,我和妻子从我岳父那里买下了以前的出租屋。原来我们的一个邻居在 IBM 工作。他问我是否有兴趣在 IBM 工作,但我对现在的工作很满意,所以我说我不感兴趣。

音频维修工作让我找到了另一份工作,成为一家新音响商店的服务经理,在那里我还花了一些时间学习电子工程课程。我擅长这些课程,因为我喜欢电子学。

当我被新的音响商店解雇时,我问我的 IBM 朋友他们是否还在招人。他给我安排了一次面试,我开始了在 IBM 的 21 年职业生涯。我在 IBM 的第一份工作是在通用系统部(GSD)做客户工程师,修理硬件。1978 年,IBM 把我调到他们在佛罗里达州博卡拉顿的工厂工作,为新产品编写培训课程。1981 年初,我被指派为最初的 IBM 个人电脑编写培训材料。 3

为了编写个人电脑的培训课程,我需要在我的办公室里放一个,这样我就可以方便地自己学习了。因为当时非常秘密,安保人员在我办公室的天花板上安装了铁丝网,门上也装了锁。我是大楼里唯一一个办公室门上有锁的非经理人员。我猜头顶上的铁丝网是为了防止一些邪恶的小偷爬过我办公室的墙,掉进里面。只有在那之后,我才能在办公室里拥有一台电脑。我的序列号是 00000001。

在编写培训课程时,我最初采用了一种更传统的 IBM 培训策略,但这并不适用于打字机部门的 CEs,他们的工资比我们在 GSD 时要低。使用打字机部门的 CEs 使其更具成本效益,但这意味着我们必须让这些 CEs 熟悉计算机概念和技术。我必须确保他们在培训期间亲自动手,但让他们去培训中心太贵了。

所以我完全重写了培训。我编写了一个完整的计算机化培训程序,它允许我编写课程内容,然后在我们运送到分公司进行培训的 IBM 个人电脑上将其提交给当地分公司的 CEs。然后我写了课程本身。尽管这绝对不是第一个计算机化的培训课程,但它是第一个用于 IBM PC 的培训软件和课件。

为了编写这份修改后的课程并保持我们的时间表与电脑发布日期一致,我要求允许我在家里拥有一台电脑,这样我就可以更容易地在晚上上课。经过几十个高层主管的签字同意后,除了我在办公室的那台电脑外,我还得到了一台可以带回家的电脑。据我所知,我是第一个在家里拥有 IBM 个人电脑的人。

当然,所有这些都让我对个人电脑产生了浓厚的兴趣。所以我通过员工购买计划给自己买了一个。扣除员工折扣后,这花费了 5000 多美元。该系统包括一对 160KB 的 5.25 英寸软驱,没有硬盘和 64K 的 RAM。当我们办公室的几个人一起去购买我们必须自己制作的第三方存储卡的零件时,我开始入侵 PC 硬件。

在经历了几次职业变动后,我在佐治亚州亚特兰大的 IBM PC 帮助中心找到了一份工作。那段时间我对操作系统非常感兴趣,并最终成为 OS/2 的主要支持人员之一。

在 1993 年搬到北卡罗来纳州的罗利后,我于 1995 年离开 IBM,创办了一家专门从事 OS/2 的咨询公司。到 1996 年,很明显 OS/2 不会存在太久。我被学习 Windows NT 的想法吓坏了。尽管我还没有听说过 Linux,但我认为我的未来在 Unix。

大约在这个时候,IBM 的一个朋友有一天打电话给我,问我是否知道有人在找工作,可以帮助 MCI(他现在在那里工作)使用他们的 OS/2 计算机。我接受了这份工作,条件是我必须学习 Unix。

在 MCI 的时候,我能够学习一些基本的 Unix 课程,这使我开始起步。我还听说了一个叫 Linux 的东西,它很像 Unix,我可以把它安装在我的个人电脑上。我认为我需要提高我的 Unix/Solaris 技能,但是我负担不起为家里购买一台 Sun 计算机和 Solaris。所以我在当地的电脑商店买了一份红帽 5.0(不是 RHEL)并安装在我的几台电脑中的一台上。我喜欢 Linux,在了解了更多之后,我发现我没有进步。我决定进行一次飞跃,除了一台之外,我把家里所有的电脑都升级到了 Linux。最后一步是将我的 web 和电子邮件服务器从 OS/2 迁移到 Linux。

所有这些学习的结果是,我能够在当地的 ISP 找到一份 Unix 工程师的工作。他们送我去上 Solaris 课程,我获得了 Sun 认证的系统管理员证书。在这里,我遇到了一些最好的导师。

我们中大约有 80 人被 ISP 解雇,我很快找到了一份承包商的工作。这是我负责修复运行在 Red Hat Linux 服务器上的所有 Perl 脚本的地方。我们还使用了一些需要清理的 bash shell 脚本。在那份工作中,我学到了更多关于 Linux 和 shell 脚本的知识。

这导致了一系列围绕 Linux 的工作,在大多数工作中,我至少接受了一些 Linux 培训。我发现我工作过的大多数地方都需要有人在 Linux 的各个方面培训其他管理员和用户。我组织了几个 Linux 课程和午餐学习会议,都很受欢迎。

我发现当我教别人的时候,无论是在课堂环境中还是在书籍和文章中,我自己学到的东西最多。我必须仔细研究事情,以确保我做对了。我还必须回答学生们提出的问题,这些问题是我在创作材料时从未考虑过的。我必须研究我没有答案的问题。

现在我在写关于 Linux 的文章,这需要更多的研究、测试和实验。

这只是我个人通向 Linux 和开源之路的一部分。有很多附带的旅行影响了我的一些决定,并改变了我生活中某些事件的时间,这样事情就为上面的故事展开做好了准备。

在我成长或上学的时候,找一份 Linux 方面的工作并不是我所能计划的,因为在我高中和大学的最初几年,Unix、Linux 和开源都不存在。我所做的选择,我遇到的人,我获得的知识,我住过的地方,我做过的一系列工作,都导致了我现在的地位,因为我一次又一次地选择追随我喜欢的和我有强烈好奇心的东西,技术,计算机,操作系统和 Linux。我的选择,不管是有意识的还是无意识的,带我走上了一条被好奇心驱使的道路。从很多方面来说,这都是令人愉快和有益的。

如果你有兴趣阅读一些其他人进入 Linux 和开源领域的故事,可以看看 Opensource.com 网站上标有“职业” 4 的文章列表。他们经常发表一些故事,讲述那些找到不同方法来到这里的人。并非列表中的所有文章都是关于“我如何在开源领域找到工作”的,但有些是。你可能会发现这些和其他带有这个标签的文章很有趣。

好奇心解决问题

有一句老话——我认为非常愚蠢——说“好奇害死猫”我小时候用过这个,幸运的是我父母没有用过。我认为这个愚蠢的说法主要是用来扼杀孩子,当他们的问题和好奇心把他们带到一些父母、老师和照顾者不愿花时间处理的地方时。这是我们周围建造盒子的方式之一。

我个人的说法是“好奇心解决问题。”跟随我们的好奇心会把我们带到盒子外面的地方,让我们用其他方式无法解决问题的地方。有时好奇心可以直接引导我找到问题的原因,而其他时候这种联系是间接的。

安全性

好奇心让我解决了许多问题,其中一些我最初甚至不知道存在。在这种情况下,计算机仍在运行,没有明显的症状,如崩溃或程序失败。没有明显的问题,一切似乎都很好。安全问题可以是这样的。

这一特殊的经历始于多年前的一天,当时我决定检查我的系统的安全性,尤其是我和外界之间的防火墙。我已经在防火墙上设置了一些防火墙规则和强密码。但是我很好奇我的安全状况,以及是否有一些我可以关闭的漏洞。我不是说代码漏洞,我是说程序和安全配置漏洞,那些我可以比当时做得更好的东西。一切都是从日志开始的。

当一切正常时,我喜欢观察 top、htop、iotop、glances 或任何其他系统监控工具,这样当它们看起来不一样时,我就知道可能有问题。我对我的日志文件也是这样。所以我花了大量时间扫描我的日志文件,看是否能发现任何异常。这总是一件非常耗时的工作,每天试图解释数百甚至数千行日志文件实在是太多了,而且很难将数据归结为可管理的东西。我需要找到一种方法来自动化这项任务,如果有潜在的问题,它会提醒我——是的,自动化一切。

日志监控

我读过 Logwatch,它就是这样做的,所以我花了一些时间研究它和其他可能对我有用的类似工具。每天,Logwatch 都会扫描前一天的日志文件,以查找 SysAdmin 应该看到的异常条目,从而确定是否存在问题。它非常适合我的需要。

我想我在用作防火墙的 Fedora 主机上安装了 Logwatch,因为它不是默认安装的。这是一段很长的时间,所以我不记得了,我的 postinstall.sh 脚本现在安装logwatch,如果它还没有安装的话。在 Fedora workstation 的当前版本中,Logwatch 肯定不是默认安装的。

Logwatch 通常由/etc/cron.daily 中的 cron 作业0logwatch运行,0logwatch 脚本配置为将结果作为电子邮件发送给 root 用户。我不想让 Logwatch 的输出进入 root,所以我在防火墙主机上的/etc/aliases 文件中添加了一行,并重新启动 sendmail。现在电子邮件会发给我。

logwatch程序也可以直接从命令行运行。在这种情况下,默认情况下将输出发送到 STDOUT,所以我没有等到第二天再查看 cron 作业发送给我的内容,而是从命令行运行了logwatch

实验 22-1 安装 Logwatch,然后让你从命令行运行它。我在我的防火墙上做了这个,有大量的数据。我已经删除了一些部分的大部分,只留下足够你看的部分

实验 22-1

这个实验必须以 root 用户身份进行。我们将首先安装 Logwatch,然后从命令行运行它。

[root@wally1 ~]# dnf -y install logwatch
Last metadata expiration check: 2:59:04 ago on Sat 07 Apr 2018 05:11:02 AM EDT.
Dependencies resolved.
======================================================================
 Package              Arch       Version             Repository  Size
======================================================================
Installing:
 logwatch             noarch     7.4.3-6.fc27        fedora     423 k
Installing dependencies:
 perl-Date-Manip      noarch     6.60-1.fc27         fedora     1.1 M
 perl-Sys-CPU         x86_64     0.61-13.fc27        fedora      19 k
 perl-Sys-MemInfo     x86_64     0.99-5.fc27         fedora      25 k

Transaction Summary
======================================================================
Install  4 Packages

Total download size: 1.6 M
Installed size: 12 M
Downloading Packages:
(1/4): perl-Sys-CPU-0.61-13.fc27.x86_  49 kB/s |  19 kB     00:00    
(2/4): perl-Sys-MemInfo-0.99-5.fc27.x 458 kB/s |  25 kB     00:00    
(3/4): logwatch-7.4.3-6.fc27.noarch.r 776 kB/s | 423 kB     00:00    
(4/4): perl-Date-Manip-6.60-1.fc27.no 1.8 MB/s | 1.1 MB     00:00    
----------------------------------------------------------------------
Total                                 1.8 MB/s | 1.6 MB     00:00     
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
  Preparing        :                                              1/1
  Installing       : perl-Sys-MemInfo-0.99-5.fc27.x86_64          1/4
  Installing       : perl-Sys-CPU-0.61-13.fc27.x86_64             2/4
  Installing       : perl-Date-Manip-6.60-1.fc27.noarch           3/4
  Installing       : logwatch-7.4.3-6.fc27.noarch                 4/4
  Running scriptlet: logwatch-7.4.3-6.fc27.noarch                 4/4
  Running as unit:   run-r859e9a9c34c64b2280025d5d33b5a7ac.service
  Verifying        : logwatch-7.4.3-6.fc27.noarch                 1/4
  Verifying        : perl-Date-Manip-6.60-1.fc27.noarch           2/4
  Verifying        : perl-Sys-CPU-0.61-13.fc27.x86_64             3/4
  Verifying        : perl-Sys-MemInfo-0.99-5.fc27.x86_64          4/4

Installed:
  logwatch.noarch 7.4.3-6.fc27                                        
  perl-Date-Manip.noarch 6.60-1.fc27                                  
  perl-Sys-CPU.x86_64 0.61-13.fc27                                    
  perl-Sys-MemInfo.x86_64 0.99-5.fc27                                 

Complete!

安装完成后,我们运行不带选项的 logwatch 命令。为了节省空间,我在某些部分删掉了大量的行。我还在输出中插入了注释,以便在某种程度上描述结果。您的结果将与我的不同,但这将让您很好地理解为什么我让我的好奇心带我到安全的其他方面。

[root@testvm1 ~]# logwatch

 ################### Logwatch 7.4.3 (04/27/16) ####################
        Processing Initiated: Fri Apr  6 14:01:32 2018
        Date Range Processed: yesterday
                              ( 2018-Apr-05 )
                              Period is day.
        Detail Level of Output: 10
        Type of Output/Format: stdout / text
        Logfiles for Host: wally1.both.org
 ##################################################################

前一部分是描述运行命令的条件、日期和时间的标题。下一节包含内核信息,主要是各种服务的开始和停止条目,以及登录。请注意,Logwatch 已经将这一部分从 10,000 多行删减到只有 100 行。我把它砍得更低了。

 --------------------- Kernel Audit Begin ------------------------

 **Unmatched Entries** (Only first 100 out of 10226 are printed)
  audit[1]: SERVICE_START pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=mlocate-updatedb comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
  audit[1]: SERVICE_START pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=sysstat-collect comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
  audit[1]: SERVICE_STOP pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=sysstat-collect comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'

<SNIP>

接下来的几个条目是一些成功登录的结果。

  audit[16590]: CRYPTO_KEY_USER pid=16590 uid=0 auid=4294967295 ses=4294967295 msg='op=destroy kind=server fp=SHA256:e9:53:4c:65:7f:a4:cb:6d:42:0c:40:a3:a4:a2:a9:d3:05:dd:4f:41:3b:26:ed:f6:02:ec:2b:4f:f9:a2:9d:5c direction=? spid=16590 suid=0  exe="/usr/sbin/sshd" hostname=? addr=? terminal=? res=success'
  audit[16590]: CRYPTO_KEY_USER pid=16590 uid=0 auid=4294967295 ses=4294967295 msg='op=destroy kind=server fp=SHA256:2d:39:44:81:f6:e0:47:1f:f3:b1:02:a1:76:73:2e:16:26:6f:d8:e5:7d:2a:4a:ab:76:17:dd:36:54:b1:e6:a5 direction=? spid=16590 suid=0  exe="/usr/sbin/sshd" hostname=? addr=? terminal=? res=success'
  audit[16590]: CRYPTO_KEY_USER pid=16590 uid=0 auid=4294967295 ses=4294967295 msg='op=destroy kind=server fp=SHA256:c4:2a:24:f1:0b:14:d4:4e:eb:33:6b:90:e0:84:c5:64:72:ec:30:72:3c:84:28:72:88:14:e3:1a:9d:d7:de:a9 direction=? spid=16590 suid=0  exe="/usr/sbin/sshd" hostname=? addr=? terminal=? res=success'
  audit[16589]: CRYPTO_SESSION pid=16589 uid=0 auid=4294967295 ses=4294967295 msg='op=start direction=from-server cipher=aes128-ctr ksize=128 mac=hmac-sha2-256 pfs=diffie-hellman-group-exchange-sha256 spid=16590 suid=74 rport=54280 laddr=24.199.159.59 lport=22  exe="/usr/sbin/sshd" hostname=? addr=109.228.0.237 terminal=? res=success'
  audit[16589]: CRYPTO_SESSION pid=16589 uid=0 auid=4294967295 ses=4294967295 msg='op=start direction=from-client cipher=aes128-ctr ksize=128 mac=hmac-sha2-256 pfs=diffie-hellman-group-exchange-sha256 spid=16590 suid=74 rport=54280 laddr=24.199.159.59 lport=22  exe="/usr/sbin/sshd" hostname=? addr=109.228.0.237 terminal=? res=success'

<SNIP>

接下来的几行只是大量登录失败中的两个。这是我第一次看到大量攻击的迹象。仅仅通过视觉扫描很难找到这些,所以我使用 grep 实用程序来找到更多。这种方法并没有给出问题有多糟糕的真实感觉,但是后面的其他部分给出了这种感觉。

  audit[16589]: USER_LOGIN pid=16589 uid=0 auid=4294967295 ses=4294967295 msg='op=login acct="(unknown)" exe="/usr/sbin/sshd" hostname=? addr=109.228.0.237 terminal=ssh res=failed'
  audit[16596]: CRYPTO_KEY_USER pid=16596 uid=0 auid=4294967295 ses=4294967295 msg='op=destroy kind=session fp=? direction=both spid=16597 suid=74 rport=41125 laddr=24.199.159.59 lport=22  exe="/usr/sbin/sshd" hostname=? addr=221.194.47.243 terminal=? res=success'
  audit[16596]: USER_LOGIN pid=16596 uid=0 auid=4294967295 ses=4294967295 msg='op=login acct="(unknown)" exe="/usr/sbin/sshd" hostname=? addr=221.194.47.243 terminal=ssh res=failed'

<SNIP>

 ---------------------- Kernel Audit End -------------------------

cron 部分显示每个 cron 作业在过去 24 小时内运行的次数。

 --------------------- Cron Begin ------------------------

 Commands Run:
    User root:
       /sbin/hwclock --systohc --localtime: 1 Time(s)
       run-parts /etc/cron.hourly: 24 Time(s)
       systemctl try-restart atop: 1 Time(s)

 ---------------------- Cron End -------------------------

下一节列出了大量的身份验证失败。它以一种明确的方式说明了未经授权的访问尝试的问题有多严重。它通过按尝试次数的顺序列出入侵尝试所源自的 IP 地址来做到这一点。

 --------------------- pam_unix Begin ------------------------

 sshd:
    Authentication Failures:
       root (123.183.209.135): 21 Time(s)
       unknown (14.37.169.239): 10 Time(s)
       unknown (85.145.209.59): 10 Time(s)
       unknown (116.196.115.44): 8 Time(s)
       unknown (212.129.36.144): 6 Time(s)
       unknown (84.200.7.63): 5 Time(s)
       root (218.65.30.25): 3 Time(s)
       root (84.200.7.63): 3 Time(s)
       unknown (103.99.0.54): 2 Time(s)
       unknown (196.216.8.110): 2 Time(s)
       ftp (116.196.72.140): 1 Time(s)
       ftp (118.36.193.215): 1 Time(s)
       operator (5.101.40.81): 1 Time(s)
       root (103.26.14.92): 1 Time(s)
       root (103.89.88.220): 1 Time(s)
       root (103.92.104.175): 1 Time(s)
       root (103.99.2.143): 1 Time(s)
       root (118.24.28.246): 1 Time(s)

<SNIP>

       unknown (91.121.77.149): 1 Time(s)
       unknown (95.38.15.86): 1 Time(s)
    Invalid Users:
       Unknown Account: 163 Time(s)
    Sessions Opened:
       root: 2 Time(s)

 systemd-user:
    Unknown Entries:
       session opened for user root by (uid=0): 3 Time(s)

 ---------------------- pam_unix End -------------------------

上面的“无效用户”行显示,在前一天,总共有 163 次尝试侵入我的防火墙主机系统。PAM 负责整体登录安全性,上一节从 PAM 的角度来看登录。下面的 SSHD 部分也是如此。这是几乎相同的信息,但呈现方式略有不同。

在这两个部分,我可以看到针对我的系统的 SSH 攻击的完整列表,这让我很好奇它们的来源以及如何防止它们。

 --------------------- SSHD Begin ------------------------

 Didn't receive an ident from these IPs:
    103.79.143.56 port 58313: 1 Time(s)
    103.79.143.56 port 61578: 1 Time(s)
    103.89.88.181 port 53906: 1 Time(s)
    103.89.88.181 port 57332: 1 Time(s)
    103.89.88.181 port 58951: 1 Time(s)

<SNIP>

    202.151.175.6 port 39552: 1 Time(s)
    217.61.5.246 port 44974: 1 Time(s)
    66.70.177.18 port 33668: 1 Time(s)
    87.98.251.208 port 56975: 1 Time(s)

 Failed logins from:
    5.101.40.81: 1 time
       operator/password: 1 time
    18.188.155.82 (ec2-18-188-155-82.us-east-2.compute.amazonaws.com): 2 times
       root/password: 2 times
    23.97.75.224: 1 time
       root/password: 1 time
    46.105.20.171 (vps16696.ovh.net): 1 time
       root/password: 1 time
    54.37.139.198 (198.ip-54-37-139.eu): 1 time
       root/password: 1 time

<SNIP>

    221.229.166.102: 1 time
       wp-user: 1 time

 Users logging in through sshd:
    root:
       192.168.0.1 (david.both.org): 2 times

 **Unmatched Entries**
 Disconnected from invalid user test 36.77.124.2 port 48914 [preauth] : 1 time(s)
 Disconnected from invalid user ubnt 103.99.2.143 port 55522 [preauth] : 1 time(s)
 Disconnected from authenticating user root 123.183.209.135 port 58498 [preauth] : 1 time(s)
 Disconnected from invalid user ftpuser 36.36.201.21 port 46357 [preauth] : 1 time(s)

<SNIP>

 Disconnected from invalid user avis 201.155.194.157 port 52769 [preauth] : 1 time(s)
 Disconnected from authenticating user root 23.97.75.224 port 1984 [preauth] : 1 time(s)
 Disconnected from authenticating user root 64.41.86.128 port 58134 [preauth] : 1 time(s)
 Disconnected from invalid user sybase 188.187.55.243 port 36344 [preauth] : 1 time(s)
 Disconnected from invalid user cron 221.145.180.62 port 37912 [preauth] : 1 time(s)

 ---------------------- SSHD End -------------------------

这些部分的其余部分是不言自明的,与安全性没有直接关系。因此,我没有对这个输出添加任何进一步的注释。我保留了这些部分,以便您可以看到 Logwatch 可能从典型防火墙的日志中创建的许多部分。

 --------------------- Systemd Begin ------------------------

 Reached target Shutdown: 3 Time(s)

 Started:
     Cleanup of Temporary Directories: 1 Time(s)
     Generate a daily summary of process accounting: 1 Time(s)
     LVM2 metadata daemon: 1 Time(s)
     Network Manager Script Dispatcher Service: 103 Time(s)
     Update a database for mlocate: 1 Time(s)
     User Manager for UID 0: 3 Time(s)
     dnf makecache: 23 Time(s)
     system activity accounting tool: 144 Time(s)
     update of the root trust anchor for DNSSEC validation in unbound: 1 Time(s)

 User Sessions:
     root:  66 68 70

 Slices created:
     User Slice of root 3 Time(s)

 **Unmatched Entries**
    Closed D-Bus User Message Bus Socket.: 3 Time(s)

 ---------------------- Systemd End -------------------------

 --------------------- Disk Space Begin ------------------------

 Filesystem                      Size  Used Avail Use% Mounted on
 devtmpfs                        3.9G     0  3.9G   0% /dev
 /dev/mapper/fedora_wally1-root  9.8G  173M  9.1G   2% /
 /dev/mapper/fedora_wally1-usr    35G  5.3G   28G  17% /usr
 /dev/mapper/fedora_wally1-home  4.9G  262M  4.4G   6% /home
 /dev/mapper/fedora_wally1-tmp    25G   45M   24G   1% /tmp
 /dev/mapper/fedora_wally1-var    30G  6.2G   22G  23% /var
 /dev/sda1                       2.0G  399M  1.5G  22% /boot

 ---------------------- Disk Space End -------------------------

 --------------------- lm_sensors output Begin ------------------------

 coretemp-isa-0000
 Adapter: ISA adapter
 Package id 0:  +82.0 C  (high = +85.0 C, crit = +105.0 C)
 Core 0:        +79.0 C  (high = +85.0 C, crit = +105.0 C)
 Core 1:        +83.0 C  (high = +85.0 C, crit = +105.0 C)
 Core 2:        +80.0 C  (high = +85.0 C, crit = +105.0 C)
 Core 3:        +80.0 C  (high = +85.0 C, crit = +105.0 C)

 ---------------------- lm_sensors output End -------------------------

 ###################### Logwatch End #########################

Logwatch 从日志中提取大量关于 Linux 主机的有用信息。它将系统管理员需要查看的信息量从数万行减少到仅仅几千行。好的方面是它聚集了相关的日志条目,所以它们都在最终报告中的一个地方,这使得扫描更加容易。

对日志观察结果的一些快速分析表明,绝大多数攻击是脚本小子。这些是使用简单脚本的自动攻击,通过简单地尝试登录一系列 IP 地址来定位开放的 SSHD 端口。一旦找到一个开放 SSHD 端口的 IP,脚本可能会花几分钟时间尝试使用随机但已知的用户 id 和基于字典的密码登录。目的是如果他们可以获得访问权,用一些恶意软件感染计算机。

脚本小子对破解受到良好保护的主机不是特别认真。他们在寻找容易得手的机会——管理不善、保护不力或根本没有保护的主机。严重的黑客,那些针对特定的人或企业,完全是另一回事。一个足够认真的黑客最终会找到进入你电脑的方法。

在我的情况下,这些不是严重的饼干。日志观察结果显示,我的防火墙每天被攻击数百次,有时甚至数千次。这些攻击是随机的,而且从来没有持续过。但是我仍然需要减少攻击次数来提高我的安全性。随着时间的推移,我使用了多种策略来做到这一点。每走一步,我的好奇心都被新的信息、对问题的新观点,或者仅仅是好奇心本身所点燃。

防火墙

为了阻止这种攻击,我首先求助于 iptables 防火墙,因为我对它已经很熟悉了。几个星期以来,我花了大量时间将最恶劣的罪犯的 IP 地址添加到我的 iptables 防火墙中。

出于好奇,我想确定源 IP 地址来自世界的哪个地方。所以我开始用whois来确定。如果地址来自某个我知道永远都不需要自己登录的地方,我就简单地屏蔽掉整个地址范围。

这很容易做到,但它确实阻止了一些想看我网站的人。当阻止整个 A 类 IP 地址时,这种方法是多余的,但手动添加单个地址会花费大量时间。因此,出于懒惰的管理员的真实态度,以及“自动化一切”,我对自动向我的防火墙规则添加 IP 地址感到好奇。

失败 2 班

经过一番探索和研究,我找到了 fail2ban,这是一款开源软件,可以自动完成我以前手动完成的工作。"使用开源软件."

Fail2ban 有一系列复杂的可配置匹配规则和单独的操作,当试图侵入系统时可以采取这些操作。它针对许多类型的攻击制定了规则,包括 web、电子邮件和许多其他可能存在漏洞的服务。Fail2ban 的工作原理是检测攻击,然后向防火墙添加一条规则,在指定和可配置的时间内阻止来自该特定单个 IP 地址的进一步尝试。时间到期后,它会删除阻止规则。

当 IP 地址被阻止时,fail2ban 用来通知系统管理员的方法之一是发送电子邮件。默认情况下,电子邮件发送给 root 用户,但也可以进行配置。我没有配置许多不同的工具来发送电子邮件到我的个人地址,而是允许它们发送到 root 和/etc/aliases 文件(我已经配置好了),将所有电子邮件重新路由到 root 以发送给我。

出于好奇,我花了一些时间来调整规则,因为 Fail2ban 将在指定时间内进行指定次数的破解尝试后阻止一个 IP 地址。我发现——就我的环境和需求而言——在十分钟内尝试三次完全符合我的要求。我还发现,封锁一个 IP 地址至少 24 小时,而不是默认的 10 分钟,确实有助于阻止惯犯。因此,下面的示例清单 22-1 中的结果是基于那些过滤规则的。

示例清单 22-1

在安装了 fail2ban 并根据我的需要配置它几天后,我再次运行 logwatch,fail2ban部分如下所示。

 --------------------- fail2ban-messages Begin ------------------------------

 Banned services with Fail2Ban:                             Bans:Unbans
    my-sshd:                                                [ 35:35 ]
       123.183.209.135                                        17:17
       84.200.7.63                                             4:4  
       212.129.36.144 (212-129-36-144.rev.poneytelecom.eu)     3:3  
       218.65.30.25 (25.30.65.218.broad.xy.jx.dynamic.         3:3  163data.com.cn)
       18.188.155.82 (ec2-18-188-155-82.us-east-2.compute.     1:1  amazonaws.com)
       66.70.177.18 (ns545339.ip-66-70-177.net)                1:1  

<SNIP>
       183.230.146.26                                          1:1  

 Fail2Ban hosts found:
     my-sshd:
        103.20.149.252 - 2018-04-05 11:01:55 (1 Times)
        103.20.149.252 - 2018-04-05 11:01:57 (1 Times)
        103.26.14.92 - 2018-04-05 07:43:26 (1 Times)
        103.26.14.92 - 2018-04-05 07:43:28 (1 Times)
        103.28.219.152 - 2018-04-05 05:17:57 (1 Times)
        103.28.219.152 - 2018-04-05 05:18:00 (1 Times)
        103.89.88.220 - 2018-04-05 11:57:17 (1 Times)
        103.89.88.220 - 2018-04-05 11:57:19 (1 Times)
        103.92.104.175 - 2018-04-05 02:23:13 (1 Times)
        103.92.104.175 - 2018-04-05 02:23:14 (1 Times)
        103.99.0.32 - 2018-04-05 16:15:59 (1 Times)
        103.99.0.32 - 2018-04-05 16:16:01 (1 Times)
        103.99.0.54 - 2018-04-05 04:17:38 (1 Times)

<SNIP>
        88.87.202.71 - 2018-04-05 01:31:22 (1 Times)
        90.84.44.20 - 2018-04-05 19:01:06 (1 Times)
        90.84.44.20 - 2018-04-05 19:01:08 (1 Times)
        91.121.105.20 - 2018-04-05 14:21:39 (1 Times)
        91.121.105.20 - 2018-04-05 14:21:41 (1 Times)
        91.121.77.149 - 2018-04-05 04:28:28 (1 Times)
        91.121.77.149 - 2018-04-05 04:28:29 (1 Times)
        95.38.15.86 - 2018-04-05 22:13:11 (1 Times)
        95.38.15.86 - 2018-04-05 22:13:13 (1 Times)

 ---------------------- fail2ban-messages End -------------------------

寻找源头

出于对我的防火墙上的这些攻击的来源的好奇,我开始收集这些电子邮件以便分析它们。它们通常如清单 22-2 所示。这是电子邮件的完整来源,因此您可以详细检查它。

清单 22-2

Received: from wally1.both.org (wally1.both.org [192.168.0.254])
        by bunkerhill.both.org (8.14.4/8.14.4) with ESMTP id w34E9NnR002675
        for <dboth@millennium-technology.com>; Wed, 4 Apr 2018 10:09:23 -0400
Received: from wally1.both.org (localhost [127.0.0.1])
        by wally1.both.org (8.15.2/8.15.2) with ESMTP id w34E9NTA013030
        for <dboth@millennium-technology.com>; Wed, 4 Apr 2018 10:09:23 -0400
Received: (from root@localhost)
        by wally1.both.org (8.15.2/8.15.2/Submit) id w34E9NBq013023
        for dboth@millennium-technology.com; Wed, 4 Apr 2018 10:09:23 -0400
Message-Id: <201804041409.w34E9NBq013023@wally1.both.org>
Subject: [Fail2Ban] SSH: banned 123.183.209.135 from wally1.both.org
Date: Wed, 04 Apr 2018 14:09:23 +0000
From: wally1 <wally1@both.org>
To: dboth@millennium-technology.com
X-Spam-Status: No, score=-48 required=10.6 tests=ALL_TRUSTED,BAYES_00,USER_IN_WHITELIST
Content-Type: text/plain
MIME-Version: 1.0
X-Scanned-By: MIMEDefang 2.83 on 192.168.0.51

Hi,

The IP 123.183.209.135 has just been banned by Fail2Ban after
3 attempts against SSH.

Here is more information about 123.183.209.135:

GeoIP Country Edition: CN, China

Regards,

Fail2Ban

这是我收到的电子邮件类型的一个典型例子。主题行包含来源的 IP 地址、报告攻击的主机名称和 Fail2ban 规则,在本例中是 SSH。我想使用原始 IP 地址来识别来源国。Fail2ban 规则集包括一个使用 GeoIP 的选项,geo IP 是一个搜索 IP 地址及其分配到的国家的数据库的程序,以确定攻击来自哪个国家。这是一个有趣的侧游,我安装它没有太大的困难。对于 Fedora,这是我的防火墙所使用的,GeoIP 位于 Fedora 存储库中。对于 CentOS,它位于 EPEL 5 存储库中。

在收集了大量的 Fail2ban 邮件后,我把它们从雷鸟导出到一个主题行列表中。我编写了一个脚本来读取列表,并使用 GeoIP 来识别来源国家,并按频率生成国家列表。

最频繁和持续的攻击似乎来自亚洲的各个地区、东欧、南美的几个国家和美国。

收集电子邮件

为了收集这些邮件,我开始使用雷鸟中可用的过滤器来识别 Fail2ban 发送的邮件,并将它们移动到特定的文件夹中。这工作正常,但我发现雷鸟过滤器并不总是准确的。这并不是造成问题的唯一过滤器。

我决定我需要一些不同的东西来过滤和分类我的邮件——所有的邮件,而不仅仅是 Fail2ban 的邮件。

procmail

我以前听说过 procmail。它作为默认的本地传递代理(LDA)安装在所有基于 Red Hat 的发行版上。这让我在这个项目中使用 procmail 变得不需要动脑筋。该项目是,首先,将 Fail2ban 发送的电子邮件分类到一个专门为他们准备的文件夹中,其次,也对其他电子邮件进行分类。

我还想解决一个技术问题。客户端电子邮件过滤依赖于在邮件存入收件箱后对其进行扫描。由于某种未知的原因,有时客户端不会从收件箱中删除(清除)已移动的邮件。这可能是雷鸟的问题(也可能是我的雷鸟配置的问题)。我多年来一直致力于解决这个问题,但没有成功,甚至通过多次完全重装 Fedora 和 Thunderbird 也没有成功。

为了解决这些多重问题,我需要一种基于服务器而不是基于客户端的方法来归档电子邮件(即,将它们分类到适当的文件夹中)。这将意味着,没有必要为了执行电子邮件分类而保持雷鸟或任何电子邮件客户端运行。

在对 procmail 做了一些研究后,我能够创建一个 procmail 规则,将收到的 Fail2ban 电子邮件分类到一个特定的文件夹中。我还创建了一些其他规则,将其他类型的电子邮件分类到不同的文件夹中。

虽然我使用 SpamAssassin 来识别垃圾邮件,但我现在使用 procmail 规则将垃圾邮件归档到我的垃圾邮件文件夹中。我还收到了一些 SpamAssassin 规则似乎永远无法识别的垃圾邮件。在不久的某个时候,每当看到我的收件箱里那些特定的垃圾邮件时,我会创建一个或多个规则来过滤它们。我也对我的垃圾邮件过滤的功效感到好奇,所以我没有删除垃圾邮件,而是将它存储了几天,以防我需要查看 SpamAssassin 分数来改进这些规则。

2017 年 11 月,我为 Opensource.com 写了一篇关于使用 SendMail、SpamAssassin、procmail 对邮件进行分类整理的文章 6 。它比我这里讲的更详细,是关于解决多个问题,而不仅仅是将 Fail2ban 邮件分类到一个特定的文件夹。

rkhunter

在看到每天有多少人试图侵入我的防火墙后,我更加担心了。因此,我对如何进行的好奇心引导我发现了根工具包。我决定开始专门寻找 root kits,这是安装在被黑客入侵的主机上的软件包。他们允许黑客访问主机,并根据自己的目的控制它。

确保没有安装 root kit——至少没有已知的——是一个名为 rkhunter 的软件工具执行的任务。我安装了 rkhunter 并定期运行它,以确保没有安装任何已知的根工具包。

当然,最后一句话表明了任何旨在扫描计算机系统中任何类型恶意软件的软件的问题——它只能找到已知的恶意软件,并且已经为其开发了签名。当你发现一个根包的时候,损害已经造成了,是时候清除一切并重新开始了。

我确实使用 rkhunter,但我并不仅仅依赖它来保证我的网络安全。

我试图阻止的攻击是简单的脚本密码破解。因此,显而易见的步骤是确保密码基本上是不可破解的,并在我的网络内部使用 SSH 和公钥/私钥对(PPKP)进行系统间通信。

因此,我为所有内部系统设置了长度适中的密码,为防火墙主机设置了极长的密码。我一直使用 SSH 和 PPKPs 登录我网络上的其他主机,所以我在那里的操作程序没有重大变化。我不会在我的防火墙上描述这种设置的细节,因为它可能会为严重的黑客提供一些帮助。此外,这一部分更多的是关于我的好奇心带我去的方向,而不是关于我为保护我的系统所采取的所有步骤的细节。

所以这次对我的防火墙主机系统安全性的探索非常有启发性。我已经开始做一些非常基本的安全工作,但是直到我真正看到这个问题,我才完全理解这个问题的范围。一步一步,我的好奇心把我带进了保护我的系统的世界。我用这些知识来保护我和我的客户的系统。

如果这个问题没有被 Logwatch 清楚地提出来,我可能不会有足够的好奇心去探究安全和防火墙。因此,我的系统更加安全。

我还研究了我的系统上的一些其他安全选项,但是像各种形式的入侵检测这样的事情往往是在事后进行的,它们所带来的管理负载超过了我自己的系统所需要的。在这种情况下,风险降低并不能证明启用和维护风险所需的工作成本是合理的。

SELinux 7 也默认安装在所有基于 Red Hat 的发行版上,我的好奇心也曾多次带我走上这条路。我发现——在我的环境中——它不适合在我的所有主机上使用。我确实在我的防火墙上启用了限制模式,因为这是我获得最大成果的地方。任何严重的攻击者都需要首先突破防火墙,这样就会触发警告,有人正试图侵入我的网络。然后,我可以在违规行为实际发生之前采取适当的措施。但是,请记住,没有完美的安全性这种东西。任何一套安全防范措施的目的都是让入侵您的系统在时间和精力上比坏人愿意付出的代价更高。归根结底是你能承受多大风险的问题。

这一切都是从我的好奇心开始的。我原本打算简化检查日志文件中潜在问题的任务。这让我想到了一些非常有趣的方向,其中最主要的是我的网络安全,尤其是防火墙。它还带我去了一些其他地方,我甚至没有在这里介绍,只是因为你应该已经明白这一点。

跟随你自己的好奇心

我已经不止一次地提到过,你应该探索 Linux 的许多方面,并且去你的好奇心引导你去的任何地方。只是通过跟随我的好奇心,首先是关于电子学,然后是计算机、编程、操作系统、OS/2、Linux、服务器、网络等等,我才能够做这么多有趣的事情。

你心中可能有具体的个人和职业目标,这可能会激发你的好奇心,带你去能帮助你实现这些目标的地方。你也可能是一个天生好奇的人,更倾向于对你特别感兴趣的事情好奇,而不执着于特定的目标。你的好奇心如何被驱使并不重要。重要的是你要跟随它,不要让任何人或任何事挫伤你的好奇心。

成为一名作家

我目前为 Opensource.com8写了许多文章,无论我写什么,我总是能学到新的东西,甚至是我已经熟悉的东西。我写过的每一篇文章,无论是为 Linux 杂志Linux 杂志还是 Opensource.com 写的,都是满足我好奇心和了解更多 Linux 知识的机会。

写这本书也不例外。甚至当我研究这本书的各个方面时,我学到了更多我已经知道的命令,并且我学到了一些新的命令。我让我的好奇心带我走上了本书中永远不会出现的道路,只是因为学习关于 Linux 的新事物很有趣,而且有太多东西需要学习。

找到我想写的话题几乎从来都不是问题。我通常用最近发生的事情作为我文章的主题。值得写的事情总是在发生。这只是一个识别它们并把故事用语言表达出来的问题。在写这本书的过程中发生了许多事情,这些事情成为了这本书的一部分;我在几章中提到了这些。

有时,当我偶尔努力将我的哲学转化为语言时,我对这一哲学有了更多的了解,了解了我是如何使用它的,以及它是如何帮助和指导我的。我了解到,在许多方面,我的哲学不仅仅是关于 Linux。

失败是一种选择

我没有失败。我刚刚发现了一万种行不通的方法。

—托马斯·A·爱迪生

虽然在测试过程中,数千种个别材料和制造技术的特定组合都失败了,但爱迪生还是继续实验。正是如此,未能解决问题或创建执行其定义任务的代码并不意味着项目或总体目标会失败。这只意味着特定的工具或方法没有产生成功的结果。

我从失败中学到的东西比其他任何方式都多。我尤其为那些自己造成的失败感到高兴。我不仅必须纠正我自己造成的问题,而且还必须找到并解决最初的问题。这总是会导致大量的研究,使我学到比快速解决原始问题更多的东西。

这是我的天性,我认为这是所有优秀系统管理员的天性,将这些情况视为学习机会。如前所述,我已经当了很多年的培训师,一些最有趣的经历是当我在教学时,演示、实验和实验室项目失败了。对我和我班上的学生来说,这些都是很棒的学习经历。有时我甚至把那些偶然的失败融入到以后的课程中,因为它们使我能够教授一些重要的东西。

做就是了

每个人都以自己的方式学得最好。作为一名培训师,我每次上课都会看到这种情况,不管是什么科目。追随我们的好奇心是一样的——我们都有那种引导我们发现更多的火花。我们的方法可能不尽相同,但它们会引导我们获得更多的知识和技能。

我开始在家里所有的电脑上安装 Linux。这迫使我学习 Linux,不回头。只要我有办法回到我的老的和众所周知的做事方式,我就没有必要真正学习 Linux。当我决定学习 Linux 时,我就是这么做的,它教会了我很大一部分知识。我有几台电脑,并在我的家庭办公室创建了一个完整的内部网络。这些年来,我的人际网络不断发展变化,每一次变化都让我学到了更多。这很大程度上是由我的好奇心驱动的,而不是任何特定的需求。

我有来自 ISP 的静态 IP 地址和两个防火墙来提供外部访问和保护我的内部网络。其中一个防火墙是树莓 Pi,上面有 CentOS。这些年来,我一直用英特尔盒子装着 Fedora 和 CentOS。我学到了很多关于在防火墙和路由器的角色中使用这两者的知识。

我有一台运行 DHCP、HTTP、SMTP、IMAP、NTP、DNS 和其他服务的服务器,以便为我的内部网络提供这些服务,并使其中一些服务对外部世界可用,例如我的网站和接收的电子邮件。我学到了很多关于在服务器角色中使用 Linux 的知识。我学到了很多关于实现和管理这些服务的知识。

我有几个台式工作站,一台笔记本电脑,一个 EeePC,都连接到我的有线网络。EeePC 和笔记本电脑也可以使用我的一个无线路由器进行连接;由于每月费用的原因,我不使用 ISP 提供的无线网络,也没有机会学习如何配置无线路由器。我还有几部智能手机、一部 Kindle 和一部 iPad。学习如何设置我的电子邮件服务器来最好地使用这些工具,同时尽最大努力以安全的方式提供这些服务是一项挑战。

对我来说,好奇心是学习的动力。我不能只是坐在教室里,因为有人说我需要学习特定的东西,并在这方面取得成功。我需要对这个主题感兴趣,并且需要一些能激起我好奇心的东西。这种在我喜欢的科目上更加努力学习的倾向在我上学期间非常明显,因为我在吸引我的科目上表现很好。

摘要

通过利用我的家庭网络来满足我的好奇心,我有了很多安全的空间,可以在灾难性的失败中学习从失败中恢复的最佳方法。失败的方式有很多,所以我学到了很多。当我不小心打碎东西时,我学到了最多,但当我故意打碎东西时,我也学到了很多。在这种情况下,我知道我想学什么,并可以针对破损的方式,使我能够了解这些具体的事情。

我也很幸运,因为我有几份工作需要,或者至少允许我上 Unix 和 Linux 各方面的课。对我来说,课堂作业是一种验证和巩固我自己所学知识的方式。这让我有机会与大多数知识渊博的老师交流,他们可以帮助和澄清我对自己无法理解的零碎内容的理解。

做一个好奇的系统管理员。对我很有效。

二十三、没有应该

直到我开始写这本书,尤其是关于我为 Opensource.com 创作的竞赛的部分,这才真正成为我的信条之一。当我在写这一节的时候,我突然意识到我已经不止一次地使用了“没有应该”这个短语。我甚至在第二章中简单地讨论过它,所以我开始以一种新的方式思考这个问题,并决定它真的应该成为一个信条。

这个原则是关于可能性的。这也是本书所有章节中最具禅意的一章。它更多的是关于我们的大脑如何工作来解决问题,而不是特定的技术。这也是关于克服或至少认识到一些阻碍我们充分利用自身潜力的障碍。

总是有可能的

本书涵盖的每一个原则都揭示了一些关于 Linux 的基本事实,以及作为一名系统管理员,您可以如何与之交互。我并不是说这些事实是关于你“应该”如何与 Linux 交互的。在 Linux 中没有“应该”

img/462716_1_En_23_Figa_HTML.png

在《卡恩之怒》中,斯波克说,“凡事皆有可能。”有了 Linux,总是有各种可能性——有许多方法来处理和解决问题。这意味着您可能以一种方式执行任务,而另一个系统管理员可能以另一种方式执行。没有一种“应该”完成任务的方法。只有你做这件事的方式。如果结果符合要求,那么达到要求的方式就是完美的。

我在第四章“转换数据流”的“管道挑战”一节中提供了一个很好的例子来自世界各地的 80 多名系统管理员提交了他们对我在 Opensource.com 竞赛中提出的一个问题的解决方案。一些解决方案非常接近相同,但是没有两个是完全相同的,许多是非常不同的。每个系统管理员都有一个独特的、创造性的解决方案来满足竞赛的要求。

一个单一的问题,在面对如此多不同的人时,怎么会产生如此广泛的解决方案呢?这里有两个因素在起作用。这是因为乍一看,Linux 表面上的复杂性实际上是其难以置信的灵活性。这个特定问题的许多不同解决方案是 Linux 系统管理员可以使用的许多不同命令和实用程序的直接结果。

第二个因素是,哪些 Linux 系统管理员通过许多不同的方式学习了 Linux,而我们的体验是如此不同。也正是这些经历让我们认识到,Linux 和开源软件不受限制的特性让我们对操作系统有了更充分的理解和推理。即使使用完全相同的命令,我们也可以找到不同的方法来解决手头的问题。

释放力量

我读到和听到很多人谈论“利用……的力量”,不管他们今天在宣传什么。这些都是多次营销活动。许多自助大师谈论驾驭心灵的力量,或“内在的力量”,尽管“内在的力量”从来没有确切的定义,也没有被提及。

我在谷歌上搜索了“驾驭 Linux 的力量”,找到了大约 14 篇关于这个短语的文章。我谷歌了一下“释放 Linux 的力量”,得到了六个结果。这对我说明了一个问题的角度。当我们谈论驾驭某物的时候,我们暗示我们想要包含某物或者把它置于控制之下;当我们谈论释放某物时,我们是从释放它的角度来思考的。

在某些方面,这是关于语义的 1 ,因为语义是我们如何思考的指示。语义学是对意义的研究,它考虑诸如词语选择及其对意义的影响。我们使用的词语对说话者或作者来说有不同于听者和读者赋予它们的意义。我认为在计算领域,尤其是在 Linux 领域,选择“释放”与“利用”是很有启发性的。

我非常不科学的观察让我得出了这样一个理论:我们这些 Linux 系统管理员倾向于更多地考虑“释放”或“释放”Linux 的力量。我相信,我们 Linux 系统管理员在解决 Linux 问题时,比那些似乎更多地考虑“利用”和“限制”的人对我们的思维限制更少我们有如此多简单而强大的工具可用,以至于我们不会发现自己受到操作系统或任何关于我们使用的工具或我们可能应用它们的操作方法的束缚。

当成千上万的人的想象力被释放到可以用开源软件解决的问题上时,就有了无法估量的力量。

问题解决

我们大多数人几乎不知道如何解决问题。这可能会削弱我们这样做的能力。解决问题是一门非常依赖科学方法和批判性思维的艺术。理解这个概念可以把我们从制度化思维强加给我们的认知限制中解放出来。

教育系统教导我们以特定的方式思考,似乎在宣扬当今解决问题的方法。例如,我用所谓的传统方法学习数学基础。我学习了数字系统,计算和与差的算法,包括位置值,进位和借位的概念;我记住了乘法表、除法的具体算法等等。我的孩子用不同的方法学习数学,这可能是“新数学”今天,我的孙子们正在学习新的数学。

读过一些关于当前数学教学的方法,我希望我也是这样学习的。我喜欢这样一个事实,学生们被教授定义明确的、可重复的、可教授的解决数学问题的过程。

在 2017 年发表在Journal of Physics:Conference Series、 3 、的一篇文章中,作者建立了学习数学的调查方法与批判性思维技能增加之间的正相关关系。批判性思维是系统管理员解决问题时的一项关键技能。

问题是,我喜欢用别人教我的方式做数学。对我来说,这比最近的方法更容易,尽管我现在对这些方法有了更好的理解,这是我为这一章所做研究的结果。我看过我的孙子们解决数学问题,我不知道他们在做什么,也不知道他们如何得出正确的结果。用我的“旧数学”方法检查他们的结果,我得到了相同的结果——除了他们出错的那些。或者是我犯了错误。主要是我的结果错了。但是当我们都答对的时候,答案肯定是一样的。

那么,谁是对的?我们中谁用了正确的方法来解决这些问题?当我们在学校的时候,得到正确的答案只是解决问题的一部分。另一部分是使用正确的算法,也就是今天教授的算法,来得出正确的答案。教授的是算法,以及我们如何选择正确的算法,并将其应用于手头的数学问题。在教育系统之外,在所谓的现实世界中,唯一重要的是计算的数字结果是正确的。

在 Linux 和计算机领域,最重要的是解决手头的问题。无论这是硬件问题、软件问题还是其他问题,都无关紧要。修复它是衡量系统管理员成功的标准。

打破机构教学方法教给我们的“应该”,特别是我们盲目遵循的死记硬背和算法,可以解放我们,让我们以新的方式思考解决问题。这并不意味着这些方法是错误的,只是其他方法也可以考虑,它们可能更适合特定的情况,特别是解决技术问题。

用“应该”囚禁我的不仅仅是机构。很多时候,是我自己。我发现自己在盒子里思考,因为那是我认为我“应该”在的地方。当我发现自己被“应该”困住时,有一些方法可以防止这种情况的发生并恢复过来。

在第二十四章,“指导年轻的系统管理员”,我们将详细探索一种植根于科学方法的解决问题的算法。现在,让我们来看看解决问题和避免思维受限所需的两项重要技能——批判性思维和推理。

批判性思维

早在第一章中,我简单提到了我在一些我工作过的地方参加了对潜在新员工的面试。我们会先问受访者一些基本问题,然后再问一些更难的问题,旨在探索他们知识的极限。我们考虑的大多数人都能相当容易地通过这一阶段的面试。

当我们开始问一些问题时,我们对许多潜在新员工产生了担忧,这些问题要求他们观察问题情况,并通过一系列步骤进行推理,使他们能够确定假设问题的原因。大多数人做不到这一点。他们解决问题的标准方法是重启计算机,而不做任何真正的问题分析。然后,他们通常的方法是在一个序列中使用一组特定的脚本动作,希望能够根据一组特定动作修复特定症状的概率来解决问题。从来没有人试图去理解为什么要采取具体行动的原因,或者找出问题的根源。

我称之为“症状-修复”方法。它基本上是一个脚本——一系列选择——可以在很少或根本不了解底层系统如何工作的情况下执行。当涉及限制性系统时,这是修复损坏的计算机和其他设备的常用方法。这是唯一真正有效的方法,因为限制性和封闭的系统不可能像开放系统那样真正为人所知,尤其是像 Linux 这样的操作系统。

那些能够通过我们为他们设置的麻烦场景进行推理的绝大多数人往往对 Unix 和 Linux 有丰富的经验。在我看来,这是因为 Unix 和 Linux 用户和系统管理员考虑解决问题的方式与那些使用限制性更强的操作系统的人不同。使用和管理 Unix 和 Linux 系统需要更高水平的推理技能。Unix 和 Linux 不受约束的特性也邀请我们学习和提高这些技能。凭借对功能强大的操作系统的深刻了解、对可用工具的透彻理解以及成熟的批判性思维技能, 4 Linux 系统管理员能够快速解决问题,并在选择和使用工具时拥有极大的自由度。

批判性思维是 Linux 和 Unix 系统管理员如此擅长我们工作的关键因素。它让我们能够观察问题的症状,确定什么是重要的,什么是不重要的,将这些症状与我们以前的经验或知识联系起来,并利用这些来确定问题的一个或多个可能的根本原因。

请不要误解我。有许多非常聪明的系统管理员使用 Windows 和其他封闭的专有操作系统。所有这些非常聪明的系统管理员也使用批判性思维和推理来解决问题。真正的问题是他们工作的系统的封闭性,这限制了他们可以利用的可能性。

解决问题的推理

另一项有助于系统管理员解决问题的技能是推理。 5 在我们的批判性思维使我们能够看待问题的症状之后,我们可以使用不同形式的推理来确定出现症状的一些可能的根本原因,以便确定下一步的步骤。

有四种被广泛认可的推理形式,我们系统管理员使用它们来帮助我们解决问题。我们使用归纳、演绎、溯因和综合推理 6 来引导我们得出结论,指出观察到的症状的一个或多个可能的原因。让我们简单看一下这些推理形式,看看它们是如何应用于问题解决的。

演绎推理

这是我们大多数人都知道的最常见的理性形式。它用于从大量更一般的观察中得出关于特定实例的结论,从而形成一般规则。例如,下面的三段论阐释了演绎推理——及其主要缺陷。

一般规则:电脑温度升高是由一个机械设备(风扇)故障引起的。

观察实例:我的电脑过热了。

结论:我电脑里的风扇坏了。

这种演绎推理多次成功地解决了过热问题。然而,结论完全取决于规则和当前观察的准确性。

考虑其他可能性。计算机房的环境温度可能非常高,导致计算机内部的温度更高。或者 CPU 上的散热片可能被灰尘堵塞,从而减少气流,从而降低冷却系统的效率。我也能想到其他可能的原因。

这个三段论和所有演绎推理一样,有一个巨大的谬误。为了使结论为真,规则和断言必须总是正确的。这种谬误并没有让使用这种类型的推理变得错误,但它确实告诉我们,我们确实需要小心。

归纳推理

归纳推理流向相反的方向。结论是从一些观察中得出的,有时只是一个观察。这个归纳推理的例子也显示了潜在的内在谬误。

观察:一个风扇的故障导致我的电脑过热。

结论:电脑总是因为风扇故障而过热。

实际上,从中可以得出更多同样糟糕的结论。一种是所有风扇故障都会导致电脑过热,这也是不正确的。另一个是所有电脑迷都失败了。还有一个原因是所有的电脑都会因为风扇故障而过热。

同样,我们必须小心我们得出的结论。在这种类型的归纳推理中,我们很可能会综合出一个一般规则,当我们把这个规则作为演绎三段论中的一个断言来应用时,这个规则会把我们引入歧途。

理性失败

演绎推理和归纳推理都包含着失败的种子,因为它们错误地假设所有的证据都是可用的,所有的断言都是正确的。这两种类型的推理都是僵化和不灵活的。演绎推理和演绎推理都不考虑可能性、概率、不完整的数据、不正确的断言、随机性、直觉或创造性。

让我们来探讨一下这个问题。首先,我声明在这个思维实验中,我们没有任何经验或训练来帮助我们确定问题的原因。

我的电脑过热了。我能感觉到箱子的顶部,它比我过去经历过的任何时候都要热。我关掉电脑,打开机箱后,我又把它打开了一会儿。我现在可以看到一个大型机箱风扇不旋转。

因为我没有理由认为故障的风扇是问题所在,所以我只是碰碰运气,换了一个新的可以正常工作的风扇。这解决了问题,计算机不再过热。

我用了一点归纳推理如下。

观察:我通过更换机箱风扇修复了一台过热的电脑。

规则:更换机箱风扇可以解决电脑过热问题。

我举了一个例子,并把它归纳成一条规则。现在我们来看另一个问题。在这种情况下,另一台计算机过热。下面是我的演绎逻辑。

规则:更换机箱风扇修复电脑过热问题。

断言:电脑过热。

结论:我应该换掉 case fan。

在这一点演绎逻辑中,我把我从过热的单一经验中创造的规则应用于计算机过热的第二个实例。我采用了一个通用规则,并将其应用于一个具体的实例。逻辑是正确的。逻辑上没有错误,但是更换机箱风扇并不能解决问题。为什么呢?因为在第二种情况下,电源过热,因为空气入口被环境中的灰尘堵塞。

这里的困难首先在于这样一个事实,即我们第一次过热经历所产生的规则是有缺陷的,因为它太笼统了。第二个问题是,基于这个单一的错误规则和这种推理形式强加的僵化,如果迫使我得出一个结论,那可能是问题的唯一可能原因,所以我停止寻找其他根本原因。这种逻辑导致我甚至懒得去检查风扇是否工作。

这一套僵化逻辑的另一个问题是,没有其他可能性的灵活性。我们的规则集太有限,无法解决这个问题。这就提出了一个问题:我们是否能有一个足够大的规则集来解决所有可能的问题,或者任何一个规则都可能足够复杂来解决任何时候的单一症状。你明白我的意思了吗?

溯因推理

溯因推理是第三种公认的推理形式,它更复杂,也更灵活。它允许不完全的信息和特定关系存在的概率。它还允许有时最好的方法是根据现有信息进行有根据的猜测。

溯因推理利用了所有可用的数据——我们的观察——并允许我们得出结论,指出所观察到的症状的一个或多个最可能的根本原因。无论我们是否掌握了所有的信息,溯因推理都会起作用。它允许我们根据手头的最佳信息得出结论。它允许灵活性,因为我们从以前的归纳推理中制定的任何规则,以及我们使用演绎推理从这些规则中得出的任何结论,都不是严格执行的。

有了溯因推理,我们不需要像归纳推理和演绎推理那样接受结论是唯一可能的结果。然后,我们可以自由地调整我们的规则,用新的数据重新开始我们的推理过程,也就是说,以前的推理是不正确的——,在这个例子中是。因此,我们现在拥有的推理自由是综合推理的基础。

综合原因

我相信系统管理员使用前面讨论的所有三种推理形式来解决问题。事实上,我们做得如此天衣无缝,以至于很难确定我们思维过程中代表三种公认推理形式之一的特定部分。事实上,这种类型的组合推理是成功的系统管理员所使用的,而不是单一的风格。这叫综合理性。

例如,我已经有了关于过热的规则,用来推断可能的原因。这个例子说明了灵活性和使用有限的信息来分析问题,并使用额外的测试来获得更多的数据。它还考虑到了归纳过程,这种过程可以向我们在演绎过程中使用的规则集添加更多的规则。也有可能忽略和抛弃明显不正确、过时或不再需要的规则。

综合推理对我来说感觉天衣无缝,也许对你来说也是如此。例如,当我在解决问题的过程中从演绎推理转换到溯因推理时,我几乎不知道我正在做这件事,而且几乎没有任何迹象表明我在做这件事。综合推理,有意或无意,有意识或无意识,帮助我避免“应该”的陷阱。不总是,但肯定是大部分时间。通过理解我自己的推理过程,我可以更容易地认识到我什么时候陷入了“应该”的陷阱,并且更容易地找到摆脱它的方法。对于我们过热的计算机,这可能意味着一个更像这样的推理过程。

电脑过热,我从以前的经验知道,至少有两种可能的原因。我检查了电脑,发现没有一个风扇出现故障,电源也没有过热。由于我已经知道的两个可能原因都不是当前问题的根源,我使用hddtemp命令和 touchy-feely 方法做了一些进一步的检查,这两种方法都显示了一个硬盘非常热的事实。

我可以更换硬盘,但我发现硬盘周围没有气流。进一步研究发现,有一个地方可以安装一个风扇,在硬盘上产生冷却气流。我安装了一个新风扇。然后我检查硬盘,它的温度现在低多了。

在这个实际问题的情况下,我不只是盲目地更换过热的组件。硬盘本身不是问题的原因,尽管可以观察到它非常热。缺少风扇来提供冷却气流也是一个原因,还有其他因素。首先,尽管风扇提供了足够的气流将驱动器冷却到正常水平,但我很好奇,所以我使用 System Activity Reporter–SAR 检查了它的使用模式。SAR 日志显示该驱动器一直处于频繁使用状态。使用htopglances进行的额外调查显示,/home 文件系统被一个名为 baloo 的程序频繁访问。

我的文件系统分布在两个物理硬盘上,但是最常用的两个/home 和/var 在同一个硬盘上。为了减少硬盘上的压力,我的第一步是安装一个新的硬盘来分散负载,并将最常用的/home 文件系统移到这个新的硬盘上。然后,我对 baloo 做了一些研究,它是一个文件索引器,是 KDE 桌面环境的一部分。我想出了如何关闭它,这将/home 中的磁盘活动减少到几乎为零,除了我自己的工作。

事实上,这种过热的单一症状有多种原因,我实施的所有修复都是适当的。根本原因是一个流氓程序在单个文件系统中产生大量活动。这导致了高水平的磁盘活动,从而使磁盘驱动器过热。由于没有冷却风扇,硬盘上的气流不足,这加剧了问题的严重性。

是的,这是一个真实的事件,并不罕见。遵循严格的逻辑形式永远不会把我们带到真正解决问题的地方,并减少问题再次发生的机会。溯因推理让我们既有逻辑又有创造力,并能跳出所谓的框框去思考。它还允许我们采取预防措施,以确保相同或相关的问题不再发生。

溯因推理允许我们从经验中学习。这不仅在事情进展顺利并且我们解决了问题的时候是正确的,尤其是在事情进展不顺利并且我们没有把事情做好的时候。

自我认知

当然,这些推理风格是人工结构,旨在使哲学家、心理学家、精神病学家和认知科学家拥有一个词汇和共同的结构参照,以讨论和探索我们如何思考。

这些纯粹人为的结构不应该被解释为对系统管理员应该如何工作的限制。它们仅仅是帮助我们了解自己和思考方式的工具。一点自省可以帮助我们在工作中做得更好。

寻找你的中心

作为一名瑜伽学生,当我开始(几乎)每天的练习时,无论是在我自己的小瑜伽室还是在课堂上,我做的第一件事就是找到我的中心。现在是时候做自己了,用我的思维去探索我存在的物质层面,同时敞开心扉去体验仅仅存在的体验。

根据我自己的经验,我认为这是探索我们作为系统管理员的思维和推理的一个极好的方法。这并不是说我使用这种技术来解决问题,而是探索我自己解决问题的方法。

很多时候,在解决了一个问题之后,尤其是一个新的或者特别困难的问题,我会花一些时间去思考这个问题。我从症状、我的思考过程以及这些症状把我引向何处开始。我会花时间思考是什么最终引导我找到了解决方案,哪些事情我本可以做得更好,哪些新东西我想学。

它给了我一个机会,作为一个个人,在我以前工作的地方进行我们过去称之为“经验教训”的会议。这是一个机会,看看我们作为一个团队做对了什么,我们可以做得更好。现在最棒也是最难的是,我没有其他人来帮助我理解我本可以做得更好。这使得尽可能多地做这件事对我来说更加重要。

没有必要为了做到这一点而练习瑜伽。只要留出一些时间,找一个你不会被打扰的空旷地方,闭上眼睛沉思。在回顾事件之前,深呼吸,放松,让你的心平静下来。从头开始,思考整个事件。回顾事件的完整顺序和您用来最终找到解决方案的步骤。你需要知道和学习的事情会让你明白的。我发现这种形式的自我评估非常有效。

我也喜欢在开始处理一个新问题之前花点时间让自己集中注意力。这让我对各种可能性敞开了心扉。首先是代表问题可能原因的可能性。然后是代表我必须找到问题原因的方法和工具的可能性。最后,这些可能性代表了解决问题的方法。

多样性的含义

作为个体,我们的推理过程是复杂多样的。我们每个人都有不同的经历,这些经历构成了我们在推理中使用的结构和过程的基础。由于这些差异,我们中没有两个人会以相同的方式解决问题。

我不可能要求比我为 Opensource.com 创建的命令行挑战更好的说明这个特殊的原则,“没有应该”,我们在第 4 “转换数据流”中探索过思想、创造力和解决问题的方法的多样性令人难以置信,其范围令人震惊,其影响令人振奋。

挑战的结果说明了将一些小的、普通的实用程序组合起来产生正确结果的各种方法。在使用 Linux 时,这是需要记住的重要一点。有多少系统管理员、开发人员、开发人员等等,就有多少解决问题的正确方法。重要的是结果。

测量狂热

技术由两类人主导:一类人了解他们不管理的东西,另一类人管理他们不了解的东西。

—archibald putt, Linux Journal

我仍然听到 PHB 谈论 KLOCs、 7 击键、错误计数和其他类型的数字测量,这些数字测量被设计为低级别 PHB 用来向高级 PHB 报告结果,并且旨在表明正在取得进展。这些量化开发人员和系统管理员执行的工作的质量和数量的尝试完全没有抓住要点,并且可能导致开发人员的代码膨胀。如果你付我钱,让我每天写 X 行代码,我会写,不管它们是否是执行程序设计的任务所需要的。

对于系统管理员来说,这种尝试性的量化只能解决症状,而不能解决问题的根本原因。在特定时间段内获取和解决的票据数量是一种极其无知的绩效衡量方式。生产率测量概念基于时间和运动研究 8 实践,由泰勒、吉尔布雷斯和吉尔布雷斯在 19 世纪中期开发,并在当时的实业家管理中流行开来。维基百科有一篇关于时间和运动的简短但有趣的文章。 9

像这样使用超过 150 年历史的度量策略,贬低了系统管理员和开发人员所做的工作。他们关注错误的事情。他们以一种 PHB 可以理解的方式创造了应该包含我们的界限,PHB 不知道如何与我们打交道。

在这本书里,我们已经看到了懒惰的系统管理员。衡量一个有思想的头脑的生产力是不可能的。我希望这样的事情永远不会成为可能。然而,系统管理员思维的结果可以根据从该思考得出的结论所实现的生产率来间接测量。例如,每一个新的脚本——在我们深思熟虑的状态下构思的脚本,每一种安装和管理计算机及其操作系统的新的和改进的方法,以及每一种为寻找更好的方法而分析的故障模式,都提高了整体生产力,并减少了将来系统管理员和其他人干预的需要。这使得系统管理员有更多的时间进行无拘无束的思考。

在本书中,我一直以贬损的方式谈论 PHB。多亏了《呆伯特》连环漫画,PHB 现在成了真正糟糕的经理的同义词。我遇到过一些这样的经理,他们对优秀的团队和有创造力的成功的系统管理员来说是毁灭性的。我见过许多优秀的系统管理员因为有毒的管理者而离开组织。

好经理

尽管现实世界中有许多博士,但也有许多真正优秀的经理,我很幸运在我的职业生涯中有一些这样的人。优秀的员工——那些了解技术并知道如何管理直接与技术打交道的员工的员工——通常是少数从普通员工中脱颖而出的人。他们在以前的工作中是 CEs,开发人员,系统管理员,测试人员,甚至是黑客。

这些令人惊叹的经理知道,与我们这些正在工作的人打交道的最佳方式是问一些有见识的问题,以了解情况,然后让我们解决任何问题,同时尽可能让更高级别的经理和 PHB 了解情况。他们明白,没有持续的微观管理,我们工作得最好,做必要事情的自由是这种管理者的标志。

一起工作

现在我已经让你相信了“没有应该”并且用你自己的方式去做每一件事是很好的,这给我们留下了一个问题。我们这些完全不同的系统管理员如何在团队中一起工作?

团队?!什么团队?我们不需要讨厌的团队。

事实上,我们确实需要团队,我们确实需要在这些团队中一起工作。我们有一些优秀的团队例子,以及他们在几乎所有开源软件中的成果。

由来自世界各地的开发人员组成的团队一起工作,生产我们都使用和欣赏的开源软件。一些团队成员可能有报酬,但大多数没有,他们自愿付出时间和精力来编写代码。其他志愿者扮演系统管理员的角色,帮助开发系统保持正常运行。其他人测试生成的代码,还有一些人开发文档。

团队在地理上是多样化的,因为组成团队的个人在工作方式上是独一无二的。这种人才的地理分散给团队工作带来了一些有趣而重要的限制。

2012 年,Ryan Tomayko 根据他作为 GitHub 早期员工的经历写了一篇博文。这篇文章的标题是,“你的团队应该像一个开源项目一样工作”,这篇文章的前提是,地理上兼容并能在同一办公空间工作的团队,如果地理上对广泛分散的开源团队施加的通信和人际互动的约束类型相同,将会工作得更好。根据 Tomayko 的说法,“……为符合开源约束而设计的过程会产生一个运行良好的项目,吸引人们的注意,并且似乎会自我延续,而同样的项目结构更传统地需要更多的手动协调和权威激励……[它]创造了在没有协调的情况下合作的可能性。…"

我强烈建议你看一下 Tomayko 的帖子。它有一些有趣的事情要思考。他列举了一些限制因素,并表示办公室作为一个工作空间正在减少,将主要用作专门为移动工作者设计的空间,提供在像你最喜欢的咖啡馆这样的地方工作所需的相同服务。

作为系统管理员,我们经常被要求跨时区工作,并与我们只通过电子交互认识的团队成员一起工作。我自己的经验与 Tomayko 的一致,并表明这比每个人都是本地人并应用传统的所谓管理更有效。显然,大多数开放源码项目都是这种成功的极好例子。

筒仓城

我在一个组织工作了大约一年,这是传统团队方法走向极端失败的一个极好的例子。最糟糕的是,这也是一个可怕的日常通勤。

在这个组织中,管理层创造了非常窄、非常高的筒仓来容纳一切。有多个团队,Unix 团队、应用团队、网络团队、硬件团队、DNS 团队、机架团队、电缆团队、电源团队——几乎是你能想到的任何团队。

手术过程令人难以置信。例如,我的一个项目是在几台服务器上安装 Linux,这些服务器将用于组织网站的各个方面。第一步是订购服务器,但这一请求需要几周时间才能通过行政官僚程序。

服务器交付后,Unix 团队会将它们安装在安装实验室中,并安装操作系统。我们很好地完成了那部分。但是首先我们必须请求一个 IP 地址。在我们交付服务器之前,我们不能这样做,因为请求 IP 地址需要服务器的序列号和网卡的 MAC 地址。

这里的问题是,每个孤岛必须与其他所有孤岛签订服务级别协议(SLA ),并且 SAL 定义的响应时间至少为两周。并且每个筒仓花费的响应时间不低于 SLA 中指定的时间。

但是,在服务器机房中分配了机架位置之前,我们无法获得 IP 地址,因为 IP 地址是按机架和机架中的位置分配的。因此,我们必须发送一个机架分配请求,并等待两周才能提供。

因此,获取 IP 地址后的下一步是将其发送到处理 DHCP 配置的思洛存储器。然后,在获得 IP 地址至少两周后,我们必须等待 DHCP 设置完成。

只有在 DHCP 服务器上配置了服务器的网络配置数据后,才能发送将服务器从我们的机架移动到服务器机房的请求。又是两周的转机。

在移动请求获得批准后,我们才可以发送将计算机安装到机架上的请求。安装完成后,我们可以发送请求,为服务器连接网络和电源。只有当这一步完成后,我们才能向服务器发送加电请求。

除了安装操作系统,我们碰不到服务器。我们甚至不被允许进入服务器机房。永远不会。

不用说,安装每台服务器、让它运行并为生产团队接管做好准备需要几个月的时间。我还可以举出更多的例子来说明这个地方是一场功能性灾难,但我想你已经明白了。他们所谓的团队只是政治领地,被密不透风的筒仓保护着。

简单的方法

我和布鲁斯在思科的经历要好得多。在下一章你会学到更多关于布鲁斯的知识。他和我设计了一个非常棒的系统。

服务器通常在我们订购后不到一周就送到了。布鲁斯和我会在早上将其中的四个装上机架,分配 IP 地址,配置它们所连接的交换机,将它们添加到 DNS 和 DHCP 服务器上,并在其上安装 Linux。下午我们会再做四个。

不同之处在于所有的团队都在一起工作。处理网络寻址和配置的团队已经编写了脚本(自动化所有工作),并给予我们访问权限,以便我们可以使用这些脚本来完成 DHCP 和 DNS 的所有网络配置。我编写的脚本用于执行 Linux 安装。

BRuce 和我完全负责机架和其中与启动和运行服务器有关的一切。他和我作为一个团队工作得很好,因为我们每天都花一点时间来确定需要做什么,并决定我们中的哪些人将承担我们需要完成的各种任务。这不是一次会议;这一点也不正式。没有人根据某种武断的标准给我们分配任务。布鲁斯和我都是个性很强的系统管理员。当我们独自做自己的工作时,我们做得又快又轻松。我们只是在我们之间分配任务,因为我们都觉得合适,然后开始做我们的事情。很多时候,我们需要本地和远程的其他系统管理员的帮助,我们只是继续进行我们的小晨会讨论来决定一天的工作,根据我们的人数,在三到四个人之间分配。我们与其他团队合作得很好,通常会在服务器交付到实验室的当天晚些时候将它们移交给开发人员或测试人员。

管理层在这一方法上给予了合作。我们只是被告知,一个新项目正在启动,或者需要对现有项目进行更改。我们将与项目负责人协商,确定他们的需求和目标。正如你将在下一章中看到的,有时 BRuce 和我不得不努力获取我们需要的信息,但是一旦我们得到了信息,我们就很少或根本没有管理层的干预或监督来处理剩下的事情。

思想

我们中那些在 Unix 和 Linux 系统管理方面取得成功的人天生就好奇和善于思考。我们抓住一切机会扩大我们的知识基础。

出于好奇和“因为它就在那里”,我们喜欢尝试新知识、新硬件和新软件当电脑坏掉时,我们享受着向我们敞开的机会。每一个问题都是学习的新可能。我们喜欢参加技术会议,因为可以接触到其他系统管理员,也因为我们可以从预定的演示中收集到大量的新信息。

僵化的逻辑和规则没有给我们系统管理员足够的灵活性来有效地执行我们的工作。我们并不特别关心事情“应该”怎么做。系统管理员不容易被别人试图约束我们的“应该”所限制。我们使用灵活的逻辑和批判性思维,并产生出色的结果。我们用独立的、批判性的思维和综合的推理创造自己的做事方式,这使我们在做的时候能够学到更多。

我们系统管理员是个性很强的人——为了做好自己的工作,尤其是以“正确”的方式做事,我们需要这样。这不是关于我们“应该”如何执行我们需要做的任务,而是关于使用最佳实践并确保最终结果符合那些实践。

我们不只是跳出框框思考。我们是破坏别人试图让我们在里面工作的盒子的人。对我们来说,没有“应该”

二十四、指导年轻的系统管理员

这些年来,我参加了许多培训课程,大多数课程都非常有用,帮助我了解了更多关于 Unix 和 Linux 以及许多其他主题的知识。但是培训——尽管有用和重要——不能涵盖履行系统管理员职责的许多基本方面。

我参加的所有课程都是几天,通常是四五天。关于命令、过程、文件系统、进程以及本书中提到的许多东西,信息太多了,无法涵盖您需要了解的一切。并不是所有的东西都可以在教室里教授。有些事情只能在现实世界的环境中由一个好的导师来教授,通常是在你处于极端压力下解决一个关键问题的时候。

没有什么比让 PHB 或一个或多个奴才在你身后监视并批评你的每一个举动和决定更好的了。它发生了。这些提供每小时进度报告的压力,回答诸如“什么时候能修好”等愚蠢问题的压力,抵制 PHB 试图在一个人的任务上增加三个人的压力,等等,不仅浪费了我们的时间,还打断了我们的思路,降低了我们的整体效率。大多数时候我们知道该做什么和怎么做,我们只是需要一个能让我们相对平静地工作的环境。

一个好的导师会让你在这些情况下做实际的工作,这样你就可以有一个有价值的学习经历,同时避免陷入困境,在不受干扰的情况下承受压力。一个伟大的导师也能够在任何情况下创造学习的机会,不管情况有多危急。

刚开始的时候,我是一个年轻天真的系统管理员。我很幸运,因为我做过几份不同的工作,其他经验丰富的系统管理员愿意指导我,鼓励我。当我问在他们看来答案显而易见的问题时,他们没有一个人嘲笑我。这些耐心的系统管理员从来没有告诉我 RTFM。

雇佣合适的人

指导合适的人从来都不简单也不容易;指导错误的人是不可能的。考虑到这一点,让我们来看看如何雇用合适的人。

作为一名系统管理员,特别是如果你是一名高级系统管理员,你的部分工作应该是帮助你的团队雇佣合适的人。如果你的 PHB 让你在招聘过程中孤立无援,你应该尽你所能改变这种情况。幸运的是,在我的大部分工作生涯中,这很少成为问题。聪明的经理会让整个团队参与招聘新成员。

我经历过的最好、最愉快的面试之一是我申请思科公司的测试员和兼职实验室系统管理员的工作。我和经理呆了一会儿,然后部门里的其他人跟了我大约五个小时。他们三三两两地来了,问了我各种各样的问题。每个小组给我一些假设的情况来解决,他们问我一些技术性的问题,并且测试我的耐心。实际上,我在那次面试中很开心,因为每个面试我的人都是他们被雇佣来做的工作的合适人选。我确实得到了那份工作。我不是什么都知道。我确实告诉了面试官那个事实,当它是真实的时候。

有许多方法可以用来雇佣合适的人,但没有万无一失的方法。然而,我发现,合适的面试官和合适的问题对实现这一点大有帮助。

正如我之前提到的,很多面试系统管理员类职位的人都没有准备好,因为他们不知道如何解决问题。有时候,直到你雇佣了这个人,你才能知道这一点,而且在那个时候很难“解雇”他们。我工作过的一个地方采用了动手测试。我们的测试很简单。我们设置了一个 Linux 主机,其中有三个具体但相当简单的问题,申请人必须在指定的时间内解决。

虽然这个测试是关于发现和解决问题,我们也看了申请人完成任务的方式。那些惊慌失措或或多或少随机前进、漫无目的地挣扎的人很快就被我们排除在外了。即使他们没有解决所有的问题,那些带着某种目的感,带着成熟的问题解决算法前进的人,是我们认为最有可能在我们现有的工作中取得成功的人。我们可以轻松地教授技术,但我们不容易教授解决问题的技巧和禅。

测试可能存在法律问题,但是,如果测试真正代表了申请人将要从事的工作类型,并且所有申请人都需要参加测试,那么(请咨询您的律师)应该可以使用测试。

指导

一个人如何指导一个年轻的系统管理员?银河系中有多少颗恒星?每个系统管理员都有自己的指导方式,每个年轻的系统管理员都需要不同的知识和不同的方法。

当我有一个优秀的老师时,学习起来更容易,但我发现,当我喜欢一门学科并对它感兴趣时,我的老师的质量几乎没有影响。最好的导师允许甚至鼓励我追随自己的好奇心。即使我没有完成目标,他们也会在我尝试的时候奖励我。

然而,一个真正糟糕的老师不仅可以摧毁学习的欲望,还可以摧毁学习的能力。一个非技术性的例子是我的高中英语文学老师。很明显,她真的很喜欢我们应该学习的书籍、故事、诗歌和其他文学作品。不幸是,她不知道如何教学,也不知道如何将对这门学科的热爱传递给我们这些学生。除了别的以外,我们还学习了莎士比亚,我简直烦透了。

第二年夏天,因为我参加了一些学校的戏剧演出,戏剧老师喜欢我的作品,她把我推荐给爱尔兰山剧院的一名招聘人员,这是一个在密歇根州南部演出莎士比亚剧目的夏季剧团。哇哦!莎士比亚的整个夏天?是的,我很喜欢。那年夏天,作为一名学徒,我对莎士比亚的了解比我在课堂环境中学到的更多,就像我在文学课上遇到的老师一样。

那个夏天,我有几个很好的导师。他们为我们所有人举办了培训班。他们帮助我们学习材料的意义以及表演的技巧。对我来说,理解是最有帮助的。我认为理解是导师可以帮助的最重要的事情之一。死记硬背不是关键——理解、批判性思维和解决问题的技能是我的技术导师赋予我的最重要的东西。

导师布鲁斯

我很幸运有许多非常优秀和耐心的导师,他们允许我失败,以便我可以学习。特别是有一个人,布鲁斯,因为他喜欢在他的电子邮件上签名,所以他确保我接受了必要的培训,但他也允许我很快地运用这些培训。他马上给我分配了困难的任务,这些任务迫使我运用新获得的知识,突破自我舒适和自我限制的界限。

布鲁斯和我在两家不同的公司共事多年,这两家公司都需要深厚的 Unix/Linux 知识和技能。我们合作得很好,因为我们都非常擅长我们所做的事情。他理解我开始时的技能水平不如他,但他尊重我所拥有的技能,并给我很多机会去使用这些技能和学习新的技能。

从很多方面来说,BRuce 都是典型的坏脾气系统管理员——这是有充分理由的。我这么说的意思是,当与技术含量较低的人打交道时,如营销人员和 PHB,关于他们想在我们负责的实验室中做的事情,他的第一反应几乎总是一个干脆、明确的“不”。这总是因为项目,无论是什么,都会在实验室中引起问题,因为它们最初是构想出来的,因为它们都没有经过深思熟虑,没有什么可以做,什么不可以做的概念。然后,布鲁斯问了提出请求的人一系列问题,最终让我们知道他们真正想做什么。似乎提出这些请求的大多数人也在尝试设计支持这些项目的基础设施,而这是我们的专业领域,不是他们的。他们也没有很好地考虑他们的项目会如何影响其他使用实验室测试他们项目的人。

布鲁斯并不像有些人想的那样是个混蛋。他在做他的工作,那就是确保实验室对每个使用它的人来说都是完全正常的。我们收到的大多数初始请求都有明显的缺陷。我们有责任确保这些缺陷不会影响实验室的其他部分。BRuce 非常直率,因为我们没有时间处理其他人引起的问题,当时只有我们两个人在实验室中处理超过 15 排 24 个机架,所有这些机架都装满了运行测试的设备,如果有人的实验失控,实验室网络将不得不重新启动。

在那种环境中,不能容忍任何错误。布鲁斯和我只是在执行旨在保护所有用户的实验室准则。作为我的导师,这也是布鲁斯试图帮助我理解的事情——这是一个多数人的利益压倒少数人的利益的时候。实验室的运行方式必须防止一些用户干扰其他用户的工作。

解决问题的艺术

我的导师帮助我做的最好的事情之一是制定一个明确的过程,我可以用它来解决几乎任何类型的问题。在我看来,它与科学方法密切相关。

在我为这本书做研究的过程中,我发现了一篇名为“科学方法是如何工作的”的短文,它用一张图表描述了科学方法,这张图表与我为解决问题的五个步骤创建的图表非常相似。因此,作为一名导师,我将此传递给你们,这是我对所有年轻系统管理员的贡献。我希望你和我一样觉得它很有用。

解决任何类型的问题都是艺术、科学,有些人会说,或许还有一点魔法。解决技术问题,如计算机出现的问题,也需要大量的专业知识。

解决任何性质问题的任何方法——包括 Linux 问题——必须不仅仅包括症状列表以及修复或规避导致症状的问题的必要步骤。这种所谓的“症状修复”方法在理论上对管理者来说很好,但在实践中却很糟糕。解决问题的最好方法是拥有大量的相关知识和强大的方法论。

解决问题的五个步骤

问题解决过程涉及五个基本步骤,如图 24-1 所示。这种算法与脚注 1 中提到的科学方法非常相似,但专门用于解决技术问题。

img/462716_1_En_24_Fig1_HTML.png

图 24-1

解决问题的五个步骤与科学方法非常相似

当您对某个问题进行故障诊断时,您可能已经遵循了这些步骤,但您甚至没有意识到这一点。这些步骤是通用的,适用于解决几乎任何类型的问题,而不仅仅是计算机或 Linux 的问题。多年来,我在各种问题中使用这些步骤,却没有意识到这一点。为我整理它们让我在解决问题时更加有效,因为当我陷入困境时,我可以回顾我采取的步骤,验证我在过程中的位置,并在任何适当的步骤重新开始。

在过去,你可能听过几个用于解决问题的其他术语。这个过程的前三个步骤也称为问题确定,即找到问题的根本原因。最后两步是问题解决,实际上就是修复问题。

接下来的部分将更详细地介绍这五个步骤。

知识

了解你试图解决问题的主题是第一步。我看到的所有关于科学方法的文章似乎都把这作为一个先决条件。然而,知识的获取是一个持续的过程,由好奇心驱动,并通过使用科学方法探索和通过实验扩展现有知识而获得的知识来增强。这是我在本书中使用术语“实验”而不是“实验室项目”的原因之一。

您必须至少了解 Linux,甚至更多,您必须了解能够与 Linux 交互并影响 Linux 的其他因素,例如硬件、网络,甚至环境因素,例如温度、湿度和 Linux 系统运行的电气环境如何影响它。

通过阅读关于 Linux 和其他主题的书籍和网站可以获得知识。你可以参加课程、研讨会和会议。你也可以在一个网络环境中,通过与其他有知识的人交流,设置一些 Linux 计算机。当你解决一个问题并发现一个特定类型问题的新原因时,你就获得了知识。当试图解决问题导致暂时失败时,您也可以找到新的知识。

课堂在为我们提供新知识方面也很有价值。我个人的偏好是玩——呃,尝试 Linux 或者某个特定的东西,比如网络、名称服务、DHCP、Chrony 等等,然后上一两堂课来帮助我将学到的知识内化。

记住,“没有知识,抵抗是徒劳的,”套用博格人的话。知识就是力量。

观察

解决问题的第二步是观察问题的症状。记下所有的问题症状非常重要。观察什么在正常工作也很重要。现在不是试图解决问题的时候;只是观察。

观察的另一个重要部分是问自己关于你看到什么和没有看到什么的问题。除了你需要问的特定问题之外,还有一些一般性的问题。

  • 这个问题是由硬件、Linux、应用软件引起的,还是由于缺乏用户知识或培训引起的?

  • 这个问题和我见过的其他问题类似吗?

  • 是否有错误信息?

  • 是否有与该问题相关的日志条目?

  • 错误发生前,计算机上发生了什么?

  • 如果错误没有发生,我期望会发生什么?

  • 最近系统硬件或软件有什么变化吗?

当你努力回答这些问题的时候,其他的问题也会显露出来。这里要记住的重要事情不是具体的问题,而是尽可能多地收集信息。这增加了您对这个特定问题实例的了解,有助于找到解决方案。

当你收集数据时,永远不要假设从别人那里获得的信息是正确的。自己观察一切。如果你和一个在远处的人一起工作,这可能是一个大问题。仔细询问至关重要,当试图确认提供给您的信息时,允许远程访问相关系统的工具非常有用。当在远处询问一个人时,不要问引导性的问题;他们会尽力帮助你,用他们认为你想听的话来回答。

在其他时候,你得到的答案将取决于这个人对 Linux 和一般计算机知识的多少。当一个人知道——或者认为他们知道——计算机时,你得到的答案可能包含难以反驳的假设。而不是问。“你检查了吗……”最好让对方实际执行检查物品所需的任务。与其告诉用户他们应该看到什么,不如让用户向你解释或描述他们看到了什么。同样,远程访问机器可以让您确认提供给您的信息。

最好的问题解决者是那些从不认为任何事情是理所当然的人。他们从不认为他们拥有的信息是 100%准确或完整的。当你所掌握的信息似乎与症状本身相矛盾时,从头开始,就好像你什么信息都没有一样。

在我从事的几乎所有计算机行业的工作中,我们总是试图互相帮助,我在 IBM 的时候也是如此。我一直非常擅长解决问题,有时当另一个客户很难找到问题的根源时,我会出现在客户面前。我要做的第一件事是评估形势。我会问基层行政长官,他们至今已采取了甚么措施去找出问题所在。之后我会从头开始。我一直想亲眼看看结果。很多时候这样做是有回报的,因为我会观察到一些其他人没有注意到的东西。在一次非常奇怪的事件中,我坐在一台大型电脑上修理它。

坐下来工作

这发生在大约 1976 年,当时我还是俄亥俄州利马的一名 IBM CE。我们中的两个人正在安装一台 IBM System 3,它比 IBM 大型机小,比如 360 或 370,但仍然足够大,需要一个自己的房间、高压电源和大量的空气冷却。

当我们遇到问题时,我们已经组装了主 CPU,并开始连接 IBM 1403 行式打印机控制器。打印机控制器安装在 CPU 左侧稍低于桌面高度的位置。那个漂亮的大工作台面的高度正好适合坐着。

我们刚刚把打印机控制器用螺栓固定在 CPU 的框架上,正在做安装说明中的众多检查之一。我们将欧姆表的引线连接在 CPU 的框架和打印机控制器电源的特定端子之间。结果应该是开路,即无穷大电阻,这将表明电源的热引线没有短接到框架。在这种情况下,有一个短-零电阻-这是不好的。不会像你在电视上看到的那样出现壮观的噪音和焰火,但这会是一个问题,因为它会阻止计算机启动。最好在它还在组装的时候就抓住它,而不是以后。

经过一个小时的努力寻找问题,我们无法这样做。我们致电佛罗里达州 Boca Raton 的 System/3 支持中心,并在他们的指导下完成了几个不成功的进一步问题确定步骤。

有点沮丧,我坐在打印机控制单元上。我用眼角的余光看到欧姆表上的指针摆动,表示开路。我向另一位 CE 和 Boca Raton 的 Vern 提到了这一点,后来当我作为课程开发代表(CSR)去那里工作几年时,他成了我的导师之一。

我们从控制器上取下了我曾栖息过的顶盖,幸运的是,我们发现将顶盖固定在打印机控制器框架上的一个螺栓松了,掉进了电源中,导致了短路。当我坐在控制器的顶部时,框架移动了足够的距离,导致螺栓不再接触,从而产生短路。从电源上取下那个松动的螺栓就解决了这个问题。

当时负责 System/3 支持的 Vern 对说明做了一些修改,以覆盖这个问题,以防再次发生。他还与制造人员合作确保这种情况不会再次发生,进行检查以确保螺栓在制造过程中正确拧紧。

要记住的事情是真正观察系统的所有部分发生了什么。注意一切,不要忽略丝毫线索。有时,观察 top 或用于监控内核或网络内部功能的其他实用程序可以提供一些线索,让我们朝着正确的方向开始。

有时需要一点运气,比如坐在打印机控制单元上。

论证

运用推理技巧从你对症状的观察和你的知识中获取信息,以确定问题的可能原因。我们在第二十三章中详细讨论了不同类型的推理。通过你对问题的观察、你的知识和你过去的经验进行推理的过程是艺术和科学相结合产生灵感、直觉或其他一些神秘的心理过程,这些过程提供了对问题根源的一些洞察力。

在某些情况下,这是一个相当容易的过程。您可以看到一个错误代码,并从可用的资源中查找其含义。或者你观察到一个熟悉的症状,你知道该采取什么措施来解决它。然后,您可以应用您通过阅读 Linux、本书和 Linux 提供的文档所获得的大量知识来推断问题的原因。

在其他情况下,这可能是问题确定过程中非常困难和漫长的一部分。这些是最困难的案件类型。也许是你从未见过的症状,或者是你用过的任何方法都无法解决的问题。正是这些困难的问题需要更多的工作,尤其是更多的推理。

记住症状不是问题是有帮助的。问题导致了症状。你想解决真正的问题,而不仅仅是症状。

行动

现在是执行适当的修复操作的时候了。这通常是简单的部分。困难的部分是之前发生的事情——弄清楚该做什么。知道问题的原因后,就很容易确定要采取的正确修复措施。

您采取的具体措施将取决于问题的原因。请记住,我们正在修复根本原因,而不仅仅是试图摆脱或掩盖症状。

一次只做一个改变。如果可以采取多种措施来纠正问题的原因,那么只进行一种改变或采取最有可能解决根本原因的一种措施。选择最有可能解决问题的纠正措施是您在这里要做的事情。无论是你自己的经验告诉你该采取什么行动,还是其他人的经验,从最高到最低优先顺序,一次一个行动。每次行动后测试结果。

试验

在采取一些公开的修复措施后,应该对修复进行测试。这通常意味着首先执行失败的任务,但也可能是说明问题的一个简单的命令。

我们在第十一章讨论了测试以及为 shell 脚本编写代码,这里的过程是一样的。我们做一个单一的改变,采取一个潜在的纠正措施,然后测试该措施的结果。这是我们可以确定哪个纠正措施解决了问题的唯一方法。如果我们要做几个纠正措施,然后测试一次,没有办法知道哪个措施负责修复问题。如果我们想在找到解决方案后退回那些无效的改变,这一点尤其重要。

如果修复操作不成功,您应该重新开始该过程。如果你可以采取额外的纠正措施,回到那个步骤,继续这样做,直到你用尽了所有的可能性,或者确信你已经走上了错误的道路。

测试时一定要检查原来观察到的症状。它们可能由于您采取的行动而发生了变化,您需要意识到这一点,以便在流程的下一次迭代中做出明智的决策。即使问题没有得到解决,症状的改变对于决定如何继续进行也是非常有价值的。

例子

我自己解决问题的一个例子发生在我作为一名兼职 Linux 系统管理员的时候。它相当简单,但对于说明我所概述的步骤的流程非常有用。

我收到了一封来自我们一位测试人员的电子邮件,指出他在测试中安装的一个应用崩溃了。它给出了错误消息,表明交换空间不足。这是用户执行的初始观察并传送给我。

我的知识告诉我,用于测试这个应用的系统有 16GB 的 RAM 和 2GB 的交换空间。以前的经验(知识)告诉我,这些计算机中的交换空间几乎从未使用过,RAM 使用率通常远远低于这些机箱中 16GB RAM 的 25%。

在这一点上,我推断这个问题实际上不是交换空间的问题,因为这看起来不太可能。我仍然保留着这种可能性,尽管可能性很小。你会发现程序提供的许多错误信息可能会误导人,用户的观察甚至会更误导人。

我做了一些自己的观察。我登录到机器上,使用 free 命令作为查看内存和交换空间的工具。我可以观察到有很多空闲内存,交换空间使用率为零。我知道如果交换空间的使用实际上为零,那么很可能没有可用的交换空间被分配,并且自上次引导以来没有发生分页。

我还从以前的经验(知识)中推理出在那个错误信息中可能有真理的内核。也就是说,它很可能是出于某种资源或其他。其他主要消耗资源是 CPU 周期和磁盘空间。

这似乎不是 CPU 问题,所以我使用 df 命令观察了磁盘空间,这表明/var 文件系统已满。我推断文件系统已满是问题的原因。对/var 的一点探索表明,测试人员的软件确实位于那里,并且已经填满了文件系统。

所有系统都是以 1.5GB 的/var 文件系统启动的。策略是将应用安装在/opt 中,这是我们要测试的应用的设计安装位置,它被配置为占用所有剩余的磁盘空间,因此大小很容易达到 100GB 或更大,对于任何被测试的应用来说都绰绰有余。

我与测试人员讨论了这个问题,并被告知他确实在/var 中安装了应用。我告诉他从那里卸载新程序,并在/opt 中安装应用。在采取这个行动之后,我让他通过执行之前失败的操作来测试纠正行动。测试成功,问题得以解决。

循环

当你解决一个问题时,至少有必要重复一些步骤。例如,如果执行给定的纠正措施不能解决问题,您可能需要尝试另一种已知的措施来解决问题。图 24-1 显示你可能需要重复到任何先前的步骤才能继续。

可能有必要返回到观察步骤,收集有关问题的更多信息。我还发现,有时回到知识阶段,收集更多的基础知识是个好主意。后者包括阅读或重读手册,手册页,使用谷歌,任何必要的获取知识的方法,以继续越过我受阻的地方。

灵活一点,如果没有其他方法可以产生一些进步,不要犹豫退一步重新开始。

结束语

在这一章中,我们看到了一种解决问题的方法,这种方法适用于许多非技术性的东西,也适用于计算机硬件和软件。我们在这里讨论的是如何在一个算法的框架内使用特定的推理方法来解决问题。这种特殊组合的灵活性非常强大。

我不是告诉你你应该使用这种方法。然而,如果你全力以赴,分析你自己解决问题的方法,你很可能会发现它已经非常接近我在这里描述的算法了。作为一名导师,我建议你花时间分析你自己的方法。我想你会发现这是一次富有成效的时间利用,会很有启发性。

我也恳求你去指导别人。传递知识、技能和你自己的哲学。对于有经验的系统管理员来说,没有什么比这更重要的了。我们的技能是惊人的,我们并不是靠自己取得的。我们很了不起,因为那些指导我们的人,他们认为我们具备成为伟大的系统管理员的素质。我们有责任将这一点传递给年轻的系统管理员。

最后,我有一些了不起的导师,他们知道学习——真正的学习——需要什么,并允许我这样做。你们都给了我从失败中学习的机会。你帮我找到了问题所在,让我重回正轨。你们是我的英雄。敬你,李斯琦,布鲁斯,弗恩,丹,克里斯,希瑟,罗恩,唐,戴夫,厄尔和帕姆。对于你们这些默默无闻的导师,你们真棒!感谢您的支持和指导。

二十五、支持你最喜欢的开源项目

Linux 和我们在其上运行的大部分程序都是开源程序。许多较大的项目,如内核本身,是由专门为此目的而建立的基金会(如 Linux 基金会)和/或对此感兴趣的公司和其他组织直接支持的。

作为一名系统管理员,我写了很多脚本,我喜欢这样做,但我不是一名应用程序员。我也不想成为系统管理员,因为我喜欢系统管理员的工作,这允许不同类型的编程。因此,在很大程度上,为开源项目贡献代码对我来说不是一个好的选择。还有其他贡献的方式,我使用这些选项。本章将帮助你探索一些你可以做出贡献的方式。

项目选择

在我们讨论我们为开源项目做贡献的不同方式之前,我们将看看如何选择我们想要贡献的项目。这可能看起来令人生畏,因为许多项目需要这样或那样的支持。

我主要考虑的是我是否使用项目生产的软件或硬件。比如我每天都用 LibreOffice。我依赖它,并发现它对我的工作效率非常有用。所以我支持的一个项目是 LibreOffice。

我也支持高层次的组织,那些监督开源的某些方面的组织,比如 Linux 基金会,它支持并鼓励开源软件的使用,并且支持许多不同的开源社区。

选择一些对你有意义的项目并支持它。但是不要忘记“隐藏”的项目。其中一些项目对开源软件的成功至关重要,但是没有人知道它们,所以它们得不到支持。几年前的 Heartbleed 1 漏洞就是这类项目的一个例子。由于只有一个维护者和很少的预算,几乎在每个 Linux 发行版和其他操作系统中使用的 OpenSSL 软件都有一个缺陷——一个漏洞——危及每台使用 OpenSSL 的计算机。这个漏洞从 2012 年2开始就存在了,但直到 2014 年才被发现。

这个漏洞很快就被修复了,一些组织参与了这个项目,以确保开发人员可以继续工作,并帮助确保代码中不存在其他漏洞。

无论你选择什么,找一些你能支持的项目,并以对你有意义且有趣的方式去做。应该一直都很好玩吧!

密码

仅仅因为我选择不向开源项目贡献代码,并不意味着你也应该避免。我知道许多系统管理员都是优秀的程序员,他们可能对数百个开源项目中的一个或多个非常有帮助。没有编码员,一开始就不会有项目。

许多项目有很大的开发团队,而其他项目则很小,有时只有一个开发人员,他将开源项目作为第二份无报酬的工作。其他开发人员为更大的组织工作,这些组织付钱给他们为开源项目编码,通常是因为组织对那个项目有一些特别的兴趣。在大多数情况下,小项目的新开发人员是非常受欢迎的,但是大项目也非常欢迎新开发人员。

不同的项目使用不同的编码语言。许多项目是用 C 或 C++编写的,而其他项目则使用解释语言,如 Perl、PHP、Python、Ruby、bash 或其他 shell 脚本语言。

无论你的技能水平如何,你都可以找到一个有大量任务需要你去完成的项目。

试验

代码写好之后,需要有人来测试。测试和编写代码一样重要。我们在第十一章“尽早测试,经常测试”中详细讨论了测试,由于它的重要性,我们为它单独指定了一章。

一些项目需要专门的测试人员,他们在开发人员完成代码后立即获取代码,并通过一系列正式的测试来运行代码。这和我在思科工作时的一半职责非常相似。这种类型的测试需要写一个正式的测试计划,然后系统地完成这个计划。失败的测试被报告给开发人员来修复。

您还可以下载和测试许多常见和流行的软件包的测试版。这些测试版中的大部分都是公开发布的,目的很明确,就是为了引出错误报告——如果你愿意的话,还有修正。这种类型的测试通常不太严格。项目领导使产品可以在现实世界中使用,并可能提供一些指导,例如测试特定的功能。您可以像使用最终版本一样使用该产品,但是当您发现一个 bug 时,您可以将其报告给项目进行修复。

提交错误报告

提交 bug 报告是支持开源项目的一种非常重要的方式。我已经做了一些,这很容易做到。

大多数项目都有定义良好并记录在案的报告 bug 的方法。许多项目使用 Bugzilla 报告 bug,有些项目使用其他工具,包括一些自主开发的工具,甚至只是给开发人员发电子邮件。如何提交 bug 的细节通常可以通过项目主页上的链接找到。

在这种情况下,我们不像上一节那样讨论 beta 测试。这里我们使用的最终发布代码已经通过了所有的 alpha 和 beta 测试。这是真实世界,“生产中的测试”类型的测试;因为生产是最好的,也是最后的检验。

当我们在生产产品中发现一个 bug 时,即使这不是任何类型的官方测试程序的一部分,我们也有责任向项目提交一份 bug 报告。几乎每个项目都有报告 bug 的方法。

向开发人员请求更多信息是很常见的,这样他们就可以缩小问题的来源。这些请求很重要,快速响应非常有帮助。大多数情况下,这些请求是为了澄清错误发生的条件,例如操作系统版本,或者故障发生时可用的空闲内存、交换空间和磁盘空间的数量。有一次,一个内核开发人员要求我安装一个带有检查点的内核版本,这些检查点旨在帮助开发人员定位存在问题的代码部分。

报告生产软件中的错误有助于使它对所有用户都更好,而不仅仅是我们自己。

文件

不管我们是否编码,文档是我们许多人都可以参与的领域。虽然有很多关于人们不读文档(RTFM)的笑话,但是文档非常重要。

有不同类型的文件。从命令行实用程序和工具的手册页,到大型应用(如 LibreOffice)的完整在线手册。LibreOffice 有一系列精心编写的手册,可以下载为 PDF 文件,也可以通过浏览器在线使用。

LibreOffice 还有一个非常好的帮助工具,包括目录、索引和搜索工具。图 25-1 显示了 LibreOffice 帮助工具的第一页。它有明确的使用说明和不同的查找信息的方法。这是我见过的最好的帮助设施之一。

对于我们这些喜欢写作的人来说,创建和维护文档是一种很好的贡献方式。

img/462716_1_En_25_Fig1_HTML.jpg

图 25-1

LibreOffice 帮助工具的主页提供了多个选项来查找有关当前应用的信息。大卫两个,抄送。

帮助

帮助他人是支持开源软件的另一个好方法。这种类型的参与提供了许多不同的选择。

一种选择是参加本地聚会,在那里开源爱好者与不熟悉开源的人讨论开源的优势。在许多这样的聚会中,有时被称为安装节,有经验的用户帮助新手安装 Linux 并从基础开始。另一个选择是简单地向你的朋友和家人介绍 Linux,并帮助他们入门。一些 Linux 用户喜欢逛各种论坛和 IRC 聊天室,通过回答问题来帮助人们。

我有时会这样做,但这不是我最擅长的。

我喜欢教书。根据这些年来我收到的课程评估表,我很擅长。自从 1978 年我开始在 IBM 担任课程开发代表以来,我已经教授了许多不同的硬件和软件课程。

在过去的 15 年里,我编写了关于 Linux 的课程,并为我工作过的各种组织教授这些课程。后来,当我创办了自己的 Linux 咨询公司(几年前我关闭了它)时,我写了三个为期几天的课程,涵盖了从入门到高级系统管理的所有内容。

如果你有这样的倾向,并且你有一些做演讲的技巧,教书可能很适合你。会议时间可能从一小时到一周不等。在我的两个雇主那里,我参加了长达一小时的午餐和学习会议,这些会议只是对 Linux 和其他开源软件各个部分的概述。在其他地方,我参加了一两天的课程,旨在向 Windows 管理员介绍一些基本的 Linux 命令和文件系统之类的东西。

这是我做出贡献的主要方式之一——传播我的知识。我还设法加入了一些关于我的 Linux 哲学的指导和信息。无论你教别人做什么,都是在帮助他们学习 Linux 和开源软件。

我也喜欢写作。这本书只是我的写作项目之一,其他的是我经常为 Opensource.com3和我自己的网站写的文章,尤其是 Linux DataBook 4 网站。DataBook 网站是我尝试记录我学到的很难找到的东西。

DataBook web 站点起源于我在 IBM 工作的时候,是一个关于 OS/2 的信息数据库。该数据库的设计允许我和其他 OS/2 支持人员快速找到关于 OS/2 的信息。我还用它来确保一旦我发现了如何做某事,或者发现了一个特别难以捉摸的信息,我就不必再花时间去获取它。它对我来说基本上是一种记忆辅助工具。许多关于 OS/2 的信息也出现在硬拷贝书《OS/2 Warp 内部的的第六章“文件系统”和第二十二章“故障排除”中。 5 “在我离开 IBM 之后,这些信息成为了 OS/2 的 DataBook 的基础,它和它的前身一样,是我在我的独立咨询公司的记忆工具。

在 OS/2 被 IBM 放弃后,我开始写两本新书,Linux 管理员数据手册Linux 用户数据手册。这两本新书是分别为系统管理员和用户收集的关于 Linux,尤其是 Fedora Linux 的资料。它们包含了我在使用 Linux 的这些年中发现的信息,我需要为自己维护这些信息——再次作为记忆辅助。我也想让每个人都能得到这些信息,所以我把它们都放在了我的网站上。

我为 Opensource.com 写了许多文章,这些文章大多深入一些重要的主题,如文件系统、各种服务器软件、桌面以及其他 Linux 和开放源码软件。

撰写文章和书籍来帮助系统管理员和其他想成为系统管理员的人是我自己回报开源社区的主要方式,也是提供某种程度的指导,即使这与预期的接收者有些距离。

同样,如果你擅长写作,你会发现写关于 Linux 和开源的文章是一个很好的帮助方式。

捐赠

最后,大多数项目接受货币捐赠。乍一看,这似乎是为开源项目提供支持的一种相当笨拙、不干涉的方式,但是所有的项目都需要资金支持。我选择了三个,我会时不时地给它们捐点钱。

因为它在我日常工作中的重要性,我用小额捐款支持图书馆办公室。我也支持一些高层组织。我向 Linux 基金会 7 捐款,因为他们支持 Linux 基础设施,他们直接支持 Linus Torvalds,付钱让他继续他在内核方面的工作,他们还支持其他对 Linux 的健康和发展很重要的开源社区。我还向开源倡议组织 8 捐款,该组织负责批准各种许可证,并证明它们符合开源原则。

还有许多其他开源组织和项目需要资金。你的捐款可以直接支持那些站在开源运动最前沿的人的工作。

思想

开源就是以这样或那样的方式做出贡献。我的主要贡献是在教学和写作方面。我喜欢做这两件事,而且我很擅长。

我不打算在这里列出一堆项目。主要原因是有太多,我肯定会错过一些。此处打印的任何此类列表都将是某个时间点的快照——即使我可以列出所有列表——并且在我将第一稿提交给出版商之前就会过时。在撰写本文时,我只是简单地列出了我支持的几个例子。

因此,如果你想支持一个项目,选择一个你熟悉的、对你有影响的项目,找到它的主页,在那里找到如何以对你有意义的方式做出贡献。

那就投稿吧!

二十六、真相

在这本书的大部分时间里,我们都心不在焉。毕竟,这是一本通常不太实用的技术哲学书。我只是想借此机会在这本书结束之前把我们带回现实世界。

这里有“真相”。现实每天都以多种方式强加给系统管理员。总是能够遵循本书之前提出的每一个原则是可能的——但这是不太可能的。在“真实”世界中,我们系统管理员面临一些难以置信的挑战,仅仅是为了完成分配给我们的工作。截止日期、管理和其他压力迫使我们每天多次决定下一步做什么和如何做。会议通常会浪费我们的时间——不总是,但通常是。寻找时间和金钱进行培训在许多组织中是闻所未闻的,并且需要在其他组织中推销您的系统管理员灵魂。

找到时间来记住并运用这一哲学是最大的挑战。然而,从长远来看,坚持这一理念确实会带来高额回报。

尽管如此,现实总是侵入如此完美的哲学领域。没有灵活性的空间,任何哲学都仅仅是教条,这并不是针对系统 管理员Linux 哲学。在这一章中,我们将探讨现实的某些方面如何影响作为系统管理员的我们。

电脑容易,人难

-布里奇特·琼斯

系统管理员必须工作并与人互动。这可能很难,但我们确实需要不时这样做。

从 1969 年我第一次坐在电脑前开始,我就一直喜欢电脑的一个特点是,当我写程序时,它完全按照我告诉它的那样去做。我可以让它做我想做的任何事情——在它的能力范围内——通过输入一系列组成程序的命令。如果我想改变它的功能,我所要做的就是改变程序。很简单。

人一点都不简单。我不仅无法访问他们的程序,他们也不总是关注他们自己的程序,或者其他人认为他们拥有的程序。人一点都不简单。如果我是每个人的老板,他们都会按照我的方式去做,那么事情可能就简单了。但事实并非如此。

因此,在我们努力成为最有禅意的系统管理员的过程中,我们遇到了人。他们通常是善意的,甚至是大多数 PHB。问题是很多不懂技术。

微观管理者

我曾经遇到过这样的情况,在我做志愿者工作的地方,某个有权威地位的人给我发了一封电子邮件,说我需要尽快把一份文件放到网站的新闻提要上。他们还说硬拷贝在办公室的桌子上。他们想让我扫描硬拷贝,并把它作为一个图像。我回答说我想看这个文件,在我有机会看硬拷贝之前,我可以把它放上去。

这个人回复我说,他们(意思是现在有两个人以上)想让我看硬拷贝,因为这是一个奇怪的尺寸,他们不希望它“太大”。不管那是什么意思。但是,哦,顺便说一句,他们确实有一份发送到打印机的 PDF 的副本。不幸的是,没有附上 PDF。对此我回应说,我不在乎硬拷贝的大小,因为我会使其大小适合网站新闻提要上的可用空间,请将 PDF 发送给我。

我收到的下一封邮件附有一份 PDF 文件,并附有几句话,大意是说,如果文件在网站上太大,很多人会很不高兴。什么事?!

我从 PDF 复制粘贴到网站上的 WordPress 帖子上,并在文档上添加了他们想要的图像副本。它看起来真的很好,但是当你读到这封信的时候它已经不见了,所以我不会告诉你去哪里找它。

但是等等!还有呢!

到目前为止,这已经花了大约三天的时间。如果他们在第一封邮件中发送了 PDF 文件,我本可以在收到文件二三十分钟后将文件上传到新闻订阅源上。

为了确保相关人员之间的和谐,我们第二次见面时,我与电子邮件的作者进行了交谈,表明我对他们的语气不太满意。然后,我简要地解释了我这样做的四个理由,以及为什么在网站上直接使用 PDF 不如使用我复制的原始文本好。我相信你能想到很多原因,所以我在这里就不赘述了。

我与之交谈的那个人看起来很困惑,他说,“我一点也不明白你刚才说的话。”我说我只是想确保文档在网站上看起来尽可能的好,然后就此结束了谈话,留下了我当时想的一些事情没有说出来。

那就是与人打交道。这是我们的现实。

我知道参与其中的人只是想让一切看起来都很好,给网站的访问者留下一个好印象。我知道。但是知道这一点并不会使处理多人试图微观管理一项根本不需要管理的任务的挫折变得更容易。

多即是少

如果你不能用才华蒙蔽他们,就用废话迷惑他们。

—厕所场地

我曾经有一件又旧又丑的 t 恤,上面用非常醒目的字体印着这句话。我听到许多系统管理员对与他们一起工作的非技术人员说类似的话。这种态度可能适用于 t 恤,但不适合真正专业的系统管理员。

我们系统管理员必须与其他人互动,无论他们是用户、其他团队的技术专家、同事还是管理层。我们需要和其他知识水平不同的人讨论我们的工作。知识不是二元状态;它是模拟的。人们对计算机和技术的了解程度差异很大。这包括从看似一无所知到知识渊博。他们的知识水平对我们如何与他们互动很重要。

我发现的一件事是,无论人们对计算机和技术的知识水平如何,当我详细解释事情时,他们几乎总是反应良好。在这种情况下,我假设我向其解释事情的人足够聪明,能够理解我说的一切,如果他们不理解我说的话,他们会要求澄清。

当我这样做时,有两种不同的反应。第一种反应是来自那些对技术不太了解的人。在我走得很远之前,他们通常只是说他们不明白。在这种情况下,我尽我所能总结,让它去了。在许多情况下,这些人对我试图告诉他们的东西感到困惑,但感觉很好,因为我假设他们应该被视为知识渊博。这有助于产生好感,并为你们双方的积极体验创造条件。第二种反应来自有见识的人。他们很感激我愿意给他们详细的解释,但通常只是想很快切入正题。

这种方法让对方在谈话中设定自己的限制。他们可以随时告诉我们他们想要更多或更少的信息。我发现,从长远来看,给人们更多的信息意味着更少的麻烦。

技术支持恐怖

我也是一个人。当我为我的网络连接打电话寻求技术支持时,在我说“我确实重启了调制解调器”之前,我甚至不让第一级支持人员问我他们脚本上的第一个问题。我没有重启我的电脑,因为它是 Linux,不需要重启。我想和第三级支持人员通话。”

他们讨厌我打电话。我知道我给他们打电话后,他们会谈论我好几天。然而,当我打电话时,我已经做了他们在与一级支持人员的脚本对话中试图让我做的所有事情。我不能忍受不得不通过各种级别的支持来提升自己。我要花时间完成工作。

然而,有时候,他们可以马上把它修好。有时候,谈话另一端的人实际上有一些相关的知识。

所以我问自己,“自我——当别人需要我的帮助时,他们是如何看待我的?”答案并不好。我问了我的妻子,她没有犹豫。这不太好。我可以傲慢,居高临下,唐突,同时又是一个真正的混蛋。这当然不是我的意图,但事实就是如此。

对我来说,这可能完全是对其他事情感到沮丧的结果,我被打断了,我多次听到同样的问题,我只是累了,或者其他什么。所有这些对正在发生的事情的情绪反应都会阻碍问题的解决。

这是我的现实——双向的。所以我个人的任务是对需要我帮助的人和我试图帮助的人更友好。

你应该按我的方式做

我记不清在这本书里说过多少次了,在 Linux 中做任何事情都没有唯一正确的方法。我甚至写了一章题为“没有应该”,来表达我的观点。

然而,如果我屈服于自己的冲动,告诉人们按照我的方式去做,一切都会简单得多。我只知道如果他们按我的方式做,一切都会好的。看着一个新的系统管理员为一些我可以快速修复的东西而苦苦挣扎,是令人沮丧和艰难的。我作为导师很难让他们犯错。我认为这对我来说是最难的事情,看着并让年轻的学习艰难的方式。

我从我的飞行教练那里学会了如何做这件事。许多年前,我上过飞行课。这个过程花了我几个月的时间,我已经进行到一半了,在和我的教练进行飞行训练之前,我正在对一架塞斯纳 152 飞机进行预演。我已经完成了整个外部清单,上了飞机,坐在左手边的座位上。我检查了小屋的清单和启动清单。清单对飞行员来说是件大事。一直以来,我的教官只是坐在副驾驶的位置上看着。

在检查清单的最后,我松开了驻车制动,并稍微推进了一下油门。飞机没有移动。我把油门踩得更大一点,但还是没有反应。发生这种情况只有一个原因。我从侧窗向外看,以确认我确实把楔块留在了原处。那架飞机不会去任何地方——这正是轮挡应该做的。

我检查了停机清单,离开飞机,拉上轮挡,回到飞机上,第二次检查了启动清单。这一次,飞机确实自由移动了。我滑行到跑道尽头,起飞开始我们的训练飞行。

我的教练对此只字未提。她不必这样做,因为我知道我遗漏了清单上的一个步骤。我很好地吸取了教训。我教练的工作是教我如何自己驾驶飞机,而不是为我做事。如果她做了我为我忘记的事情,我怎么能学会呢?我再也不坐飞机了,但是当我坐飞机的时候,我总是,总是记得执行清单上的每一项,并确保我检查了轮挡已经被拉上。

这些绝对是最好的教学时刻。当你看到年轻的系统管理员显然正在犯一个错误,而你一句话也没说就让他们继续。

在这些情况下,还需要注意其他一些事情。观察你正在训练的系统管理员的行为举止。如果他们感到沮丧和愤怒,责怪你没有告诉他们他们知道你看到了什么,如果他们把他们的问题归咎于别人,他们可能不适合系统管理员的工作。

可以说不

有时候系统管理员不得不说不。平坦的,笔直的,没有其他选择,不。布鲁斯和我不得不完全拒绝一些想使用我们实验室的项目。那些项目会在我们平稳运行的实验室中产生巨大的动荡,破坏其他几个项目的工作。

我的意思是,我们确实解释了为什么我们不能承担这些项目。我们花了一些时间与提出这些项目的工程师在一起,帮助他们理解为什么他们的项目与我们实验室已经在做的工作不相容。他们不高兴,但他们最终理解了为什么我们不能在实验室做他们需要的事情。在这两种情况下,我们都提出了替代方案,包括建立自己的实验室,但我不知道他们最终做了什么。

有时一个强烈的“不”是正确的答案,不管它是否被欣赏。

科学方法

我们已经研究了使用基于科学方法的算法来执行问题确定和解决。有效。你的算法可能和我的有些不同,但是如果你成功了,就坚持下去。使用某种形式的这种算法将使问题的解决更加严格和可重复。

然而,有些问题就是难以解决。虽然如果有足够的时间和算法中各种循环的迭代,它们可能会被解决,但从头开始可能更有意义。毕竟,在生产环境中工作时,有必要将停机时间降至最低。

我有时会将硬盘从一个有故障的系统转移到一个正常工作的系统。Linux 现在处理硬件的方式是使用 dbus 和 udev 在/dev 中自动添加设备专用文件,这使得移植硬盘的系统很容易启动并运行,没有任何问题。一旦新系统启动并运行,我就可以在有故障的硬盘上安装另一个硬盘,并试图找到问题的根源。在其他情况下,最快的解决方案是重新安装操作系统。

有时,即使我有足够的时间和大量的谷歌搜索,我仍然无法解决问题。这是我发现有必要重新安装操作系统的又一次,以便回去做更有效率的工作。我不喜欢这样做,因为我可能永远也找不到问题的根源。

先说清楚,科学方法确实有效。然而,有时需要修理电脑并让它再次工作意味着我们只需要振作起来,做任何必要的事情让它再次运行。如果我们以后能找出根本原因,那就太好了,对我们未来有帮助。如果没有,我们只能带着未被满足的好奇心前行。

了解过去

我发现学习 Unix 和 Linux 的历史既有趣又有教益。在本书的前面,我特别提到了两本书,它们对我理解 Linux 及其哲学很有帮助。

迈克·甘卡兹的《Linux 和 Unix 的哲学》在哲学方面特别有趣。第二本书是 Eric S. Raymond 所著的《Unix 编程的艺术》2,提供了关于 Unix 和 Linux 编程和历史的有趣的内幕历史观点。这第二本书的全文也可以在网上免费获得。 3

*如果你还没有读过这两本书,我建议你都读一读。它们为我在本书中所写的大部分内容提供了历史和哲学基础。

最后的想法

这是一本有趣的书。当我第一次概述这些章节时,我想我可能找不到太多关于其中一些章节的内容。看来我真的有很多话要说。所以我会尽量缩短最后一部分。

  • 电脑坏了。

  • 系统管理员修复损坏的计算机。

  • 人很难。

  • 系统管理员与各种类型的人打交道。

  • 阅读我在这本书里提到的书。它们是令人惊奇的资源,可以为成为 Linux 系统管理员提供强有力的见解。

  • 永远不要停止学习新事物。每天都有更多的东西要学。

  • 遵循哲学。

  • 使用算法。有效。

最后,你应该在这本书里找到的唯一“应该”。

             ___________________
            < It should be fun! >
             -------------------
                   \   ^__^
                    \  (oo)\_______
                       (__)\       )\/\
                           ||----w |
                           ||     ||

*

第一部分:简介

Introduction

的第一部分面向系统管理员的 Linux 哲学向您介绍了 Unix 哲学和最初的 Linux 哲学,它直接来源于 Unix 哲学。您将了解一些关于 Unix 和 Linux 发展的历史和参与者,以及他们是如何首先带来 Unix 哲学,然后是 Linux 哲学的。

你也会了解到我设定自己哲学的原因和动机。这在很大程度上是由于最初的 Linux 理念在应用于系统管理员时的不足。

在这本书里,你会发现动手实验可以实现大多数系统管理员最喜欢的学习方式——边做边学。在第一部分中,您将为这些实验做准备。你将得到一套运行实验的 Linux 计算机的最低规格,你将准备一个 USB 记忆棒用于一些实验。

本书中的实验旨在简短明了。它们的主要目的是帮助系统管理员理解 Linux 的哲学。

第二部分:基础

Foundation

用户可以充分利用 Linux 命令行的强大功能。今天的图形用户界面(GUI)使得许多人不需要使用命令行,他们只想使用一些相对简单的工具来浏览网页、使用电子邮件,或者阅读或编写文档。大多数 Linux 用户无法想象 GUI 背后隐藏的强大功能。然而,允许更多用户轻松访问计算机能力的 GUI 隐藏了这些计算机交给我们的大部分能力。

有一群人特别容易成为命令行的主要用户:系统管理员,也称为系统管理员。系统管理员是命令行的最终超级用户,因为它提供了对全部可用功能的直接访问。

这并不是说普通的非 root 用户不使用命令行。很多人会这样做,但通常是在 GUI 无法满足他们需求的时候。大多数 Linux 发行版都有安装程序的图形化工具;管理用户和组及其权限;移动和管理文件;处理电子邮件;浏览网页;管理进程和 CPU 功能;限制某些用户对系统资源的访问;还有更多。但是如果命令行界面(CLI)的普通用户进行足够深入的探索,他们会发现 Linux 提供了许多文本模式和命令行工具来执行每一个可以在 GUI 中执行的任务——以及许多不能在 GUI 中执行的任务——通常速度更快,具有更多的特性和功能。

作为一名系统管理员,我的需求包括能力、速度、灵活性以及对操作系统的全面控制。满足所有这些需求的唯一方法是不受限制地访问 Linux 命令行,在那里所有的能力和速度都暴露无遗。作为一名系统管理员,我发现自己在管理任务中使用 CLI 的次数远远多于使用 GUI。在很大程度上,这是因为我更喜欢 CLI,但也有许多 Linux 计算机没有安装任何类型的 GUI,即使那些安装了 GUI 的计算机在试图通过任何远程桌面工具执行远程管理时也会非常慢。如果你有一个到远程计算机的非常快的互联网连接,这些远程 GUI 工具可能是有用的,但是它们永远不会像一个好的老式终端会话那样快,因为 GUI 数据的网络开销会耗尽带宽。

我并不是说我不使用 GUI 桌面,它们是“坏的”事实上,我发现 GUI 桌面可以提高我在 CLI 上的工作效率。我使用 GUI 通过同时打开多个终端会话来利用我的 CLI 访问,从而为多个 Linux 主机上的多个用户提供对 CLI 的同时访问。

我在 GUI 桌面上使用图形工具。我用 LibreOffice Writer,一个强大的,图形化的,免费的,开源的文字处理程序来写这本书。我欣赏并利用 CLI 和 GUI 各自的优势。然而,Linux 的真相是 CLI 为那些愿意使用它的人提供了最强大的功能。

系统管理员的 Linux 哲学的这一部分将向你介绍哲学的基本原则。这些原则是 Gancarz 书中记录的 Unix/Linux 哲学原则的发展体现,我们将在这一部分看到更多。这种对 Unix 和 Linux 的基本设计的哲学方法,为这两种操作系统的稳定性、优雅性、简洁性和内在功能做出了贡献。

这不是偶然的。Linus Torvalds 最初开发 Linux 是作为一种爱好,但有意将其基于 Unix。他采用免费提供的 GNU 实用程序,然后针对 Linux 重新编译,并将它们添加到他的操作系统中,当结合在一起时,纯化论者称之为 GNU/Linux。

任何操作系统的个性和可用性都是设计者所做的假设的函数。Linux 也不例外。它从一开始就被设计成类似 Unix 的,Unix 开发者已经决定 Unix 将允许它的用户访问它所设计的每一点功能。不仅如此,他们还为用户提供了获得这种能力所需的工具。毕竟,设计一个操作系统——或任何其他相关的东西——然后限制对它的访问有什么好处呢?GNU/Linux 是免费的自由开源软件——FLOSS——在理念和实现上很像 Unix。

由于它们的重要性和对 Linux 个性的深远影响,我在本书中花了大量的篇幅用文字解释这些基本原则,并用动手实验来说明它们。我相信,只有对这些原则有了坚定的理解,功能原则才能得到理解,它们对系统管理员的日常任务的适用性才能得到更全面的认识。

在本书的第二部分,我们的启蒙始于哲学的最基本层面。我们将了解“Linux 真相”、数据流、标准 IO (STDIO)、转换数据流以及“一切都是文件”的含义随着我们的工作生活开始有所启发,我们发现自己正在学习许多新的命令,如何在简单的命令行程序中有效地使用它们,以及如何利用一切都是文件的事实。

第三部分:功能

Function

在第三部分中,我们的启示不仅仅是在键盘上敲击命令,我们开始以更高级的方式应用基础知识。为了更好地利用命令行,我们开始扩展我们的命令行程序,并创建经过测试的、可移植的和可维护的 shell 程序,我们保存这些程序并可以重复使用,甚至共享。我们变成了“懒惰的管理员”,开始自动化一切。我们使用 Linux 文件系统层次结构以开放格式存储数据。

对于系统管理员来说, Linux 理念的这一部分是关于让我们的工作更容易。我们使用命令行的功能,并应用一些新的原则来利用我们在第二部分中学到的东西,尽可能多地实现自动化,并创建适合我们的程序。

系统管理员的自动化不是编译程序,因为创建、测试、发布和维护这些程序需要太多的时间和精力。面向系统管理员的编程是关于 shell 程序的,比如 BASH 编程,它是快速、开放和可移植的。

一些业内人士会认为 shell 编程不如用编译语言编写程序那么费力。这是不正确的,虽然我在某些地方使用了术语脚本和脚本,但是编写 shell 脚本和使用 c 一样是编程。shell 编程的优势是多方面的,我们将在本节中详细讨论这些优势。

让我们同意“脚本”和“程序”这两个词是可以互换的。所以当我说“程序”时,您可以理解为 shell 脚本,尤其是 BASH 脚本,因为 BASH 是几乎所有 Linux 发行版中的默认 shell。

第四部分:成为大师

Becoming Zen

本书的第四部分把我们从作为一名系统管理员的日常实践带到了更深奥的禅宗世界。我们着眼于面向系统管理员的 Linux 哲学的各个方面,这些方面是关于做出我们自己的选择,以对我们有意义的方式做事,与包括 PHB 在内的所有合作者打交道并尊重他们,以及回馈社区。

在这本书的这一部分,你只会发现几个实验。然而,你会发现一些我通常试图传授给学生和新的系统管理员的建议和意见——那些我曾经是他们导师的人。

进入并和我一起成为禅。

posted @ 2024-08-02 19:34  绝不原创的飞龙  阅读(6)  评论(0编辑  收藏  举报