农妇山泉一亩田 发表于 2024-8-16 13:04:43

linux进程篇总结——实战——自定义shell

        前言:颠末过去两章十二篇文章的学习,我们已经知道了进程的基本概念以及进程的控制方法。 本篇内容就是使用过去学习的内容本身写一个功能简单的shell外壳程序, 也就是我们使用的bash命令行。 本篇内容是过去进程知识的集大成者。 我们在这个实战程序中, 将过去学过的进程控制方法——创建、等待、退出、终止,各种概念——PID、优先级、子进程、情况变量、命令行参数等等都会回忆起来, 复习起来。
        ps:本节内容很综合, 难度很大。须要学完进程的概念以及进程控制的方法。建议学习前先去复习一遍进程概念相关知识点。
目录
shell是什么        
实现过程
交互板块
命令行的外表
办理输入问题:
剖析字符串
平凡命令执行 
内建命令 
cd命令
ls颜色问题 
export 
echo

shell是什么        

        shell是外壳程序, 也就是说bash, shell本质上也是一个进程。 而在执行命令的时候, 对于平凡命令本质上就是本身创建子进程执行的。而对于内建命令, bash命令其实是直接在本身上面调用的一个函数。 我们看到的bash命令行, 其实就是一个输出的字符串。 这个字符串是由用户名 + 主机名 + 当前路径组成的。 
        我们本身制作的shell主要分为几个板块——用户交互获取命令行、分解命令行参数、内建命令执行、平凡命令执行
实现过程

交互板块

命令行的外表

用户名, 主机名,当前路径都生存在情况变量中。 
https://i-blog.csdnimg.cn/direct/e881cf9f15b64432b9034e4ca3de7c3a.png
        那么, 我们就可以封装函数, 获取三个情况变量——一个是用户名、一个是主机名、一个是当前路径
https://i-blog.csdnimg.cn/direct/86cd9b4d54784b608639321d1901bdd4.png
主函数部分我们这里只写一下输出, 先将命令行的外表打印出来:
https://i-blog.csdnimg.cn/direct/410a63d33049428c8916a3f0c1cc3dd7.png
上图的三个黄框框是博主定义的宏, LEFT是打印"[", RIGHT是打印"]"。
然后对于平凡用户最后一个字符一样平常都是$符号, 那么我们就定义一个LABLE的宏, 让这个宏替换$。如下图:
https://i-blog.csdnimg.cn/direct/b02806ded6d341c1a44564eb9184af6c.png
此时完备的代码如下:
https://i-blog.csdnimg.cn/direct/87746d3bcd624da69b9274bff6e2a817.png
此时运行结果就能打印一个属于我们本身的命令行:
https://i-blog.csdnimg.cn/direct/154cdca89bbe4ba79b53401f46ed4396.png
办理输入问题:

但是不能输入, 接下来我们就办理输入问题。我们可以定义一个字符串,这个字符串的长度是1024。
https://i-blog.csdnimg.cn/direct/d740945d714d4bbd81725117d6db37ca.png
在主函数这里添加一个scanf: 
https://i-blog.csdnimg.cn/direct/352b7b26696e4079b2729aaf785fe132.png
要留意此时黄框框这里没有\n, 不然输入参数会多打印一个换行。 此时的运行结果如下, 可以输入了:
https://i-blog.csdnimg.cn/direct/052e7c455dbf456d865c7e48744c9e3c.png
           但是此时只能输入, 这些输入的字符不能全部生存到commendline数组中。 因为scanf碰到空格会终止读取。 所以就用到了另一个输入函数——fgets , 如下图:
https://i-blog.csdnimg.cn/direct/17b2e9007ea449e182128ee49ed3a032.png
        fgets中, 第一个参数是要拷贝到的起始地址, 第二个参数是要拷贝的字节数, 第三个是从哪个文件读取。 我们是从表现器读取, 也就是stdin。
        上图的s用来接收fgets的返回值, fgets的返回值是读取乐成后的地址, 假如读取不乐成就是返回NULL。
        下图是运行结果:
