📜  开发基于Linux的Shell

📅  最后修改于: 2021-06-28 07:47:26             🧑  作者: Mango

什么是贝壳?

它是用户与之交互的操作系统的可见部分,用户通过向外壳提供命令来与操作系统进行交互,外壳反过来解释并执行这些命令。

下图显示了简化的执行过程,其中shell接收输入,
将其传递给词法分析器(将进行详细讨论),该词法分析器将创建标记,然后将词法分析器的输出传递给解析器,该解析器检查其语法错误并执行分配的语义动作(这将构建命令表) ,最后,当解析器到达某个点时,将执行该表。

该外壳将由3个组件实现,如下架构图所示:

架构图

1.词法分析器
在输入分析的第一部分是在读取输入为单位来构成标记词法分析阶段,我们将使用一个名为lex命令来构建我们的文件,在这个文件中,我们将定义我们的模式其次:标记名称词法分析器将逐字符读取输入的字符,并且当模式与左侧的字符串匹配时,它将转换为右侧的字符串。
前任:

Command input: ls -al

解析器将读取l,然后形成称为WORD的令牌,然后读取–和字符(al)并形成OPTION,输出将为WORD OPTION,此输出将传递至解析器以检查是否存在语法错误。

上面的语法由11个标记组成,当输入符合标记描述时,这些标记会形成。
IO令牌由一个#字符或一个’>’组成,其后可以加一个数字(最多一次)或’我们将其作为新令牌引入以代替错误重定向令牌的方式,IO的另一种形式是使用’> >”,其后可以加一个数字(最多一次),最后是“>&”,它是一个IOR,可以在其前和/或后跟一个或两个。

管道符号和符号在“ |”处形成和“&”分别表示当带连字符,空格和任何字母字符或数字的连字符时形成的选择令牌。
当有两个连字符前加空格,后跟任何字母字符时,会形成option2令牌。
WORD令牌可以由字母字符,数字和以下字符%,=,+,’,“,(,),$,/,_,-,。,?,*,〜

2.解析器
从输入形成令牌后,令牌将作为流传递到解析器,该解析器对输入进行解析以检测语法错误并执行分配的语义动作。解析器可以被认为是语言的语法和语法(定义了命令的外观),我们将使用名为yacc的命令来编译语法,将语法构造为状态的一种形式,使语法的构建和部署更加容易。

以下是我们的语法定义:

上面的语法指定了解析过程的不同状态,

解析器从状态q00开始进行解析,直到到达状态q5,q3,q1之一为止,由于使用的解析技术(自下而上的解析),这种情况以相反的方式发生,语法根据其位置减少了标记,Word可以如果它出现在开头,则减小为cmd;如果它出现在命令后,则减小为arg_list;如果文件的重定向后出现,则减小为file,然后根据语法对句子进行解析,从状态q00开始,解析器通过读取cmd进入状态q1 ,在状态q1处,如果解析器读取了一个选项,则该句子可以具有以下参数之一(IO或背景后面),或者不包含以下任何一个参数;如果解析器读取参数,则该句子只能在其后具有重定向功能;如果解析器读取与号,则应该一无所有。

然后,当解析器读取管道时,该过程再次开始,这允许通过管道连接多个简单命令以形成一个复杂命令。
我们将简单命令定义为由命令,选项,参数和/或IO重定向组成的任何命令。
使用管道将多个简单命令组合在一起,就形成了我们称为复杂命令的结构。

与语法相关的语义动作将构建解析表,并将命令值分配给数据结构,该数据结构在构建命令表后将其发送给执行器。
命令表由几行简单的命令组成,这些行由通过管道连接的复杂命令,一个简单的Command条目(包含要执行的命令名称),options(与该命令一起执行的选项),arguments(由参数组成)组成应当传递给命令的参数,standard-in(stdIn),它指定命令从默认位置获取命令输入的位置,除非命令中另有指定,否则它是终端,否则,standard-out(stdOut)指定命令的位置将打印执行的输出,默认情况下是终端,standard-error(stdError)指定命令将打印执行错误消息的位置,默认情况下,它是终端,除非用户将其重定向。

构建的语法允许以下语法:

句法

它允许带有选项,参数,IO重定向的命令成为后台进程(&)。当我们连接多个简单命令而形成一个复杂命令时,具有上述任何命令的命令就是一个简单命令。

在解析命令时,解析器会将命令详细信息保存在表中,以传递给执行程序。

我们选择了一个表作为数据结构,我们需要存储有关每个命令的以下信息,即Command,Option,Option2,Arguments,StdIn,StdOut,StdError。

例如:

ls –al | sort -r

该命令将生成下表(每行是一个简单命令,该表本身是一个复杂命令)。

桌子

3,执行人

建立命令表后,执行程序负责为表中的每个命令创建一个进程,并在需要时处理任何重定向。
执行程序遍历表以执行每个简单命令,并将其连接到表中每个条目(简单命令)的下一个命令。执行程序命令将命令,选项和参数传递给execvp函数,以替换当前的调用过程对于被调用的文件,execvp函数作为第一个参数接收要执行的文件的名称,以及一个以空值结尾的数组,该数组包含选项(如果有)和参数。

但是在调用execvp之前,执行程序将在外壳程序中处理重定向,如果命令前面带有命令,这意味着之前有一个管道,因此将命令的输入设置为从上一个管道接收,然后检查该命令是否如果存在,则任何输入重定向都会覆盖前一个管道的输入,如果该命令之前没有命令,则没有管道(简单命令),否则(多个简单命令)该命令的输出将发送到下一个管道在表中的命令中,如果存在输入重定向,则将检查命令的输出重定向,如果分配文件中的输入覆盖了前一个命令的输入。

处理完重定向后,将检查命令的后台标志,该标志指示外壳程序是否应等待命令完成执行或发送要在后台执行的进程,现在为了使执行程序能够执行该命令,创建要执行的外壳程序的映像,执行程序将分支当前进程(外壳程序)并在此分支的子级上执行命令。

执行程序从执行第一行开始,将命令的输出设置为标准输出,然后在第一个命令(ls -al)执行第二个命令之后,将输出覆盖到管道,以便第二个命令接收首先从分配要从标准输入中读取的输入开始执行,然后由于该命令之前是另一个命令,因此该输入被设置为从管道接收,并且由于该命令不包含任何输入重定向(来自文件)该命令的标准输入将保留在管道中,该命令的标准输出将显示在屏幕上,然后检查该命令是否应将其输出发送到以下命令,在这种情况下,这是最后一个命令,因此输出不会被管道覆盖,但是由于命令具有输出重定向到文件的功能,因此文件将覆盖标准输出。

执行以下命令

ls –al | sort –r >file

解析器生成的表将如下所示:

表2

执行程序代码将遍历该表并执行上述步骤,并在命令完成时清除所有内容,以准备接收下一条命令。