当前位置:   article > 正文

Linux——详细模拟实现shell(进程控制综合运用)_综合利用进程控制的相关知识,结合对shell功能的和进程间通信手段的认知,编写简易s

综合利用进程控制的相关知识,结合对shell功能的和进程间通信手段的认知,编写简易s

在运行linux时,我们总免不了需要输入各种指令让shell进行解析,从而与系统进行交互。

那么我们有没有可能自己自制一个简易的shell呢?

答案是当然没问题。

目录

一.大体思路

二.具体实现

(一).搭建shell框架

①打印命令行输入提示符

②接收命令行参数

(二).解析命令行参数

(三).子进程完成命令,父进程接收

(四).特殊处理(颜色显示,路径切换cd,export添加环境变量)

①ll指令和颜色显示

②路径切换cd

③export添加环境变量 

三.完整代码


一.大体思路

进程控制篇章中,我们知道了可以通过进程替换来间接运行系统命令。还不懂的小伙伴推荐看一下这篇博客:Linux——进程控制之替换

 那么思路就出来了,我们通过进程替换execvp把每次输入的指令加载成一个进程不就OK了么。

因为指令允许无限次输入,所以shell一定是死循环

又因为进程替换成功不会返回原有进程,我们需要一个子进程来执行每次具体的系统命令,父进程来完成shell的“轮回”——当子进程(系统命令)执行完毕后父进程重开一个新shell接收新指令

有了思路就可以进一步实践了。

二.具体实现

(一).搭建shell框架

首先,我们搭建的shell框架是死循环的,因此shell是在while(1)循环内部。

其次,每次循环开始时,我们需要显示命令行输入提示符和接收输入命令。所谓输入提示符就是类似这样的东东:

 

 之后fork创建父子进程来完成各自工作。

所以大体框架如下:

①打印命令行输入提示符

这里我们需要注意,shell本身的命令行输入是在提示符之后,而不是第二行,因此我们在打印的时候不能书写换行符。但如果只是单纯的cout并不能在电脑上显示提示,因为此时打印内容还在缓冲区内,因此,我们需要fflush(stdout)来清除输出缓冲区

②接收命令行参数

这里我们不能用单纯的cin来接收,因为我们输入的命令经常是带有参数的,这中间需要空格隔开。但是cin本身遇到空格后就会停止读取,因此,我们可以使用gets/fgets/getline来完成命令接收。有兴趣的小伙伴可以 看一下这篇博客:getline函数介绍 

 之后就可以开始搭建框架了:

  1. while(1)
  2. {
  3. //打印命令输入提示符
  4. cout << "[myshell@CDL~]";
  5. fflush(stdout);
  6. //输入命令
  7. char* str = new char[128];
  8. gets(str);
  9. ...//命令行解析
  10. pid_t id = fork();//创建子进程
  11. if(id == 0)
  12. {
  13. ...
  14. }
  15. else{
  16. ...
  17. }
  18. }

(二).解析命令行参数

所谓解析命令行参数就是把它由一个字符串变成一个字符串数组。第一个是指令,后面是参数,最后是NULL。

至于为什么要变换,是因为我们需要用execvp来进行进程替换。(我们又不知道具体有多少个参数,因此无法用execlp完成)。

那么工作内容也就明了了——将char*变成char* []

这里我们可以使用strtok函数完成字符串的剪切任务。

每当遇见空格时剪切即可,当然小编我还是贴心的贴上strtok的使用方式:

值得注意的是,如果后续的剪切还是来自之前的字符串,char *str输入NULL即可。

当后续剪切中走到\0时会返回NULL。

 那么我们的代码也就出来了:

  1. //可以专门定义一个函数用于字符串解析
  2. void GetVector(char* str)
  3. {
  4. //char* argv[size];argv就是字符串数组
  5. int i = 0;
  6. argv[i++] = strtok(str, " ");
  7. //剪切str字符串,直到argv接收到NULL为止
  8. while(argv[i++] = strtok(NULL, " ")) {
  9. }
  10. }

(三).子进程完成命令,父进程接收

这一部分就一目了然了,我们fork子进程后将它用execvp来替换成我们所希望的系统命令(进程)。父进程进行等待,当收到系统命令执行完毕后的信号后,开启新一轮循环。

代码如下:

  1. while(1)
  2. {
  3. ...
  4. pid_t id = fork();
  5. if(id == 0)//子进程
  6. {
  7. execvp(argv[0], argv);
  8. exit(-1);//如果走到这里说明进程替换失败
  9. }
  10. else if(id > 0)//父进程
  11. {
  12. waitpid(id, NULL, 0);//等待子进程
  13. }
  14. else exit(-1);//进程创建失败
  15. }

(四).特殊处理(颜色显示,路径切换cd,export添加环境变量)

我们按上述虽然可以制作简易的shell但是有些命令还不能完成或很好完成。

①ll指令和颜色显示

ll指令我们无法直接用进程替换得到,那么可以识别到我们输入ll后将它替换成ls -l即可。

