这篇文章的内容主要是利用进程的创建,等待,终止,替换。这些知识来实现一个自己的简易shell。
文章目录
- 1. 大致思路
- 2. 基本实现
- 3. 额外拓展
- 3.1 让文件带上颜色
- 3.2 内建命令
- 3.3 添加环境变量
1. 大致思路
我们用了这么长的shell,它的大概过程是这样:
其实就是我们在不断的输入一个程序,然后执行它。因为这个shell是不断可以执行程序,所以应该是shell创建一个子进程来完成我们输入的命令。如下图所示:
所以要写一个shell,需要循环以下过程:
1. 获取命令行
2. 解析命令行
3. 建立一个子进程(fork)
4. 替换子进程(execvp)
5. 父进程等待子进程退出(wait)
2. 基本实现
首先,shell就是一个死循环,当我们输入一个命令结束后,他就会开始下一个命令:
第二步,我们要提示一下用户:
这里我们为什么要这样来刷新,因为我们\n它会换行,我们输入命令一般是在提示符后面输入,而不是在下一行输入。
我们定义一个数组来存放你的字符串,然后把输入的字符串存进数组里:
这里我们一定要把回车清理,因为回车也是一个字符(\n)。我们这里用的fgets函数,可以看一下它的语法:
第三步,我们需要把输入的字符串按空格分割。因为在程序替换时,它是一个一个分开传的,我们可以把分割后的字符串放进一个数组里。
= 是故意这么写的,strtok 截取成功,返回字符串其实地址,截取失败,返回NULL。
这个strtok函数不理解的可以看这个文章:字符函数和字符串函数
第四步,我们创建一个子进程去进行程序替换:
我们来看一下运行结果:
我们可以看到大致是没有问题的。
3. 额外拓展
3.1 让文件带上颜色
我们平时用的shell,像可执行程序是绿色的,目录是蓝色的。那我们该如何实现呢?
首先,有一个alias的命令,它的意思是别名。
我们可以看到:myls和ls -a -l执行的一样的,但本质还是ls。那么我们可以看一下系统里的ls。
这个alias的意思是别名。ls本身是不带颜色的,但是加上一些其它选项。所以,我们自己写的时候,子进程程序替换找的是系统的ls,没有加上后面的选项。
所以遇到ls命令时,可以在下标为1的数组里加上这个选项。
3.2 内建命令
这个现象,我们可以看出我们cd过后,路径没有变化这是为什么。
从这里可以看出,我们用的cd可能不是/usr/bin目录下的。那这个cd是什么呢?
如果直接exec*执行cd命令,最多只是让子进程进行路径切换,但子进程是一运行就完毕的进程。但是在shell中,我们更希望父进程(shell本身)进行路径切换,因为父进程路径发生变化,后面的子进程也会继承父进程的路径。
如果有些行为,是必须让父进程shell执行的,不想让子进程执行的,绝对不能创建子进程。只能父进程自己实现对应的代码,由shell自己执行的命令,我们称之为内建(bind-in)命令。相当于shell内部的一个函数。
我们先看一下这个函数chdir:
这个系统调用函数意思是:用于改变当前工作目录,其参数为path 目标目录,可以是绝对目录或相对目录。
command_arge[1]里面存的就是目标路径。我们看一下运行结果:
这样就完美的解决了。那么有哪些内建命令呢?cd,export,echo这些都是。
3.3 添加环境变量
我们来看一下这样的运行结果:
所有的程序替换,如果我们不传环境变量,那么它会默认继承父进程的环境变量列表。
在前面,我们使用带e的exec函数时。自己传环境变量,它会覆盖系统的环境变量,那么我们该如果添加式的传环境变量,而不是覆盖式的呢?
从这个结果可以看到,我们在myshell里export没有办法添加,我们也不能使用environ指针去改变环境变量数组。我们可以使用一个putenv的函数:把字符串加到当前环境中。
我们先来演示一下:
我们这样来做看能不能成功:
我们可以看到还是没有成功。原因是:
每次循环时,它会重新初始化数组。这样我们写的环境变量的内容下一次就没了,所以我们需要自己保存一下环境变量内容。
这里的env_buffer如果我们不想消失可以用动态开辟的,如果我们想存多个环境变量可以用二维数组。
此时我们再测试一下:
我们看到成功添加了环境变量。
总结:
1. 环境变量的数据,保存在进程的上下文中。
2. 环境变量会被子进程继承下去,所以会有全局属性。
3. 当我们进行程序替换的时候,当前进程的环境变量非但不会被替换,而且会继承父进程的。