从Unix的早期开始,shell就已经成为用户与操作系统的接口的一部分。第一个Unix shell具有非常有限的功能,主要是I / O重定向和命令管道。后来的shell在那个早期的shell上进行了扩展,并增加了越来越多的功能,这给了我们强大的功能,包括单词扩展,历史替换,循环和条件表达式等。那么如何构建一个Linux Shell?
为什么使用本教程?
在过去的20年中,我一直使用GNU / Linux作为主要操作系统。我使用了许多GNU / Linux shell,包括但不限于bash,ksh和zsh。但是,我一直被这个问题困扰:是什么使shell打勾?例如,例如:
Shell如何解析我的命令,将它们转换为可执行指令,然后执行这些命令?
Shell如何执行不同的单词扩展过程,例如参数扩展,命令替换和算术扩展?
Shell如何实现I / O重定向?.. 等等。
由于大多数GNU / Linux外壳都是开源的,因此,如果您想学习外壳的内部工作原理,可以在线搜索源代码并开始深入研究。但是,这个建议实际上说起来容易做起来难。例如,您应该从哪里开始阅读代码?哪些源文件包含实现I / O重定向的代码?在哪里可以找到解析用户命令的代码?我想你明白了。
这就是为什么我决定编写本教程的原因,以帮助Linux用户和程序员更好地理解其shell。我们将一起从头开始实现一个功能齐全的Linux Shell 。在此过程中,我们将看到Linux shell如何通过实际编写执行上述任务的C代码来管理解析和执行命令,循环和条件表达式。我们将讨论字扩展和I / O重定向,并看到执行功能的代码。
在本教程结束时,我们将拥有一个基本的Linux shell,目前尚不能做很多事情,但是在接下来的部分中我们将对其进行扩展和改进。在本系列的最后,我们将提供一个功能齐全的Linux shell,该shell可以解析和执行一组相当复杂的命令,循环和表达式。
您将需要什么
为了遵循本教程,您将需要以下内容:
一个运行良好的GNU / Linux系统。
GCC来编译代码。
编写代码的文本编辑器。
如何用C编程
我不会在这里详细介绍安装所需软件的细节。如果不确定如何使系统运行上述任何软件包,请参考Linux发行版的文档,并确保在进行下一步操作之前已完成所有设置。
现在让我们开始做生意。我们将从对构成Linux shell的鸟瞰图开始。
Linux Shell的组件
Shell是一个复杂的软件,包含许多不同的部分。
任何Linux壳的核心部分是命令行解释,或CLI。这部分有两个目的:读取和解析用户命令,然后执行解析的命令。您可以将CLI本身分为两部分:解析器和执行器。
该解析器将扫描输入,将其分解到令牌。甲令牌由一个或多个字符,和表示输入的单个单元。例如,令牌可以是变量名,关键字,数字或算术运算符。
该分析器采用这些令牌,组在一起,并创建我们所说的一种特殊结构抽象语法树,或AST。您可以将AST视为您提供给Shell的命令行的高级表示。解析器获取AST并将其传递给执行器,该执行器读取AST并执行解析后的命令。
Shell的另一部分是用户界面,通常在Shell处于交互模式时操作。在这里,shell循环运行,我们称为Read-Eval-Print-Loop或REPL。
就像循环的名称所示,shell读取输入,解析并执行输入,然后循环读取下一个命令,依此类推,直到输入以下命令为止: exit , shutdown, 要么 reboot。
大多数外壳程序实现一种称为符号表的结构,该外壳程序用于存储有关变量及其值和属性的信息。我们将在本教程的第二部分中实现符号表。
Linux Shell还具有历史记录功能,该功能使用户可以访问最新输入的命令,然后无需过多输入即可编辑和重新执行命令。Shell也可以包含内置实用程序,它们是作为Shell程序本身的一部分实现的一组特殊命令。
内置实用程序包括常用命令,例如cd,fg和bg。在学习本教程时,我们将实现许多内置实用程序。
现在我们知道了典型Linux shell的基本组件,让我们开始构建自己的shell。
我们的第一个壳
我们第一个版本的shell不会做任何花哨的事情。它只会打印一个提示字符串,读取一行输入,然后将输入回显到屏幕上。在本教程的后续部分中,我们将添加解析和执行命令,循环,条件表达式等的功能。
让我们从为该项目创建目录开始。我通常使用~/projects/ 用于我的新项目,但是请随意使用您喜欢的任何方式。
我们要做的第一件事是编写我们的基本REPL循环。创建一个名为main.c,然后使用您喜欢的文本编辑器将其打开。在您的计算机中输入以下代码main.c 文件:
#include
#include
#include
#include
#include "shell.h"
int main(int argc, char **argv)
{
char *cmd;
do
{
print_prompt1();
cmd = read_cmd();
if(!cmd)
{
exit(EXIT_SUCCESS);
}
if(cmd[0] == '