内核和实用工具

UNIX系统在逻辑上被划分为两个不同的部分:内核和实用工具。或者你也可以认为是内核和其他部分,通常来说,所有的访问都要经由 Shell
77e0478c-0fb1-4fca-b26e-18a03dbfdcf7.jpg
内核是 UNIX 系统的核心所在,当打开计算机并启动(boot)之后,内核就位于计算机的内存中,直到关机为止。组成完整的 UNIX 系统的各种实用工具位于计算机磁盘中,在需要的时候会被加载到内存中并执行。实际上你所知道的所有UNIX命令都是实用工具,因此这些命令所对应的程序也都在磁盘上,仅在需要时才会被载入内存。举例来说,当你执行date命令时,UNIX系统会将名为date的程序从磁盘上载入到内存中,读取其代码来执行特定的操作。Shell也是一个实用工具程序,它作为登录过程的一部分被载入到内存中执行。实际上,有必要了解当终端或终端窗口中的第一个Shell启动时所发生的一系列事件。

登录Shell

在早期,终端是一个物理设备,通过线缆连接到安装了UNIX系统的硬件上。而如今,终端程序能够让你停留在Linux、Mac 或Windows 环境内部,在受控窗口中同网络上的设备交互。通常来说,你会启动如Terminalxterm这类程序,然后在需要的时候利用 ssh、telnet 或 rlogin 连接到远程系统。对于系统上的每个物理终端,都会激活一个叫作getty的程序。
537ad905-3fc7-4af5-bcc7-e924a733faa5.jpg
只要系统允许用户登录,UNIX系统(更准确地说,应该是叫作init的程序)就会在每个终端端口自动启动一个getty程序。getty是一个设备驱动程序,能够让login程序在其所分配的终端上显示login:,等待用户输入内容。如果你是通过ssh这类程序来连接的,会分配到一个伪终端或伪tty。这就是为什么在输入who命令时会看到有类似于ptty3pty1这样的条目。在这两种情况下,会有程序读取账户和密码信息,对这些信息进行验证,如果没有问题的话,就调用登录所需的登录程序。
只要输入相应字符并敲下Enter键,login程序就完成了登录过程。 当login开始执行时,它会在终端上显示字符串Password:,然后等待用户输入密码。完成输入并按下Enter键后(出于安全性的考虑,你在屏幕上看不到输入的内容),login会比对文件/etc/passwd中相应的条目来验证登录名和密码。每个用户在该文件中都有对应的条目,其中包括了登录名、主目录以及用户登录后要启动的程序。最后一部分信息(登录 Shell)存储在每行最后一个冒号之后。如果这个冒号后面没有内容,则默认使用标准Shell,即/bin/sh

d6b9d5dc-6d2b-4c1c-abdc-f600d7fe2da0.jpg
如果是通过终端程序登录,数据交换也许会涉及系统上的程序(如ssh)和服务器上的程序(如sshd),要是你在自己的UNIX计算机上打开了窗口,可能不需要再次输入密码就能够立刻登入。
一旦该用户通过验证,login会结束掉自身,将控制权转交终端连接,该连接与标准Shell相连,然后login就从内存中消失了。
init程序会针对网络连接运行类似于getty的程序。例如,sshd、telnetd和rlogind会响应来自ssh、telnet和rlogin的连接请求。这些程序并没有直接和特定的物理终端或调制解调器线路联系在一起,而是将用户的Shell连接到伪终端上。

在Shell中输入命令

Shell启动时,它会在终端中显示出一个命令行提示符,通常是美元符$,然后等待用户输入命令。每次输入命令并按Enter键,Shell就会分析输入的内容,然后执行所请求的操作。如果你要求Shell调用某个程序,Shell会搜索磁盘,查找环境变量PATH中指定的所有目录,直到找到指定的程序。找到了该程序后,Shell会将自己复制一份(称为子Shell),让内核使用指定的程序替换这个子Shell,接着登录Shell就会休眠,等待被调用的程序执行完毕。内核将指定程序复制到内存中并开始执行。这个复制过来的程序称为进程。程序和进程之间是有区别的,前者是保存在磁盘上的文件,而后者位于内存中并被逐行执行。
如果程序将输出写入到标准输出中,那么输出内容会出现在终端里,除非你将其重定向或通过管道导向其他命令。与此类似,如果程序从标准输入中读取输入,那么它会等着你输入内容,除非输入被重定向到了另一个文件或通过管道从其他命令导入。
当命令执行完毕后,就会从内存中消失,控制权再次交给登录Shell,它会提示你输入下一条命令,只要你没有登出系统,这个周期就会周而复始下去。如果登出系统,Shell就会终止执行,系统将会启动一个新的getty(或者 rlogind 等)并等待其他用户登入。
重要的是要认识到Shell就是一个程序而已。它在系统中没有什么特权,也就是说,只要有足够的专业技术和热情,任何人都可以创建自己的Shell。这就是为什么如今会有这么多不同风格的Shell