https://i-blog.csdnimg.cn/direct/06b24c48b21e45a5879c762a692111fe.png
            上面我们打印之所以多了一个换行, 是因为我们在输入的时候回车符被读取进去了。 也就是相称于printf那边有一个我们本身加的换行, 然后commendline最后一个字符也是一个换行。 commendline内里的换行是我们不想要的, 请问我们怎么去掉呢?——既然回车符是最后一个字符, 那么我们就把最后一个字符酿成\0。如下图:
https://i-blog.csdnimg.cn/direct/b95c813a400548e0bb32190e4ebe1dd0.png
        下面是打印结果:
https://i-blog.csdnimg.cn/direct/9899408af9e040fa87ddc825b4757d8e.png
        如今, 我们将我们上面写的命令行的外表, 命令行的获取这些与用户交互的操作独自封装成一个函数——Interact。 
https://i-blog.csdnimg.cn/direct/5e29fa1024c94aaa8a295ba667436c29.png
以上, 我们就实现了与用户的交互,获取命令行。 大体就是这样, 但是后续可能随着我们其他板块的增长, 修改内里的部分代码。 但不会影响大思路。
剖析字符串

在正式剖析字符串之前我们先把多次扣问的问题办理。 因为我们的shell命令行是一直存在的, 不应该是使用一次后就退出。 这里我们要给代码套上一层循环。 ——即在我们要实现的逻辑:交互、剖析、执行的外面套上一层死循环。这样就能让逻辑循环起来了。https://i-blog.csdnimg.cn/direct/de5f06d68977416990671267cfd1a5f9.png
        运行情况就是如下,程序正常情况下已经不会退出了。(ctrl + C可以退出) 
https://i-blog.csdnimg.cn/direct/56a589f10ae24edfa95463074d94938e.png 
  接下来正式对命令行进行剖析:
下面是剖析的原理:
https://i-blog.csdnimg.cn/direct/acf452771a2643908237a3ec62e9905d.pngstrtok函数如下:
https://i-blog.csdnimg.cn/direct/8af4cd79cce74c43929d966db98499a1.png
 这里我们先定义一个分隔符的宏:
 https://i-blog.csdnimg.cn/direct/d1fa81fc35d14bacb86708ed4e68689d.png
然后创建一个数组, 用来存储切割好的字符串。 
https://i-blog.csdnimg.cn/direct/6c23bfa7a6894a0694ead6c82a187064.png
然后我们就实现截取字串, 将字串放到argv中, 放进去后就i++, 比及截取不了了就返回一个空串。 但是strtok第一次截取的时候可以传参commendline, 假如之后还是截取这个字符串, 那么就传NULL,我们把剖析字符串也单独封装成一个函数, 返回值是一个argc, 也就是截取出来的字符串的个数。 如下图:
https://i-blog.csdnimg.cn/direct/ba404339ab594675b987cb6c9cc5603d.png
写好之后, 我们就在主函数上放上测试代码:
https://i-blog.csdnimg.cn/direct/f75a3dafe09b46e4916b3a2100419b08.png
如下就是分解后的结果:
https://i-blog.csdnimg.cn/direct/2c7d41912fa441768c50283cac0752b8.png

平凡命令执行 

        然后就是执行命令,当id == 0的时候执行。 
        这个时候, 假如execvpe加载乐成, 就执行加载的程序假如加载失败, 就直接退出。并且退出码EXIT_CODE, 这个是我们本身定义的宏。
