这篇文章的内容主要是利用进程的创建,等待,终止,替换。这些知识来实现一个自己的简易shell。
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如果我们不想消失可以用动态开辟的,如果我们想存多个环境变量可以用二维数组。
此时我们再测试一下:
我们看到成功添加了环境变量。
3.4 重定向的实现
这是我们平时用重定向的方法,左边是一个完整的命令,右边是一个文件,把本来输入到显示屏上,重定向到文件中。
那么在获取用户输入后,它有这几种情况:ls -a -l>log.txt or cat<file.txt or ls -a -l>>log.txt or ls -a -l,原来下面写的是分析命令的过程。那么我们就要在这两个之间完成打开文件和重定向的工作。
那么我们就写一个函数来检查是否重定向:
那么我们就需要从头开始遍历一遍:
如果我们遇到了一个大于符号,那么我们还要进一步判断它下一个位置:
情况分好之后,我们可以定义一些标识符:
以输出重定向为例:
第一步:将遇到的符号改成\0。
DROP_SPACE这个意思是:把符号后面的空格去掉。
我们把信息记录下来,其它两个也是一样的道理。
在每次循环开始,把文件信息和重定向信息重新设置一下:
到这里,我们重定向信息已经整理好了,现在我们需要完成重定向了。
这里程序替换,会影响曾经子进程打开的文件吗?
不影响,程序替换只会影响该进程的代码和数据,曾经子进程打开的文件是内核数据,程序替换不会影响。
这里在open第三个参数:在创建文件时,设置文件访问权限的初始值。我们创建时,还是设置一下,不然创建出来是随机值。第三个参数是在第二个参数中有O_CREAT时才用作用。若没有,则第三个参数可以忽略。
运行结果如下:
总结:
1. 环境变量的数据,保存在进程的上下文中。
2. 环境变量会被子进程继承下去,所以会有全局属性。
3. 当我们进行程序替换的时候,当前进程的环境变量非但不会被替换,而且会继承父进程的。