63e82645-6cf3-4d94-98cc-afe8b7583af9.jpg

Shell的职责

fed381e3-eb6b-4524-a479-210baad8207c.jpg

程序执行

Shell负责执行你在终端中指定的所有程序。每次输入一行内容,Shell就会分析该行,然后决定执行什么操作。每一行都遵循以下基本格式:

  1. program-name arguments

Shell会扫描该命令行,确定要执行的程序名称及所传入的程序参数。Shell使用一些特殊字符来确定程序名称及每个参数的起止。这些字符统称为空白字符,它们包括:

  • 空格符
  • 水平制表符
  • 行尾符(更正式的叫法是换行符)

连续的多个空白字符会被Shell忽略。

Shell会搜索磁盘,直到找到需要执行的程序为止,然后由UNIX内核负责程序的执行。在大多数时候,的确如此。但有些命令实际上是内建于Shell自身中的。这些内建命令包括cdpwdechoShell在磁盘中搜索命令之前,它首先会判断该命令是否为内建命令,如果是的话,就直接执行。不过在调用命令之前,Shell还有点事需要处理,因此,让我们先来讨论一下这方面的内容。

变量及文件名替换

和其他的编程语言一样,Shell允许将值赋给变量。只要你在命令行中将某个变量放在美元符号$之后,Shell 就会将该变量替换成对应的变量值。Shell会在命令行中执行文件名替换。实际上Shell,在确定要执行的程序及其参数之前,会扫描命令行,从中查找文件名替换字符*?[...]

I/O重定向

Shell还要负责处理输入/输出重定向。它会扫描每一个命令行,从中查找特殊的重定向字符<>>>
如果你输入命令:

  1. echo Remember to record The Walking Dead > reminder

Shell会识别出特殊的输出重定向字符>,然后将命令行中的下一个单词作为输出重定向所指向的文件名。在本例中,这个文件名为 reminder。如果 reminder 已经存在且用户具有写权限,那么文件中已有的内容会被覆盖掉。如果没有该文件或其所在目录的写权限,Shell会产生错误信息。在Shell执行程序之前,它会将程序的标准输出重定向到指定的文件。在大多数情况下,程序根本不知道自己的输出被重定向了。它仍照旧向标准输出中写入(这通常是终端),意识不到Shell已经将信息重定向到了文件中。

管道

Shell在扫描命令行时,除了重定向符号之外还会查找管道字符|。每找到一个,就会将之前命令的标准输出连接到之后命令的标准输入,然后执行这两个命令。
如果你输入:

  1. who | wc -l

Shell会查找分隔了命令whowc的管道符号。它将上一个命令的标准输出连接到下一个命令的标准输入,然后执行两者。who命令执行时会生成已登录用户列表并将结果写入标准输出,它并不知道输出内容并没有出现在终端而是进入了另一个命令。当wc命令执行时,它发现并没有指定文件名,因此就对标准输入内容进行统计,并没有意识到标准输入并非来自终端,而是来自于who命令的输出。

环境控制

Shell 提供了一些能够定制个人环境的命令。个人环境包括:

  • 主目录
  • 命令行提示符
  • 用于搜索待执行程序的目录列表。

解释型编程语言

Shell有自己内建的编程语言。这种语言是解释型的,也就是说,Shell会分析所遇到的每一条语句,然后执行所发现的有效的命令。这与C++Swift这类编程语言不同,在这些语言中,程序语句在执行之前通常会被编译成可由机器执行的形式。相较于编译型语言,由解释型语言所编写的程序一般要更易于调试和修改。然而,所花费的时间要比实现相同功能的编译型语言程序更长。Shell编程语言提供了可在大多数其他编程语言中找到的其他特性。它有循环结构、决策语句、变量、函数,而且是面向过程的。