原文:The Linux Philosophy for SysAdmins
协议:CC BY-NC-SA 4.0
七、拥抱 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 或其他小型计算机。哑终端有足够的逻辑来显示来自主机的数据,并将击键传送回主机。所有的处理和计算都在终端所连接的主机上进行。
图 7-1
DEC VT100 哑终端。本文件根据知识共享署名 2.0 通用许可协议进行许可。作者:杰森·斯科特
甚至更古老的终端,如机械电传打字机(TTY),比阴极射线管显示器的普遍使用还早。他们使用新闻纸质量的纸卷来记录命令的输入和结果。我上的第一堂大学计算机编程课使用了这些 TTY 设备,它们通过电话线以每秒 300 比特的速度连接到几百英里外的阿格(是的,通用电气)分时计算机。那时我们大学买不起一台自己的电脑。
许多与命令行相关的术语都源于这两种类型的哑终端的历史使用。例如,术语 TTY 仍然被广泛使用,但是我已经很多年没见过真正的 TTY 设备了。再次查看您的 Linux 或 Unix 计算机的/dev 目录。你会发现大量的 TTY 设备文件。
注意
我们在第五章中讨论了设备文件。
终端设计的唯一目的是允许用户通过输入命令和在纸卷或屏幕上查看结果来与他们所连接的计算机进行交互。术语“终端”倾向于暗示一个独立于计算机的硬件设备,同时用于与计算机通信和交互(图 7-2 )。
图 7-2
Unix 开发者 Ken Thompson 和 Dennis Ritchie。汤普森正坐在一台用于与 Unix 计算机接口的电传打字终端前。彼得·哈默——由马格努斯·曼斯克上传
安慰
控制台是一种特殊的终端,因为它是连接到主机的主要终端。它是系统操作员用来输入命令和执行任务的终端,这些任务在与主机相连的其它终端上是不允许的。当出现问题时,控制台也是主机显示系统级错误消息的唯一终端。
可以有许多终端连接到大型机和 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 。
图 7-3
虚拟控制台 2 的登录提示
每个虚拟控制台被分配给对应于控制台编号的功能键。因此 vc1 将被分配给功能键 F1,依此类推。在这些会话之间切换很容易。在你的电脑上你可以按住 Ctrl-Alt 键,按下 F2 切换到 vc2。然后按住 Ctrl-Alt 键,按 F1 切换到 vc1 和通常的图形桌面界面。如果没有 GUI 运行,vc1 将只是另一个文本控制台。
虚拟控制台提供了一种使用单个物理系统控制台、键盘、视频显示器和鼠标(KVM)访问多个控制台的方法。这为管理员执行系统维护和解决问题提供了更大的灵活性。还有一些其他方法来增加灵活性,但如果您可以物理访问系统或直接连接的 KVM 设备或一些逻辑 KVM 扩展(如 Integrated Lights Out 或 iLO ),虚拟控制台总是可用的。在某些环境中,screen 命令等其他方法可能不可用,GUI 可能在大多数服务器上也不可用。
图 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 等等。每个终端模拟器都有一组吸引特定用户群的有趣特性。一些具有在单个窗口中打开多个标签或终端的能力。其他的仅提供执行其功能所需的最小特征集,并且通常在要求小尺寸和效率时使用。
图 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。运行几个简单的命令,比如ls
和free
,看看这些命令的工作方式有什么不同。这是因为除了内置命令之外,大多数命令都是独立于 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”键两次。
-
输入
screen
命令,该命令将清除显示并离开命令提示符。您现在处于屏幕显示管理器中,一个终端会话打开并显示在窗口中。 -
键入任何命令,例如
ls
,除了命令提示符之外,还会在终端会话中显示一些内容。 -
按下 Ctrl-a + c 在屏幕会话中打开一个新的 shell。
-
在这个新终端中输入不同的命令,例如
df –h
。 -
键入 Ctrl-a + a 在端子之间切换。
-
输入 Ctrl-a + c 打开第三个端子。
-
键入 Ctrl-a + " 列出打开的终端。使用向上/向下箭头键选择除最后一个以外的任何一个,并点击 Enter 键切换到该终端。
-
要关闭一个终端,键入 exit 并按下 Enter 键。
-
键入命令 Ctrl-a + " 以验证终端是否已消失。请注意,具有您选择关闭的编号的端子不再存在,其他端子也没有重新编号。
-
使用 Ctrl-a + c 重新打开一个新的终端。
-
键入 Ctrl-a + " 以验证新终端已经创建。请注意,它已在之前关闭的终端位置打开。
-
要从屏幕会话和所有打开的终端断开,按下 Ctrl-a + d 。请注意,这将使所有终端和其中的程序保持完整并仍在运行。
-
在命令行输入命令
screen
-list
命令,列出所有当前屏幕会话。如果有多个屏幕会话,这有助于确保重新连接到正确的屏幕会话。 -
使用命令屏幕****–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>
。
图 7-6
要添加到 listserv 的电子邮件地址原始文档的部分列表
很明显,我需要处理数据,以便将它转换成可接受的格式,输入到列表中。可以使用文本编辑器或文字处理器(如 LibreOffice Writer)对这个小文件进行必要的更改。然而,人们经常给我发这样的文件,所以用文字处理器来做这些修改就成了一件苦差事。尽管 Writer 有很好的搜索和替换功能,但每个字符或字符串都必须单独替换,并且没有办法保存以前的搜索。Writer 确实有一个非常强大的宏功能,但是我对它的两种语言 LibreOffice Basic 和 Python 都不熟悉。我确实知道 bash shell 编程。
我做了一个系统管理员应该做的事情——自动完成任务。我做的第一件事是将地址数据复制到一个名为 addresses.txt 的文本文件中,这样我就可以使用命令行工具来处理它。经过几分钟的工作,我开发了 bash 命令行程序,如图 7-7 所示,它产生了所需的输出,即 addresses2.txt 文件。换行是可以接受的,但是在命令完全输入之前不要按回车键。
图 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 脚本。
维基百科,控制台, https://en.wikipedia.org/wiki/Linux_console
2
维基百科,Xterm, https://en.wikipedia.org/wiki/Xterm
3
维基百科,托马斯迪基,
4
维基百科,控制台, https://en.wikipedia.org/wiki/Konsole
5
Fedora 杂志,Tilix, https://fedoramagazine.org/try-tilix-new-terminal-emulator-fedora/
6
维基百科,Rxvt, https://en.wikipedia.org/wiki/Rxvt
7
维基百科,Gnome 终端, https://en.wikipedia.org/wiki/Gnome-terminal
8
维基百科,终结器, [https://en.wikipedia.org/wiki/Terminator_(terminal_emulator
](https://en.wikipedia.org/wiki/Terminator_(terminal_emulator)
9
维基百科,比较命令的炮弹, https://en.wikipedia.org/wiki/Comparison_of_command_shells
10
维基百科,Richard M. Stallman,
11
维基百科,连续表格纸, https://en.wikipedia.org/wiki/Continuous_stationery
12
维基百科,IBM 1403 打印机, https://en.wikipedia.org/wiki/IBM_1403
八、做一个懒惰的系统管理员
尽管我们的父母、老师、老板、善意的权威人士告诉了我们一切,而且我用谷歌搜索找到了数百条关于努力工作的引文,但按时完成工作并不等同于努力工作。一个不一定意味着另一个。
我是一个懒惰的系统管理员。我也是一个非常高效的系统管理员。这两种看似矛盾的说法并不相互排斥;相反,它们以非常积极的方式互补。效率是实现这一目标的唯一途径。
这一章是关于在正确的任务上努力工作来优化我们自己的效率。其中一部分是关于自动化的,我将在这里提到,并在第九章详细讨论。但是这一章的大部分是关于寻找无数种使用已经内置在 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 模式下做一些事情。
-
滚动回到我们在实验 8-4 中使用的长
echo
命令。 -
按下 Esc 键一次,进入 vi 命令模式。
-
键入 23b 返回 23 个单词。
-
键入 d18w 删除 18 个单词。
-
按一次左箭头键,将光标放在单词“file”末尾的空白处
-
按 r 进入单字替换模式。
-
按句点键替换空格。
-
按下 ^ (用 shift 键)移动到行首。这里什么也不做,只是让你看到光标移动到行首。
-
按下 $ 将光标移动到行尾。
-
这是我偶然发现的东西。按 Esc 然后按 :w <回车> 保存历史中的线。该行在未执行的情况下被保存,命令提示符现在为空。
-
现在,滚动回最后一个命令,它应该类似于下面的行。不要按回车。
```sh
[student@testvm1 ~]$ echo "This is a way to create a new file." >> newfile2.txt
```
-
使用左箭头键将光标移回“2”
-
按下 r 进入替换模式,然后按下 3 将“2”替换为“3”您的命令行现在应该是这样的。
```sh
[student@testvm1 ~]$ echo "This is a way to create a new file." >> newfile3.txt
```
- 现在按下键进入。
验证新文件是否存在及其内容。
如果你已经熟悉了 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
这样的工具来帮助隔离所需的行,搜索日志文件也可能是一项耗时且麻烦的任务。在进行故障排除时,连续查看文本格式日志文件的内容,尤其是查看到达的最新条目,往往会有所帮助。使用cat
或grep
查看日志文件,显示输入命令时的内容。
我喜欢使用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
日志监控
在处理问题时,使用像grep
和tail
这样的工具来查看日志文件中的几行内容是没问题的。但是,如果您需要搜索大量日志文件,该怎么办呢?即使使用这些工具,这也可能是乏味的。
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 时一样,通过预测问题并做必要的工作来防止问题发生,以确保它们不会发生或能够被有效地解决,从长远来看是有好处的。
我在这里讨论的策略并不是唯一可以用来提高我们自身效率的策略。我确信你已经找到了许多你自己更聪明地工作的方法。
有一种方法可以极大地利用我们的技能和知识,虽然我提到过很多次,但我还没有详细讨论过。在第九章中,我们将探索“自动化一切”的宗旨以及它真正的含义。
维基百科,单位记录设备, https://en.wikipedia.org/wiki/Unit_record_equipment
2
桑德·范·伍格特。开始 Linux 命令行 (Apress,2015),22。
3
维基百科,命令行补全, https://en.wikipedia.org/wiki/Command-line_completion
4
gnu.org,Bash 参考手册-命令行编辑, https://www.gnu.org/software/bash/manual/html_node/Command-Line-Editing.html
5
彼得·克鲁明斯的博客,痛击历史, http://www.catonmat.net/blog/the-definitive-guide-to-bash-command-line-history/
6
彼得·克鲁明斯的博客,Bash emacs 编辑, http://www.catonmat.net/blog/bash-emacs-editing-mode-cheat-sheet/
7
彼得·克鲁明斯的博客,痛击 vi 编辑, http://www.catonmat.net/blog/bash-vi-editing-mode-cheat-sheet/
8
计算机希望网站, https://www.computerhope.com/unix/usar.htm
9
Nemeth,Evi [et al.],《Unix 和 Linux 系统管理手册》 ,培生教育公司,ISBN 978-0-13-148005-6。这本书在亚马逊上也有 Kindle 格式。
10
数字海洋,“如何使用 Journalctl 查看和操作 Systemd 日志”, https://www.digitalocean.com/community/tutorials/how-to-use-journalctl-to-view-and-manipulate-systemd-logs
11
SourceForge,Logwatch 资源库, https://sourceforge.net/p/logwatch/patches/34/
*
九、自动化一切
我的问题是,“计算机的功能是什么?”正确的答案是,“将日常任务自动化,以便让我们人类能够专注于计算机还不能完成的任务。”对于系统管理员,也就是我们这些最密切地运行和管理计算机的人来说,我们可以直接使用工具来帮助我们更有效地工作。我们应该最大限度地利用这些工具。
在这一章中,我们将探索如何使用自动化来使我们作为系统管理员的生活变得更加轻松。
我为什么使用脚本
在第八章“做一个懒惰的系统管理员”中,我说,“一个系统管理员在思考的时候最有效率——思考如何解决现有的问题和如何避免未来的问题;思考如何监控 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 引导加载程序配置文件,以便它包含每个已安装内核的恢复选项。最后,如果需要重启,我会这样做。
图 9-1
执行升级后运行 mandb 的部分结果
这是一组需要一些决策的非平凡的单个任务和命令。手动执行这些任务需要注意力和干预,以便在前一个命令完成时输入新的命令。因为需要在等待输入下一个命令的时候照看孩子,这将花费我大量的时间来监控每台计算机,因为它经历了这些过程。当我在主机上输入错误的命令时,偶尔会有人提醒我,这是有出错的空间的。
使用我在上面创建的需求陈述,因为这就是那个段落的真实内容,很容易自动消除所有这些问题。我写了一个叫做 doUpdates 的小脚本。它的长度超过 400 行,提供了帮助、详细模式、打印当前版本号等选项,以及只有在内核或 glibc 更新后才重新启动的选项。
这个程序中超过一半的行都是注释,所以下次我需要修改它或者增加一些功能的时候,我可以记得这个程序是如何工作的。大部分基本功能都是从一个模板文件中复制的,该模板文件维护了我在编写的每个脚本中使用的所有标准组件。因为新脚本的框架总是在那里,所以很容易开始新的脚本。
图 9-2 是 doUpdates bash 脚本的列表。为了防止大部分较长的行换行,我将字体大小设置得比平时小一点。然而,一些很长的行被包装。如果太小,读起来不舒服,我道歉。我还删除了一些空白行和空的注释行,以尽可能缩短列表,但它仍然有大约 8 页长。
图 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)正在使用中,并且我不必在每次向远程主机发出命令时都输入密码。
图 9-3
此命令使用公钥/私钥对进行身份验证,在远程主机上运行 doUpdates 程序
现在,我在本地主机上运行一个命令,通过 SSH 隧道向远程主机发送一个命令。好的,这很好,但是这意味着什么呢?
这意味着我能在一台电脑上做的事,我也能在几台甚至几百台电脑上做。图 9-4 中的 bash 命令行程序展示了我现在拥有的能力。
图 9-4
这个 bash 命令行程序在三台远程主机上运行 doUpdates 程序
以为我们完事了吗?不,我们不是!下一步是为这个 CLI 程序创建一个简短的 bash 脚本,这样我们就不必在每次想要在主机上安装更新时重新键入它。这不一定要花里胡哨;脚本可以像图 9-5 中的脚本一样简单。
图 9-5
这个 bash 脚本包含在三台远程主机上运行 doUpdates 程序的命令行程序
这个脚本可以被命名为“updates”或其他名称,这取决于您喜欢如何命名脚本以及您认为它的最终功能是什么。我认为我们应该把这个脚本叫做“doit
.
”现在我们只需输入一个命令,就可以在for
语句列表中的所有主机上运行智能更新程序。我们的脚本应该位于/usr/local/bin 目录中,这样就可以很容易地从命令行运行它。
我们的小doit
脚本看起来可以作为更广泛应用的基础。我们可以向 doit 添加更多的代码,使它能够接受参数或选项,比如在列表中的所有主机上运行的命令的名称。这使我们能够在主机列表上运行我们想要的任何命令,我们安装更新的命令可能是doit doUpdates -r
或doit
myprogram
以在每台主机上运行“myprogram”。
下一步可能是从程序本身中取出主机列表,并将它们放在位于/usr/local/etc 中的 doit.conf 文件中——同样符合 Linux FHS。对于简单的doit
脚本,该命令看起来如图 9-6 所示。注意后面的 tics( ),它根据
cat命令的结果创建了一个由
for`结构使用的列表。
图 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 总是出现在最基本的安装中。
图 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 硬盘上。
图 9-8
/etc/crontab 中的这一行运行一个脚本,为我的系统执行每日备份
图 9-9 所示的行使用系统时钟作为精确时间的来源来设置计算机上的硬件时钟。这条线路设置为每天早上 5 点过 3 分运行。
图 9-9
这一行使用系统时间作为源来设置硬件时钟
最后一个 cron 作业,如图 9-10 所示,是我们特别感兴趣的一个。它用于在每月第一天的 04:25 执行我们的更新。这里假设我们使用的是图 9-5 中非常简单的doit
程序。cron 服务没有“当月最后一天”的选项,所以我们使用下个月的第一天。
图 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 中。[每小时|每天|每周|每月]目录,这取决于它们需要运行的频率。
这是如何工作的?这个序列比它第一次出现时要简单。
-
The crond service runs the cron job specified in /etc/cron.d/0hourly as seen in Figure 9-11.
图 9-11
/etc/cron.d/0hourly 的内容导致位于/etc/cron.hourly 中的 shell 脚本运行
-
/etc/cron . d/0 中指定的 cron 作业每小时运行一次 run-parts 程序。run-parts 程序运行位于/etc/cron.hourly 目录中的所有脚本。
-
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.
图 9-12
/etc/anacrontab 文件的内容运行 cron 中的可执行文件。适当时间的[每日|每周|每月]目录
-
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
脚本变得不仅仅能够为单个程序“做”更新。
David Both,Linux DataBook,《完成 Kickstart》, http://www.linux-databook.info/?page_id=9
2
如何伪造, https://www.howtoforge.com/linux-basics-how-to-install-ssh-keys-on-the-shell
十、总是使用 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。我们以前看过这个程序,但是让我们从不同的角度来看它。
图 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。我倾向于在从事各种项目时同时打开大量的虚拟机,包括为这本书所做的研究。
一般来说,唯一要问的问题是工作是否能及时完成。如果是的话,那就不用担心。如果没有,那么用编译语言编写和测试相同程序所需的时间很可能会更晚。编译后的程序运行时节省的时间少于使用 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 程序就是一个很好的例子。毕竟,安装更新是一个简单的yum
或dnf
命令,对吗?没有那么多。
在很长一段时间里,我会登录每台主机,运行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 的最后,我会讨论这个模板的一些其他方面。
图 10-2
我将 script.template.sh 模板文件用作新程序的起点
程序的主要部分在程序部分结束后开始。我通常以设置程序中使用的所有变量的初始值开始这一部分。这确保了我使用的所有变量都被设置为某个默认的初始值。它还提供了程序中使用的所有变量的列表。
接下来,我检查 root 是否正在运行这个程序,如果没有,显示一条消息并退出。如果你的程序可以被非根用户运行,你可以删除这个部分。
然后我有getops
和case
语句来检查命令行以确定是否输入了任何选项。对于每个选项,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 命令来完成剩下的那一点点工作,那么就编写一个小程序来做好一件事——这一点点在其他任何地方都找不到。
斯科特·K·彼得森,《源代码就是许可证》,Opensource.com, https://opensource.com/article/17/12/source-code-license