https://i-blog.csdnimg.cn/direct/0159a77595434cc1b9e12cc13ba63a29.png
https://i-blog.csdnimg.cn/direct/9deb3a1c288b4f5f9c66ec47c40cb4b4.png
我们这里先定义一个lastcode获取状态信息:
https://i-blog.csdnimg.cn/direct/6b45c17699a64d0bbbe74ca328d638ef.png
        下面是执行平凡命令的代码, environ是获取情况变量, 然后创建子进程, 假如id小于0的时候, 分析子进程创建失败, 那么直接返回。 假如id == 0, 分析是子进程,就加载程序。 我们的argv是数组, 所以带v, 但是没有绝对路径, 只有文件名, 所以要有默认搜索路径, 也就是加p。 另外还要有情况变量, 加上e。         
        假如是id > 0就分析是父进程, 父进程要等待子进程, 也就是下图的代码等待子进程。
https://i-blog.csdnimg.cn/direct/a9d27151676044b288521e3ebe4c982d.png
然后, 我们运行我们的程序就可以执行一样平常的命令了。
https://i-blog.csdnimg.cn/direct/a7530180d7bf47a99a28884d0f5dbbf2.png
内建命令 

cd命令

       但是, 这里有一个问题, 就是我们使用cd的时候, 我们会发现, 我们的工作目录不发生变化。这个是因为cd是一个内建命令!!!cd不是由bash的子进程加载得来的。就如同下图:
https://i-blog.csdnimg.cn/direct/44400f10b7a9430590844ccb3269e220.png
为什么会这样呢? 因为我们使用的fork创建的子进程。 子进程数据的修改不会改变父进程。 所以我们使用cd命令的时候, 固然加载了cd命令, 但是子进程使用cd命令, 子进程工作路径修改, 不会影响父进程。 
        那么正常使用cd命令, 就不能创建子进程, 我们可以使用chdir。 如今是内建命令的处置惩罚:
        对于这些内建命令, 我们的办理方式是对于这些内建命令一个一个地做特别处置惩罚。 起首, 为了方便维护, 我们同样将内建命令的执行封装成一个函数:
https://i-blog.csdnimg.cn/direct/5ef460d4aa2148d5ad95eb8d456124ff.png
函数要带参数argc, 我们的代码都是在这个函数中完成。
内建函数创建后, 我们的大体逻辑就搭建好了, 如今来看一下主函数的逻辑——先交互, 再剖析, 再执行:
https://i-blog.csdnimg.cn/direct/313ac2a44c8a40d5b1a5a31cf39d78d9.png
回到cd上来,起首chdir可以修改路径。 chdir可以修改当前的工作路径。但是我们也要获取当前路径用来修改PWD情况变量。
这里我们可以使用getcwd, 先创建一个pwd字符数组, 用来生存当前工作路径:
https://i-blog.csdnimg.cn/direct/00ffa77eebbf4fbc8a4b439deba7e496.png
然后将我们原先定义的pwd修改成下图:、
https://i-blog.csdnimg.cn/direct/620fb815056e4737ab60ab2fe719c6c2.png
既然获取当前路径的代码变了, 我们上面写的代码中, 有些地方也要改, 起首是交互函数的修改, 下图的红框框是getpwd的使用, 黄框框是工作路径的获取:
https://i-blog.csdnimg.cn/direct/fac3a583316b40ab9404c5ee07774679.png
然后是内建函数的修L
https://i-blog.csdnimg.cn/direct/da280f4950da452883f54909286fa6c8.png
此时, 我们的cd就能跑了:
https://i-blog.csdnimg.cn/direct/4655ca9abf2046e1b1d6f527fbd7ac96.png
ls颜色问题 