还有我们输入的ls指令是无色的,但是真正的shell是有颜色的,这个只需要我们在命令行参数中再加入"--color=auto"即可

代码如下:

  1. void GetVector(char* str)
  2. {
  3. //int i = 0;
  4. //argv[i++] = strtok(str, " ");
  5. if(strcmp(argv[0], "ls") == 0)
  6. {
  7. argv[i++] = "--color=auto";
  8. }
  9. if(strcmp(argv[0], "ll") == 0)
  10. {
  11. argv[0] = "ls";
  12. argv[i++] = "-l";
  13. argv[i++] = "--color=auto";
  14. }
  15. //while(argv[i++] = strtok(NULL, " ")) { }
  16. }

②路径切换cd

路径切换指令我们无法用子进程来完成。因为虽然子进程确实进行了路径切换,但是在切换后进程就终止了,当再创建子进程时还是继承自父进程所在的原路径,相当于路径没变。因此我们想要改变路径就要让父进程改变路径,但是父进程又不能够用进程替换,那么就可以使用chdir()函数完成。

思路也很简单,当检测到指令是cd时,直接让进程(父进程)调用chdir()函数,略过fork子进程,continue开始新一轮循环。

代码如下:

  1. while(1)
  2. {
  3. //...
  4. if(strcmp(argv[0], "cd") == 0)
  5. {
  6. if(argv[1] != NULL)
  7. chdir(argv[1]);
  8. continue;
  9. }
  10. //pid_t id = fork()
  11. //...
  12. }

 

③export添加环境变量 

对于环境变量而言,与路径同理,需要在父进程中进行改变。因为子进程继承自父进程,单纯在子进程中改变毫无意义。

这里我们就需要引入一个函数接口putenv()

在该接口中传入环境变量名和值即可在当前进程中添加环境变量。

 因此我们很容易写出下面代码:

  1. while(1)
  2. {
  3. //...
  4. if(strcmp(argv[0], "export") == 0 && argv[1] != NULL)
  5. {
  6. putenv(argv[1]);
  7. continue;
  8. }
  9. //pid_t id = fork()
  10. //...
  11. }

 但是,这样子进程根本无法获取新环境变量!

原因很简单,putenv所获取环境变量,其实是传入环境变量存储空间的地址,也就是argv[1]的地址,本质上,子进程所继承的环境变量其实是地址。但是当重新创建子进程时,argv会重新获得命令行参数,子进程去继承时,argv[1]地址空间内的数据已经发生改变,也就无法获取环境变量了

因此,我们需要专门创建一片空间用以记录新添加的环境变量:

  1. int main()
  2. {
  3. char* my_env[64];
  4. int envi = 0;
  5. while(1)
  6. {
  7. //...
  8. if(strcmp(argv[0], "export") == 0 && argv[1] != NULL)
  9. {
  10. my_env[envi] = new char[strlen(argv[1]) + 1];
  11. strcpy(my_env[envi], argv[1]);
  12. putenv(my_env[envi++]);
  13. continue;
  14. }
  15. //pid_t id = fork()
  16. //...
  17. }
  18. return 0;
  19. }

 

三.完整代码

  1. //...头文件
  2. char* argv[64];//也可设成非全局变量
  3. void GetVector(char* str)
  4. {
  5. int i = 0;
  6. argv[i++] = strtok(str, " ");
  7. if(strcmp(argv[0], "ls") == 0)
  8. {
  9. argv[i++] = "--color=auto";
  10. }
  11. if(strcmp(argv[0], "ll") == 0)
  12. {
  13. argv[0] = "ls";
  14. argv[i++] = "-l";
  15. argv[i++] = "--color=auto";
  16. }
  17. while(argv[i++] = strtok(NULL, " ")) {
  18. }
  19. }
  20. int main()
  21. {
  22. char* my_env[64];
  23. int envi = 0;
  24. while(1)
  25. {
  26. cout << "[myshell@CDL~]";
  27. fflush(stdout);
  28. char* str = new char[128];
  29. gets(str);
  30. GetVector(str);
  31. if(strcmp(argv[0], "export") == 0 && argv[1] != NULL)
  32. {
  33. my_env[envi] = new char[strlen(argv[1]) + 1];
  34. strcpy(my_env[envi], argv[1]);
  35. putenv(my_env[envi++]);
  36. continue;
  37. }
  38. if(strcmp(argv[0], "cd") == 0)
  39. {
  40. chdir(argv[1]);
  41. continue;
  42. }
  43. pid_t id = fork();
  44. if(id == 0)
  45. {
  46. execvp(argv[0], argv);
  47. exit(-1);
  48. }
  49. else{
  50. waitpid(id, NULL, 0);
  51. }
  52. }
  53. return 0;
  54. }

百分之八十的成功只是出席——Woody Allen


如有错误,敬请斧正 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/577586
推荐阅读
相关标签
  

闽ICP备14008679号