保姆教学篇:利用现学知识从0到1实现一个shell

先展示部分代码和注释

在这里插入图片描述

完整版(不含注释)

在这里插入图片描述

1. 了解shell的基本样式

  • 首先我们要知道,shell本身就是一个死循环,在不断获取用户的输入,像下面这个样子
    在这里插入图片描述
  • 所以接下来我们需要知道的就是实现一个死循环,然后在死循环中写代码
    在这里插入图片描述

2. 打印提示信息

  • 我们知道,我们看到的shell界面首先都会先打印出提示信息,比如:[用户名@主机名 当前目录]#
    在这里插入图片描述
    因此,首先我们需要解决提示信息的问题,那么很容易会写出下面代码:
    在这里插入图片描述
    这样的代码的运行结果:
    在这里插入图片描述
    显然这并不是我们想要的结果,那有人就想出了另一种代码:
    在这里插入图片描述
    运行结果:
    在这里插入图片描述
    显然也不是我们想要的结果,那么有人又相出了一种代码
    在这里插入图片描述
    运行结果:
    在这里插入图片描述
    显然也不是我们想要的结果,有人又相出不要换行
    在这里插入图片描述
    运行结果:
    在这里插入图片描述
    这个结果是直接不出来,原因很简单,就是因为打印的内容放在缓冲区中,没有被刷新到外设,因此,我们需要将缓冲区的内容刷新到外设
    在这里插入图片描述
    运行结果:
    在这里插入图片描述
    那么这样就是我们想要的效果

3. 获取用户输入

  • 存储命令行
    首先我们知道,用户从键盘输入的命令行本质是一个字符串,因此,首先我们需要一个字符数组来存放这个字符串
    在这里插入图片描述
    这里我们需要注意,我们在这里将数组声明为全局的数组,是因为如果声明为局部的数组的话,那么每次循环就需要重新创建该数组,这样系统的开销就会比较大,降低了系统的效率
  • 初始化数组
    在这里插入图片描述
  • 获取命令行
    这里我们可以考虑使用函数fgets();
    这个函数的基本介绍:
    在这里插入图片描述
    在这里插入图片描述

从上面的信息,我们可以知道一下几点:

  • 这个函数的功能:从一个指定的流拿一个字符串
  • 函数的三个参数第一个参数:存储字符串的数组第二个参数:获取的最大字符个数第三个参数:从哪个流获取

从stdin标准输入流(即键盘)中获取内容
在这里插入图片描述
接下来我们可以测试一下,我们是否已经成功获取对应字符串
在这里插入图片描述
运行结果
在这里插入图片描述
显然从上面的结果中,我们发现多出了一个换行符,其实很正常,因为我们在输入命令的时候最后我们会按下回车,因此,字符串中会多出一个换行,那么我们应该怎么将它清除呢??

  • 清楚命令行中的换行符
    在这里插入图片描述
    我们需要手动将存储数组中的最后一个字符设置为’\0’,代表字符串的结束,我们要知道:strlen(command_line)对应的是数组中最后一个元素的下一个元素,但是我们想要的是最后一个元素的位置,因此应该是将strlen(command_line)-1这个位置的元素改成'\0',也就是将原来是回车的元素改成了'\0'
    运行结果:
    在这里插入图片描述
    现在我们已经成功获取到用户在键盘中输入的字符串,但是我们知道程序替换函数中的第二个参数通常要么是字符列表,要么是字符数组字符数组中也是一个个的字符,因此,我们需要将获取到的字符串以空格进行分隔,以便为后续替换函数传参做铺垫

4. 分割字符串

分割字符串我们需要使用C语言中学习到的一个函数strtok()
我们首先来看一下这个函数的使用方法
在这里插入图片描述
显然这个函数有两个参数
第一个参数是要分隔的字符串
第二个参数是分隔符

这个函数在使用的时候需要注意一些点:

  • 第一次使用的时候,第一个参数传要分隔的字符串地址,第二个参数传分隔符,这个分隔符一般情况下使用#define定义成宏,后续比较好修改
  • 第二次使用起,第一个参数传NULL,第二个参数传分隔符

这样做的原因是**strtok()函数会自动记录第一次是分隔哪个字符串的**,所以就没有必要再传这个字符串地址了
接下来我们需要对command_line中的内容进行分隔,很明显分隔出来的是一个个的字符串,所以,我们需要一个字符指针数组来存放每一个字符串的地址,这个数组中存放的都是指向字符串的字符指针,也就是字符串的地址
在这里插入图片描述
分隔字符串
分隔的时候我们需要设置一个分隔符,这里是以空格作为分隔符
在这里插入图片描述

在这里插入图片描述
这个是分隔字符串的代码,需要注意的是循环部分的代码:这个是利用到strtok()函数的返回值如果分隔成功会返回分隔出的字符串的地址,分隔失败会返回空指针,因此分隔到最后的时候会返回空指针,循环自动就结束了
下面我们可以测试一下是否真的成功分隔出来,我们可以打印一下command_arg的内容:
在这里插入图片描述
运行结果
在这里插入图片描述
成功获取到用户输入的命令并做分隔之后我们就需要创建子进程了

5. 创建子进程&等待子进程退出

在这里插入图片描述
这个代码比较常规,前面已经写过多次

6. 子进程进行进程程序替换

