Liunx系统编程——shell的简单实现
目录
一、准备工作
二、循环获取命令行
三、切割字符串
编辑 四、创建子进程、进程替换和回收进程
五、优化
1、设置颜色
2、简化形式的命令
六、自建命令(内建命令)
1、cd ..更改父进程的工作路径
2、给自己实现的shell中添加环境变量
七、取掉测试功能的完整代码
一、准备工作
我们要实现一个简单的shell,首先我们得先了解shell的运行逻辑和shell的设计形式。
- 首先shell是一个死循环,只要开始运行就会不断读取用户的输入。
- 可以看到shell的命令行窗口的形式,我们也要模仿其设计一个一样的。
- shell的一个循环的过程可以分为
- 获取命令行
- 解析命令行
- 建立一个子进程
- 替换子进程
- 父进程等待子进程退出
上面所涉及到的知识点我们都学习过了,至于更多的细节,我们边写边说。
二、循环获取命令行
首先利用死循环不断进行命令行获取,如果死循环中只有打印提示符,则 运行结果就是不断地打印提示符,为了解决这个问题且让用户输入命令。我们使用fgets()函数
fgets()函数地作用是从流中读取最多size个字符,并将其写入到s中,读取到EOF或者换行符停止,如果读取到换行符,也把换行符写入到s中,最后以‘\0’结尾,s是一个字符串,size是一个最多接收size个字符,stream是流。
运行结果:
三、切割字符串
我们现在输入了命令,但是输入的是一个长字符串,我们要对该字符串进行分析,要清楚哪一部分是需要执行的程序,哪一部分是选项参数,所以我们需要对存在数组中的长字符串进行切割。
C语言中,关于字符串的函数中,有一个strtok()函数是用来分割字符串,可以将指定字符串按照传入的分割符分开。
第一个参数是传入要分割的字符串,第二个字符串是要传入分割符。如果分割出了字符串,则返回此字符串的指针,否则,返回空指针。且第一次调用此函数之后,若字符串中还存在可以分割的字符串,可以直接在strtok()的第一个参数中传入空指针以再次调用此函数从上次分割出的字符串之后继续分割。
我们还需要将接收到的字符串的最后一个有效字符设置为‘\0',因为接收到的最后一个字符是’\n',即我们在输入命令的时候总是在最后敲一下回车键以表示结束,但是这个换行符也会被fgets()读进s中,在我们输出命令测试的时候,总会多出现一个空行。
运行结果:
四、创建子进程、进程替换和回收进程
shell中的大多数命令都是通过创建子进程来执行的。
可以fork()创建子进程,然后进程替换实现命令的执行。
实现fork()子进程替换为命令子进程,最佳的进程替换的接口是:exevp()
1、首先是因为,我们接受了命令行输入的程序及选项字符串,将字符串根据空格分隔开,就是一个命令和选项的数组。
2、带p字的接口,会默认从环境变量PATH的路径下搜索,不需要在添加程序的路径。
运行结果如下:
五、优化
1、设置颜色
我们可以看到用系统的bash执行命令之后,他会自动带上颜色,我们自己实现的却没有,那是因为我们执行的命令都是裸的命令没有经过任何的配置,我们先看看系统的命令
我们会发现系统中通过alias给ls设置了一个别名,alias是用于给命令设置别名的,上面的命令意思是:ls --color=auto等价于ls,后面的–color=auto就是给ls显示出来的文件设置一些颜色,接下来我们也可以给我们自己的shell的ls添加这样的功能,操作方法:
2、简化形式的命令
如果我们在bash中输入ll命令,他会自动识别是 ls -l,我们再完善一下这个
六、自建命令(内建命令)
在操作系统中,内建命令指的是那些直接集成在命令行解释器中的命令,这些命令不是独立的外部程序,而是作为shell的一部分存在,因此他们可以直接在shell内部运行,而无需创建新的进程来运行。
内建命令特点:
- 效率更高:不需要创建新的进程来执行,因此效率更高。
- 不依赖外部环境:完全由shell解释器支持,不受外部环境的影响。
- 直接集成:内建命令是shell的一部分,可以直接在脚本中使用。
内建命令如我们常使用的cd、export等。
1、cd ..更改父进程的工作路径
如图,在我们实现的简易shell中还运行不了一些命令。如cd ..跳到上级目录 ,即外面直接运行起来之后在命令对路径进行更改,我们会发现路径不会发生变化,这是怎么回事呢?
上面这样的代码执行cd命令的是子进程,子进程可以成功将其路径修改,但是我们知道父子进程间是相互独立的,也就是说子进程的路径发生改变并不会影响父进程的路径,而上面的代码中子进程执行完对应的程序之后就自己退出了,后面父进程继续不断创建新的子进程,那么之前修改路径的子进程并不会影响新创建出来的子进程的路径,新创建出来的子进程的路径是继承其父进程的,由于刚刚的操作父进程的路径并不会受到改变,因此创建出来的子进程的路径继续继承其父进程的路径,那么其执行pwd查看当前进程的路径的时候结果就还是父进程的路径,因此我们看到的结果就是路径并没有发生改变。
从上面的分析来看,显然我们需要改变的是父进程的路径,也就是我们自己实现的shell的路径而不是子进程的路径,改变子进程的路径没有任何意义,这个时候就需要使用我们的内建命令了。这里可以暂时理解为当前父进程执行的命令就叫内建命令。
这里会使用一个内建命令叫chdir,这个命令的作用就是改变工作路径,注意谁修改路径就让谁去调用。
所以我们应该让父进程去调用这个函数,这样才可以让父进程的路径发生变化,从而由它再创建处出来的子进程便能直接继承它的路径。
结果:可以看到路径是一层层往出跳的
2、给自己实现的shell中添加环境变量
在我们之前写的环境变量一篇中我们知道,环境变量是具有全局属性的,创建子进程后,子进程会默认继承父进程的环境变量。现在我们实现的这个简易的shell是系统的bash的子进程,所以它会继承bash的全部环境变量,因此,我们在自己的shell中仍然可以访问和使用bash中的环境变量。
再实现一个程序用来遍历环境变量
makefile文件
在我们实现的shell中去运行这个程序
可以看到它能打印出来系统中的环境变量。
那么此时我们想要在自己的shell中新增新的环境变量该如何做到呢?
我们之前学习过export,可以利用它导出环境变量,这样行不行呢?
答案是不行,因为export在这里是一个内建命令,我们可以用type命令查看
接下来换个方法:putenv()接口
它是 C 语言标准库中的一个函数,用于修改环境变量。这个函数允许程序在运行时改变或设置环境变量,这样这些变量就可以被同一进程中的其他部分或者其他由该进程启动的子进程所使用。
参数 string
是一个指向字符串的指针,该字符串的形式为 name=value
,其中 name
是环境变量的名字,而 value
是要赋予该环境变量的值。如果 value
是空字符串,则此调用将删除名为 name
的环境变量。
- 如果
name
指定的环境变量已经存在,那么它的值会被更新为value
。 - 如果
name
指定的环境变量不存在,那么新的环境变量会被创建,并且其值设为value
。 - 如果
value
是空字符串,那么对应的环境变量会被移除。
下面进行验证
可以看到添加成功。
七、取掉测试功能的完整代码