Effective Shell 间奏:理解 Shell
您可能熟悉 Shell,但详细了解一些周边概念会很有用。Shell 与终端有何不同?什么是 tty?Shell 究竟是如何工作的?希望在阅读本文时,您会发现一些关于 Shell 的新知识。

这是我的 Effective Shell 系列中的第一个“间奏”。这些间奏为一些主题提供了背景、历史或更多的趣味性。
这个内容应该足够高层,即使是非技术读者也能欣赏(或至少理解!)。我已尽力确保任何可能不熟悉的术语都在脚注中进行了描述[1]。对于技术读者来说,它提供了关于 Shell 及其工作原理的关键概念的重要基础。
面向非技术读者的介绍
令许多技术计算机用户(程序员、数据科学家、系统管理员等)惊讶的是,他们花费大量时间使用一个看起来像 60 年代界面的界面。

如果您与技术人员一起工作,您可能看到他们使用这样的界面。这种简单、基于文本的界面称为 Shell,自第一批屏幕和键盘创建以来,它一直是与计算机交互的常用方式。
考虑到计算技术的巨大进步,为什么人们会使用这样的界面?看看 Windows 操作系统在过去三十年里发生了多大的变化

(来源:(WP:NFCC#4), 公平使用, https://en.wikipedia.org/w/index.php?curid=58853841)
为什么人们会选择使用像 Shell 这样古老的界面?
- 打字速度快:熟练的 Shell 用户仅用键盘就能以惊人的速度操作系统。输入命令通常比通过鼠标浏览用户界面快得多。
- Shell 是可编程的:用户在 Shell 中工作时,通常会进行编程,创建脚本来自动化耗时或重复的过程。
- Shell 是可移植的:Shell 可以以非常相似的方式与几乎任何类型的计算机进行交互,从大型机到 Raspberry Pi。
并非所有技术用户都会定期使用 Shell,但有许多人会花费大部分时间在这种界面中。能够有效地操作它是至关重要的技能,我一直在撰写这个系列,主要是为了展示如何更有效地使用这种界面。
面向技术读者的介绍
您可能熟悉 Shell,但详细了解一些周边概念会很有用。Shell 与终端有何不同?什么是tty?Shell 究竟是如何工作的?希望在阅读本文时,您会发现一些关于 Shell 的新知识。
让我们开始吧!
要理解 Shell、终端、命令提示符等是什么以及它们之间的关系,我们需要从基础开始:现代计算机是如何工作的!
计算机概览
下图显示了典型计算机的简化视图。

已经有很多东西在里面了。
您的计算机将拥有 CPU[2] 和内存[3],几乎肯定还有网卡[4] 和显示卡[5]。大多数计算机至少会有一块硬盘。对于家用 PC,很可能还有一堆外围设备,例如鼠标、键盘、打印机、U 盘、网络摄像头等。
操作系统
操作系统是安装在计算机上能够与硬件交互的软件。没有硬件,如 CPU、内存、网卡、显卡、磁盘驱动器等,您对计算机能做的就很少了。操作系统是与该硬件的主要接口。普通程序不会直接与硬件通信——操作系统会抽象化该硬件,并为其提供软件接口。
操作系统提供的抽象是至关重要的。开发人员不需要了解与不同供应商的各个设备打交道的具体细节;操作系统为所有这些提供了标准化的接口。它还处理各种任务,例如确保系统正常启动。
操作系统通常分为两部分——内核和用户空间。

让我们更详细地看看这些。
内核
这是操作系统中负责最敏感任务的部分:与物理设备交互、管理用户和程序可用的资源、启动各种必需的系统等等。
在内核中运行的软件可以直接访问资源,因此极其敏感。内核会在用户空间中的程序之间平衡资源,我们稍后会讨论这一点。如果您曾经需要安装“驱动程序”,这些就是将在内核中运行的软件的例子——它们将直接访问您安装的物理设备,并将其暴露给计算机上的其他软件。
为什么叫“内核”?内核是坚果或种子的柔软、可食用的部分,被外壳包围。下面您可以看到一个核桃——内核是中间柔软的部分,外壳将其包围和保护。这是一个用于计算机部分的有用比喻。

(图片作者:Kkchaudhary11 - 本人作品,CC BY-SA 4.0,https://commons.wikimedia.org/w/index.php?curid=49069244)
操作系统内核确实是操作系统的核心。它是操作系统中一个非常敏感的区域,我们实际上希望尽可能避免在其中运行软件[6]。而这正是用户空间发挥作用的地方。
用户空间
绝大多数程序都运行在“用户空间”(也通常称为“用户模式”)中。
当程序启动时,内核会为其分配私有内存段,并提供有限的资源访问权限。程序通过操作系统访问一组函数库,可用于访问文件、设备等资源。用户空间中的程序本质上处于沙箱中,其造成的损害是有限的。
例如,在用户空间中运行的程序可以使用标准的 fopen
函数,该函数几乎在所有操作系统上都作为 C 标准库的一部分提供。这允许程序尝试打开一个文件。操作系统会决定该程序是否允许打开该文件(基于权限、文件位置等),然后,如果调用没问题,它会授予程序对文件的访问权限。在底层,这个“用户空间”调用会转换为内核中的系统调用。
既然已经介绍了关键组件,我们就可以开始讨论Shell了。这个名字应该不出意外,因为它是一个操作系统(本身包含内核这个敏感的内核)的包装器或外层。
Shell
那么 Shell 是什么?Shell 只是任何用户空间程序的通用名称,它允许通过某种接口访问系统中的资源。
Shell 有许多不同的风格,但通常是为了方便人类操作员访问系统。这可以通过在终端中键入进行交互式操作,或者通过包含一系列命令的脚本文件来实现。
例如,要查看文件夹中的所有文件,人类操作员可以用 C 等语言编写程序,并使用系统调用来完成他们想要的操作。但对于日常任务来说,这将是重复的。Shell 通常会提供一种快速完成该确切任务的方法,而无需手动编写程序。
这是一个例子,其中 Shell 被用来显示我当前工作文件夹中的“png”图像[7]。

所以 Shell 是一个用于与计算机交互的用户空间程序。但是,我们在这里看到的图像中,除了 Shell 之外,还有一些额外的活动部分。有不同类型的 Shell,有终端程序,还有 Shell 所调用的程序或命令(在上例中,tree
是一个程序)。让我们逐一分解。
这是一张更准确地显示正在发生情况的图。

这里出现了一些新东西。有一个用户,他正在与一个终端交互,终端正在运行一个Shell,Shell 显示着一个命令提示符。用户输入了一个调用程序的命令(在此例中是 tree
程序)。
让我们逐一解析。
终端
在这张图表中,我们并不是直接与“Shell”交互。我们实际上在使用一个终端。当用户想要交互式地使用 Shell 时,使用键盘提供输入,使用显示器在屏幕上查看输出,用户就会使用一个终端。
终端只是一个读取键盘输入、将输入传递给另一个程序(通常是 Shell)并将其结果显示在屏幕上的程序。Shell 程序本身并不执行此操作——它需要一个终端作为接口。
为什么叫终端这个词?当您查看人们历史上如何与计算机交互时,这个词就很有意义了。计算机的输入可能是通过打孔卡,输出通常是通过打印机。电传打字机终端[8] 成为用户与计算机交互的常用方式。

(照片作者:Rama, Wikimedia Commons, Cc-by-sa-2.0-fr, CC BY-SA 2.0 fr, https://commons.wikimedia.org/w/index.php?curid=17821795)
当时,计算机是非常庞大、复杂且昂贵的机器。通常有很多终端连接到一台大型机器(或“大型机”),或者几台终端供人们共享。但终端本身只是操作系统的一个人机界面。更现代的终端可能是像 IBM 3486 这样的设备

(作者:ClickRick - 本人作品,CC BY-SA 3.0,https://commons.wikimedia.org/w/index.php?curid=6693700)
这本身就是一台非常小的计算机,但仍然基本上只是一个通过电缆连接到另一台远程大型机上的哑巴屏幕和键盘。
这个机制直到今天仍然非常适用。当我想操作数据中心的一台计算机时,我不会去找那台机器,插上键盘和显示器,然后直接与其交互。我在我的计算机上运行一个终端程序来访问远程机器。我的终端程序允许我使用我的键盘和显示器来操作远程机器——所有这些都通过安全 Shell——这是一个通过网络的加密 Shell 连接。
所以,从很多方面来看,终端都非常简单——它们是接口。但因为它们是非常简单的程序,所以我们不能用它们做太多事情。因此,通常情况下,终端程序会做的第一件事就是运行一个Shell程序——一个我们可以用来操作计算机的程序。
终端本身没有什么特别之处——任何人都可以编写一个程序作为终端运行,这就是为什么您会看到各种各样的终端。例如,macOS 的标准“终端”应用程序,Linux 的 gnome-terminal,以及 iTerm2 和 Hyper。文章末尾有一些不同设置的截图。
回到 Shell
既然我们已经描述了终端,我们可以回过头来详细看看 Shell。
Shell 是一个将从某个地方获取输入并运行一系列命令的程序。当 Shell 在终端中运行时,它通常是从用户那里交互式地获取输入。当用户键入命令时,终端会将输入馈送给 Shell,并将 Shell 的输出显示在屏幕上。
Shell 程序也可以从文件中获取输入;这些文件通常被称为“Shell 脚本”。这可能用于运行自动化操作,例如在计算机启动时清理某些文件夹。
Shell 可以将输出写入文件或其他位置,依此类推。您可以在终端之外运行 Shell 程序——只是您将无法通过键盘或显示器与之交互。事实上,许多操作都是这样进行的:自动化脚本、启动任务、安装程序等等。
那么 Shell 还能做什么?大多数功能都与帮助人类操作员更有效地使用系统有关。
- 快速输入命令、查看命令历史记录并快速重构命令(请参阅 Effective Shell - 命令行的导航)。
- 在文件系统中导航,从一个文件夹移动到另一个文件夹(请参阅 Effective Shell - 移动!),这使得操作员更容易导航文件系统。
- 将命令的输出链接在一起——例如,获取一个基本程序的输出,如我们看到的
tree
程序,并将其写入文件(请参阅 Effective Shell - 理解管道)。 - 提供一种编程语言,允许操作员执行更复杂的任务(请参阅 Effective Shell - 基本 Shell 脚本)。
还有更多!事实上,这正是整个 Effective Shell 系列的主题——如何从这些强大的程序中获得最大收益,特别是对于那些经常使用它们的人来说。
命令提示符或命令行
图表中最后一个我们尚未介绍的部分是命令提示符。

当Shell在终端中运行时,它知道人类操作员会与之交互。因此,为了确保操作员有一个视觉提示,表明他们需要输入命令,Shell 会输出某种提示。
我在文章的最后,就在本节之后,包含了一组截图,您可以在其中看到一些不同的命令提示符的样子。
请注意,Shell 不一定使用命令提示符——如果您使用 Shell 程序执行脚本,则不会有命令提示符。Shell 仅在知道它们被交互式使用时才显示提示符。许多允许用户交互式操作的程序都会显示一个命令提示符。
Shell 命令提示符可以自定义,因此它们在不同机器上的外观可能会有所不同(有关更多详细信息,请参阅 Effective Shell - 自定义命令行)。下面是一个显示了大量技术信息的示例。这是来自“Z Shell” Shell 的非常流行的 oh-my-zsh 框架,该框架在开发人员中非常受欢迎。

*(来源:https://ohmy.z.sh/)*
Shell 命令和不同的 Shell
Shell 中的许多“命令”,例如 cat
(显示文件内容),实际上只是简单的程序,它们会与内核交互。无论您使用哪种 Shell,这些命令的行为都将相同,因为实际上您只是在调用另一个程序。
有些命令,例如 cd
(更改目录),内置在 Shell 中。有些命令是已定义的函数,或者是其他命令的别名(有关命令的更多详细信息,请参阅 Effective Shell - 命令)。命令在不同 Shell 之间通常会有所不同。
并非所有 Shell 都一样——任何人都可以编写一个 Shell 程序,或许会创建一个简单的计算机接口,或者一个高度复杂的、具有许多功能的接口。事实上,本系列稍后的一篇文章将探讨最常见 Shell 的谱系。
在大多数类 Unix 系统上,默认 Shell 是一个名为 bash
的程序,它是“Bourne Again Shell”的缩写(其名称和历史将在稍后的文章中详细讨论)。但还有许多其他 Shell:C Shell、Korn Shell、Z Shell 和 Fish,仅举几例。
用户和管理员可以配置他们喜欢使用的 Shell。当终端打开时,它会立即启动用户的首选 Shell 程序。这是可以更改的。不同的用户会有不同的偏好,因为 Shell 提供不同的功能。这可能会导致处理系统时的复杂性,因为我们不能总是期望每个用户都有相同的 Shell,甚至 Shell 的设置也不一致,因为它们可以进行广泛的自定义。
让我们再次回顾之前的图表。

在上面的图表中,我们可以轻松地看到“终端 -> Shell -> 程序”链条中真正发生的情况。
在 Shell 中尝试命令 pstree -psa $$
[9]。

第一个 systemd
进程是操作系统的主进程——它是进程号1
,它初始化所有其他进程。第二个 systemd
进程正在运行我用户界面的进程。我们可以暂时忽略它们;它们是操作系统如何启动和运行进程的内部机制。
有趣的是,我们可以看到一个终端(gnome terminal),它启动了我首选的Shell(即 zsh
),而 Shell 正在运行一个命令(pstree
程序)。这里我们可以看到与前面图表中显示的完全相同的链条。
总结!
这些是围绕 Shell 的关键技术和概念。
如果您对 Shell 的更多技术细节感兴趣,那么我的 Effective Shell 系列将深入探讨这些主题。本系列的目标是帮助教授使 Shell 工作更有效率的技术。
为了结束本文,下面是一些不同终端、Shell、命令提示符等的示例。
示例:iTerm 2 / tmux / zsh

在这个例子中,我们有
- macOS 操作系统
- iTerm2 作为终端程序
tmux
作为“终端复用器”(请参阅 Effective Shell: 终端复用器)zsh
(Z Shell)作为 Shell 程序,使用“oh my zsh”,通过命令提示符中的%
符号很容易识别。- 自定义命令行,它在一行中显示用户和文件夹,下面只有
%
符号,以便为输入命令留出大量空间[10]。
示例:Bash


在这个例子中,我们有
- Linux 操作系统(Ubuntu 14)
- gnome 终端
bash
作为 Shell- 在第二个截图中,用户拥有“root 权限”,为了表示这一点,
bash
会友好地将默认命令提示符从美元符号更改为哈希符号。
示例:Windows Explorer

在这个例子中,我们有
- Windows 10 操作系统
- 没有终端
explorer.exe
程序显示了一个图形化 Shell。
这与之前的示例不同。显示熟悉的 Windows 界面的程序 explorer.exe
实际上也是一个 Shell,它提供对操作系统和计算机资源的交互式访问。与此界面交互的 Windows API 的大部分都在 Shell 库中。我还维护一个流行的用于构建图形化 Windows Shell 扩展的库——sharpshell。
示例:Windows 命令提示符

在这个例子中,我们有
- Windows 10 操作系统
- 命令提示符终端和 Shell
在 Windows 中,终端和 Shell 被合并到一个 cmd.exe
程序中。有一篇关于其内部机制的出色文章——Microsoft DevBlogs: Windows Command-Line: Inside the Windows Console。
示例:Windows PowerShell

在这个例子中,我们有
- Windows 10 操作系统
- PowerShell 终端
PowerShell 是对最初在 Windows 中使用的“命令提示符”程序的改进,为脚本编写和其他现代 Shell 功能提供了更多功能。
示例:Windows Subsystem for Linux (WSL)

在这个例子中,我们有
- Windows 10 操作系统
Bash.exe
程序
这个截图来自 MSDN: Windows Subsystem for Linux 常见问题解答,显示了在 Windows 中运行的 Bash。这是撰写本文时相对较新的功能,它允许 Windows 用户使用 Linux 界面访问 PC。这项功能可能会越来越有价值,因为一般来说,编写可以在 Windows 和类 Unix 系统上运行的 Shell 代码是具有挑战性的。
分享与讨论
如果您喜欢这篇文章,请分享!请随时在下面的评论中提供建议、改进或更正。
有用参考
- 一个简单的 Linux 内核模块,展示了 Linux 中基本的内核编程是如何工作的:github.com/dwmkerr/linux-kernel-module
- Linux 工作原理 - Brian Ward
- StackExchange:'终端'、'Shell'、'tty' 和控制台之间确切的区别是什么?
- Microsoft:Windows 控制台内部
脚注
我很好奇,这对于不太懂技术的人是否有趣,所以请在评论中告诉我! ↩︎
CPU:中央处理器。这是计算机中进行大部分工作的芯片(经过多层抽象后,最终会变成算术运算和向其他地方发送简单指令)。 ↩︎
内存是“工作空间”,您的系统状态存储在这里。如果您正在编写文档,文本会保存在内存中,直到您保存它,然后才会写入硬盘。内存是短暂的——当您关闭电源时,一切都会消失。 ↩︎
这是您计算机中负责连接到 WiFi 网络或带有您可能要插入网线的网络插槽的部分。 ↩︎
这是您连接屏幕的部分。 ↩︎
这是因为内核模式程序中的错误可能会产生灾难性的后果。它可以访问任何文件,无论属于谁,控制硬件,安装更多软件——几乎任何事情。此代码中的错误可能会导致可怕的问题(例如臭名昭著的 Windows“蓝屏死机”),而内核中的恶意代码实际上可以完全访问您所有的数据,以及您的网络摄像头、网卡等。 ↩︎
顺便说一句,如果您对我的设置的视觉风格或进行的自定义感兴趣,我的设置中的所有内容都可以在我的“dotfiles”存储库中在线找到——github.com/dwmkerr/dotfiles。 ↩︎
而这正是您有时会看到的“TTY”首字母缩略词的由来。输入
ps
命令,您实际上会看到每个进程连接到的 TTY 接口。这是本系列稍后将要讨论的主题。 ↩︎