赞
踩
在运行linux时,我们总免不了需要输入各种指令让shell进行解析,从而与系统进行交互。
那么我们有没有可能自己自制一个简易的shell呢?
答案是当然没问题。
目录
(四).特殊处理(颜色显示,路径切换cd,export添加环境变量)
在进程控制篇章中,我们知道了可以通过进程替换来间接运行系统命令。还不懂的小伙伴推荐看一下这篇博客:Linux——进程控制之替换
那么思路就出来了,我们通过进程替换execvp把每次输入的指令加载成一个进程不就OK了么。
因为指令允许无限次输入,所以shell一定是死循环。
又因为进程替换成功不会返回原有进程,我们需要一个子进程来执行每次具体的系统命令,父进程来完成shell的“轮回”——当子进程(系统命令)执行完毕后父进程重开一个新shell接收新指令。
有了思路就可以进一步实践了。
首先,我们搭建的shell框架是死循环的,因此shell是在while(1)循环内部。
其次,每次循环开始时,我们需要显示命令行输入提示符和接收输入命令。所谓输入提示符就是类似这样的东东:
之后fork创建父子进程来完成各自工作。
所以大体框架如下:
这里我们需要注意,shell本身的命令行输入是在提示符之后,而不是第二行,因此我们在打印的时候不能书写换行符。但如果只是单纯的cout并不能在电脑上显示提示,因为此时打印内容还在缓冲区内,因此,我们需要fflush(stdout)来清除输出缓冲区。
这里我们不能用单纯的cin来接收,因为我们输入的命令经常是带有参数的,这中间需要空格隔开。但是cin本身遇到空格后就会停止读取,因此,我们可以使用gets/fgets/getline来完成命令接收。有兴趣的小伙伴可以 看一下这篇博客:getline函数介绍
之后就可以开始搭建框架了:
- while(1)
- {
- //打印命令输入提示符
- cout << "[myshell@CDL~]";
- fflush(stdout);
- //输入命令
- char* str = new char[128];
- gets(str);
- ...//命令行解析
- pid_t id = fork();//创建子进程
- if(id == 0)
- {
- ...
- }
- else{
- ...
- }
- }
所谓解析命令行参数就是把它由一个字符串变成一个字符串数组。第一个是指令,后面是参数,最后是NULL。
至于为什么要变换,是因为我们需要用execvp来进行进程替换。(我们又不知道具体有多少个参数,因此无法用execlp完成)。
那么工作内容也就明了了——将char*变成char* []。
这里我们可以使用strtok函数完成字符串的剪切任务。
每当遇见空格时剪切即可,当然小编我还是贴心的贴上strtok的使用方式:
值得注意的是,如果后续的剪切还是来自之前的字符串,char *str输入NULL即可。
当后续剪切中走到\0时会返回NULL。
那么我们的代码也就出来了:
- //可以专门定义一个函数用于字符串解析
- void GetVector(char* str)
- {
- //char* argv[size];argv就是字符串数组
- int i = 0;
- argv[i++] = strtok(str, " ");
-
- //剪切str字符串,直到argv接收到NULL为止
- while(argv[i++] = strtok(NULL, " ")) {
- }
-
- }
这一部分就一目了然了,我们fork子进程后将它用execvp来替换成我们所希望的系统命令(进程)。父进程进行等待,当收到系统命令执行完毕后的信号后,开启新一轮循环。
代码如下:
- while(1)
- {
- ...
-
- pid_t id = fork();
- if(id == 0)//子进程
- {
- execvp(argv[0], argv);
- exit(-1);//如果走到这里说明进程替换失败
- }
- else if(id > 0)//父进程
- {
- waitpid(id, NULL, 0);//等待子进程
- }
- else exit(-1);//进程创建失败
- }
我们按上述虽然可以制作简易的shell但是有些命令还不能完成或很好完成。
ll指令我们无法直接用进程替换得到,那么可以识别到我们输入ll后将它替换成ls -l即可。
还有我们输入的ls指令是无色的,但是真正的shell是有颜色的,这个只需要我们在命令行参数中再加入"--color=auto"即可
代码如下:
- void GetVector(char* str)
- {
- //int i = 0;
- //argv[i++] = strtok(str, " ");
-
- if(strcmp(argv[0], "ls") == 0)
- {
- argv[i++] = "--color=auto";
- }
- if(strcmp(argv[0], "ll") == 0)
- {
- argv[0] = "ls";
- argv[i++] = "-l";
- argv[i++] = "--color=auto";
- }
-
- //while(argv[i++] = strtok(NULL, " ")) { }
- }
路径切换指令我们无法用子进程来完成。因为虽然子进程确实进行了路径切换,但是在切换后进程就终止了,当再创建子进程时还是继承自父进程所在的原路径,相当于路径没变。因此我们想要改变路径就要让父进程改变路径,但是父进程又不能够用进程替换,那么就可以使用chdir()函数完成。
思路也很简单,当检测到指令是cd时,直接让进程(父进程)调用chdir()函数,略过fork子进程,continue开始新一轮循环。
代码如下:
- while(1)
- {
- //...
- if(strcmp(argv[0], "cd") == 0)
- {
- if(argv[1] != NULL)
- chdir(argv[1]);
- continue;
- }
-
- //pid_t id = fork()
- //...
- }
对于环境变量而言,与路径同理,需要在父进程中进行改变。因为子进程继承自父进程,单纯在子进程中改变毫无意义。
这里我们就需要引入一个函数接口putenv():
在该接口中传入环境变量名和值即可在当前进程中添加环境变量。
因此我们很容易写出下面代码:
- while(1)
- {
- //...
- if(strcmp(argv[0], "export") == 0 && argv[1] != NULL)
- {
- putenv(argv[1]);
- continue;
- }
-
- //pid_t id = fork()
- //...
- }
但是,这样子进程根本无法获取新环境变量!
原因很简单,putenv所获取环境变量,其实是传入环境变量存储空间的地址,也就是argv[1]的地址,本质上,子进程所继承的环境变量其实是地址。但是当重新创建子进程时,argv会重新获得命令行参数,子进程去继承时,argv[1]地址空间内的数据已经发生改变,也就无法获取环境变量了。
因此,我们需要专门创建一片空间用以记录新添加的环境变量:
- int main()
- {
- char* my_env[64];
- int envi = 0;
- while(1)
- {
- //...
- if(strcmp(argv[0], "export") == 0 && argv[1] != NULL)
- {
- my_env[envi] = new char[strlen(argv[1]) + 1];
- strcpy(my_env[envi], argv[1]);
- putenv(my_env[envi++]);
- continue;
- }
-
- //pid_t id = fork()
- //...
- }
- return 0;
- }
- //...头文件
- char* argv[64];//也可设成非全局变量
- void GetVector(char* str)
- {
- int i = 0;
- argv[i++] = strtok(str, " ");
- if(strcmp(argv[0], "ls") == 0)
- {
- argv[i++] = "--color=auto";
- }
- if(strcmp(argv[0], "ll") == 0)
- {
- argv[0] = "ls";
- argv[i++] = "-l";
- argv[i++] = "--color=auto";
- }
- while(argv[i++] = strtok(NULL, " ")) {
- }
-
- }
- int main()
- {
- char* my_env[64];
- int envi = 0;
- while(1)
- {
- cout << "[myshell@CDL~]";
- fflush(stdout);
- char* str = new char[128];
- gets(str);
- GetVector(str);
- if(strcmp(argv[0], "export") == 0 && argv[1] != NULL)
- {
- my_env[envi] = new char[strlen(argv[1]) + 1];
- strcpy(my_env[envi], argv[1]);
- putenv(my_env[envi++]);
- continue;
- }
- if(strcmp(argv[0], "cd") == 0)
- {
- chdir(argv[1]);
- continue;
- }
- pid_t id = fork();
- if(id == 0)
- {
- execvp(argv[0], argv);
- exit(-1);
- }
- else{
- waitpid(id, NULL, 0);
- }
- }
- return 0;
- }
百分之八十的成功只是出席——Woody Allen
如有错误,敬请斧正
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。