在这里插入图片描述
按照我们在命令行输入的习惯,一一般情况下,我们输入的格式都是先输入运行的命令(程序)的名字,然后后面才是各种选项(执行方法),比如:ls -a -l -i,ls就是程序名,后面的依次是选项,因此,分隔出来的第一个字符串就是要运行的程序的名字,后面的内容是告诉这个程序应该怎么运行(运行方法)
运行结果
在这里插入图片描述
到目前为止就算做一个基本的shell,可以执行一些基本的指令,比如:
我们要执行ls -a -l -i这样的命令,运行结果如下
在这里插入图片描述

7.给shell设置颜色

当我们使用系统的shell的时候,我们会发现,系统的shell打印出来的一些文件名通常都会带有颜色,而我们的shell打印出来的都是默认没有颜色,那么这是为啥呢??主要是跟我们执行的指令有关系,比如:我们刚刚执行的ls命令是裸的命令,没有经过任何配置的命令,因此打印出来的结果自然是默认不带颜色的,那么我们可以来观察一些系统的ls命令是怎么样的:
在这里插入图片描述
这是系统中的ls,我们会发现系统中通过alias给ls设置了一个别名,alias是用于给命令设置别名的,上面的命令意思是:ls --color=auto等价于ls,后面的–color=auto就是给ls显示出来的文件设置一些颜色,接下来我们也可以给我们自己的shell的ls添加这样的功能,操作方法:
源代码:
在这里插入图片描述
实验结果:
在这里插入图片描述

8.一些内建命令的学习

(1). 更改父进程的工作路径

在上面的shell中,如果外面直接运行起来之后在命令对路径进行更改,我们会发现路径不会发生变化,现象如下:
在这里插入图片描述
上面的现象:我们多次通过cd ..将路径更改为上级路径,但是结果不是我们想要的那样,其原因如下:
上面这样的代码执行cd命令的是子进程,子进程可以成功将其路径修改,但是我们知道父子进程间是相互独立的,也就是说子进程的路径发生改变并不会影响父进程的路径,而上面的代码中子进程执行完对应的程序之后就自己退出了,后面父进程继续不断创建新的子进程,那么之前修改路径的子进程并不会影响新创建出来的子进程的路径,新创建出来的子进程的路径是继承其父进程的,由于刚刚的操作父进程的路径并不会受到改变,因此创建出来的子进程的路径继续继承其父进程的路径,那么其执行pwd查看当前进程的路径的时候结果就还是父进程的路径,因此我们看到的结果就是路径并没有发生改变
从上面的分析来看,显然我们需要改变的是父进程的路径,也就是我们自己实现的shell的路径而不是子进程的路径,改变子进程的路径没有任何意义,这个时候就需要使用我们的内建命令了,
内建命令:只能是父进程执行的命令就叫内建命令
这里会使用一个内建命令叫chdir,这个命令的作用就是改变工作路径,注意谁修改路径就让谁去调用
在这里插入图片描述
所以上面这个例子,我们应该让父进程去调用chdir

  • 源代码
    在这里插入图片描述
  • 实验结果:
    在这里插入图片描述
    这样才能成功让我们父进程的路径发生变化,从而影响新创建出来的子进程的路径

(2). 给myshell进程添加环境变量

在上面的代码中,我们使用的程序替换函数是execvp,和环境变量没有任何关系,但是我们知道环境变量具有全局属性,因此子进程的环境变量会默认继承父进程的环境变量,在我们当前的系统中,最大的父进程就是bash进程,我们写的shell是bash的子进程,其中的环境变量会全部继承bash进程的环境变量,因此,我们在我们自己的shell中仍然可以使用或者访问bash进程中的所有环境变量
演示:

  • 在当前路径创建一个myenv.c的源文件,并使用environ变量写好文件中的代码
    在这里插入图片描述
  • 当前目录存在的文件
    在这里插入图片描述
  • 让我们的shell进程的子进程执行myenv文件

在这里插入图片描述
显然照样能打印出系统中的环境变量
那么如果我们想要在我们自己的shell进程中新增环境变量可以吗?前面我们学过一种方法,就是使用export可以导出环境变量,现在我们可以在我们的shell中尝试一下
在这里插入图片描述
显然程序是执行失败的,那么应该怎么做呢?这个时候需要使用到我们的内建命令了,这个时候使用的是putenv()接口
在这里插入图片描述
从上面我们可以知道putenv()的作用就是改变或者新增一个环境变量,当环境变量存在的时候即为改变,不存在的时候即为修改
在这里插入图片描述
那么如果我们写出这样的代码
在这里插入图片描述
实验结果:
在这里插入图片描述
程序会执行失败,这是因为我们导出的环境变量的内容是存放在command_line数组中的,而command_line数组中的内容会不断被覆盖清空,所以会将我们的环境变量内容清空掉导致无法导出环境变量,此时我们需要先将环境变量的内容保存在一个新的数组中,然后我们再将这个新数组中的环境变量内容导出给当前shell进程,代码如下:
在这里插入图片描述

运行结果:
在这里插入图片描述

在这里插入图片描述
可见我们成功将我们的自定义环境变量导出给了当前进程

  • 关于上面条件部分的代码?
    我们知道command_arg数组中存放的是一个个的字符串,这些字符串是我们再键盘敲入的命令行分隔出来的,那么在导出环境变量的时候,我们一般都是输入export 环境变量=值,因此,正常情况下,这里的command_arg[0]就是我们输入的export,command_arg[1]就是我们的环境变量=值,如果不是这样,那我们就没必要执行下面的代码了
  • 19
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值