处置惩罚颜色问题, 须要在最后加一个--color选项。 假如命令是ls, 那么就要处置惩罚一下, 也就是再最后加一个--color选项。 最好的是在命令行剖析的函数里进行处置惩罚。 但是在内里处置惩罚会有argc的返回值问题。 所以为了方便, 博主这里放在了内建命令执行的函数里, 因为内建命令执行函数在平凡命令执行函数之前, 并且博主的内建命令也有argc, 方便控制。 
https://i-blog.csdnimg.cn/direct/3bc88da1a0f14af3805557ab44f62198.png
当我们再运行时, 就能看到ls的颜色了:
https://i-blog.csdnimg.cn/direct/3803fd989ed44422bfba96b88e660feb.png
写到这里的时候, 博主发现了一个问题, 就是我们可以给内建命令一个返回值, 只要返回真, 分析执行了内建命令, 假就没有执行。 这样就能防止又执行了内建, 又执行了平凡。
https://i-blog.csdnimg.cn/direct/a6fac587fa92404698f237d2a4696ba2.png
https://i-blog.csdnimg.cn/direct/93c16bff330e4424a91b15aebe7d6dce.png
export 

如今看一下新建情况变量export:
假如我们不做特别处置惩罚, export创建情况变量, 是创建不出来的。因为我们直接使用export, 那就是创建子进程, 然后加载到子进程帮我们执行,然而子进程不会影响父进程。 所以就没有效处。如下图就是创建不出的例子:
https://i-blog.csdnimg.cn/direct/18fa5a76fcd345cd86d17501e74d1c38.png
https://i-blog.csdnimg.cn/direct/5c918aeb737a4ff08dca923e1f35630a.png        所以, 这里就须要将export也当作内建命令特别处置惩罚——使用putenv在当进步程导入情况变量。 但是由于putenv导入情况变量只是修改情况变量的指针指向传给的参数指向的空间。也就是相称于一个浅拷贝。 假如我们直接给putenv传argv, 那么情况变量的指针指向putenv指向字符串, 当这个字符串被覆盖时情况变量就变了!!所以我们要先malloc一块新空间。 再将数据拷贝到这个空间。 让情况变量指向这块创建的malloc空间。
https://i-blog.csdnimg.cn/direct/ea029164c94e45d4be307ad60bdf9267.png
         当进步程导入情况变量。 那么就能使用我们本身的shell导入情况变量了。
https://i-blog.csdnimg.cn/direct/b29d8bb6d81c42e9a327e220b885b3d3.png
https://i-blog.csdnimg.cn/direct/1d19dde3938748edb32f44f4929682e0.png

echo

        对于echo也要做一下特别处置惩罚。 因为一样平常情况下echo会打印正常, 但是对于情况变量来说, 它就会直接打印情况变量
https://i-blog.csdnimg.cn/direct/31b299f495864cf9ad5cd46a785dd853.png
        处置惩罚方式就是做一个特别判定, 假如argv的第一个字符时$那么就按照情况变量打印:
        如下图红框框处是做一下特别判定, 防止发生段错误。 
https://i-blog.csdnimg.cn/direct/80e0dd491d774d3caafea6499b385f83.png
        这样就能把情况变量打出来:
https://i-blog.csdnimg.cn/direct/5c83ccc770724647b6eed303601fd80f.png
        但是还不可, 因为echo可能打印$, 也就是打印最后一次退出码。 
        那么就要再进行一次特别处置惩罚:
https://i-blog.csdnimg.cn/direct/c90fe9820ef0414199ea310de0843bd0.png
lastcode内里生存了退出码, 当执行了一次echo $?后要把lastcode置为0
https://i-blog.csdnimg.cn/direct/6d81d155d45147ada8e8a035d3413999.png
当我们当进行登录的时候, 我们的系统就是要启动一个shell进程。 我们shell本身的情况变量表是从哪里来的??——是在当前用户的家目录下, 有一个叫做bash_profile 大概 bashrc的文件。 这内里就有各种各样的文件。
https://i-blog.csdnimg.cn/direct/004dabde73044b2983f3ae3e2ee7d0a7.png
        当用户登录的时候, shell会读取用户目录下的.bash文件, 内里生存了导入情况变量的方式!!!
        假如我们想要本身导入这种默认的情况变量, 那么我们就要和标准的shell一样, 创建一个情况变量表。 然后本身创建一个情况变量的设置文件。将这些情况变量读入情况变量表当中!!!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: linux进程篇总结——实战——自定义shell