网站建设视频百度网盘,电脑赚钱的项目有哪些,青岛大型网站建设,开发公司户型设计会议本章的代码可以访问这里获取。 由于程序代码是一体的#xff0c;本章在分开讲解各部分的实现时#xff0c;代码可能有些跳跃#xff0c;建议在讲解各部分实现后看一下源代码方便理解程序。 制作一个简单的Shell解释器 一、观察Shell的运行状态二、简单的Shell解释器制作原理… 本章的代码可以访问这里获取。 由于程序代码是一体的本章在分开讲解各部分的实现时代码可能有些跳跃建议在讲解各部分实现后看一下源代码方便理解程序。 制作一个简单的Shell解释器 一、观察Shell的运行状态二、简单的Shell解释器制作原理1、获取命令行2、解析命令行3、创建子进程 进行程序替换 父进程等待4、实际运行 二、对简单的内建命令进行处理1、给ls命令加上色彩2、支持cd命令3、支持export命令4、支持env命令5、支持echo命令 一、观察Shell的运行状态
我们想要制作一个简单的Shell解释器需要先观察Shell是怎么运行的根据Shell的运行状态我们再去进行模拟实现。
我们可以先考虑下面的指令与Shell的互动 我们仔细进行分析可以发现Shell执行上面的命令时可以被理解为下面的过程。 当然上面的命令都是普通命令所以Shell都是通过创建子进程的方式来执行的对于一些内建命令Shell自己去执行命令我们现在还不考虑在后面的部分我们再进行进一步的讨论内建命令应该怎么去处理。
二、简单的Shell解释器制作原理
通过观察Shell的运行状态我们知道然后Shell读取新的一行输入建立一个新的子进程在这个子进程中运行程序并等待这个进程结束。
所以要写一个shell需要循环以下过程:
获取命令行解析命令行建立一个子进程fork替换子进程execvp执行替换后的程序父进程等待子进程退出wait
1、获取命令行
我们在在Shell中输入的命令本质上就是输入一个字符串因此我们想要获取命令行可以先创建一个字符数组commandstr然后使用C语言的fgets函数从键盘中进行读取数据到字符数组里面这样我们就获取了一个命令行了。
注意 这里不能使用scanf函数 ,这里的命令会包含空格会导致scanf读取不到完整的数据。fgets函数会将我们输入的命令时的最后一个的\n符也给读取到字符数组内我们需要特殊处理将\n进行用\0进行覆盖 //这里包含的头文件是我们整个程序需要用到的所有头文件
#includestdio.h
#includeunistd.h
#includeassert.h
#includestring.h
#includesys/types.h
#includesys/wait.h
#includestdlib.h//这里的N用于定义字符数组的大小
#define N 128int main()
{//存储命令行的字符数组char commandstr[N] ;//Shell要一直运行接受命令所以这里必须是死循环while(1){//模拟Shell的提示符printf([hongmachine MiniShell]# );//从标准输入流中读取字符串char* s fgets(commandstr, sizeof(commandstr), stdin);assert(s); //判断fgets是否读取成功//处理\n 示例字符串ls -a -l\n\0commandstr[strlen(commandstr) - 1] \0;}
2、解析命令行
虽然我们通过前一步已经拿到命令行但是我们还不能直接使用因为我们拿到的字符串中间可能有许多空格以及一些其他的问题我们还需要将命令行的字符进行切割提取出我们想要的子串这样才符合程序替换函数的要求。例如将 ls -a -l提取成 ls ,-a , -l。
对于字符串的切割我们可以使用C语言提供的strtok函数由于切割以后我们的字符串从一个变成了多个因此我们需要用一个字符串指针数组argv存储每一部分切割后的首地址同时这个argv也可以直接传递给execvp函数进行程序替换了。
//在全局域中 定义切割符
#define SEP //main函数的外部 定义一个命令行切割函数
int split(char commandstr[], char* argv[])
{assert(commandstr);assert(argv);//第一次切割argv[0] strtok(commandstr, SEP);if(argv[0] NULL){//返回 -1表示异常退出return -1; }//循环切割int i 1;while((argv[i] strtok(NULL, SEP)));return 0;
}//main函数内部while循环上面定义切割后的字符指针数组
char* argv[N] {NULL};//while循环内部//切割字符串 例如将ls -a -l 变为 ls -a -lint n split(commandstr, argv);if(n -1){//切割失败就终止本次循环continue;}3、创建子进程 进行程序替换 父进程等待
创建子进程而我们可以使用fork函数进行创建创建完以后进程的执行流由一个变成了两个我们在子进程中进行程序替换可以使用execvp命令同时我们的argv[0]就是程序名argv中存储的就是命令按照什么方式进行执行。
最后我们的父进程可以在外面进行阻塞等待然后获取子进程的退出码和退出信息。
//main函数内部while循环上面定义退出码变量int last_status 0;//while循环内部//创建子进程进行命令处理pid_t id fork();assert(id 0);if(id 0){//child processexecvp(argv[0], argv); //如果执行到这里说明程序替换失败 exit(-1);}//父进程等待子进程int status;int pid waitpid(id, status, 0); //等待成功就提取退出码信息if(pid 0){last_status WEXITSTATUS(status);}}return 0;4、实际运行
我们可以执行 ls pwd ps -axj命令 看一看效果。
二、对简单的内建命令进行处理
我们知道内建命令是让Shell自己执行的命令而不是让子进程执行的命令,例如cd命令就是内建命令因为我们要改变的是Shell自己的工作目录而不是子进程的工作目录类似的命令还有export env echo命令。
由于上面我们写的程序执行命令时都是交给子进程去做的所以我们上面写的程序是没有办法执行内建命令的或者说能执行内建命令但不是我们想要的结果或目的。
所以接下来我们要对这个简单的Shell进行改造让它能够执行一些简单的内建命令还有刚刚我们的ls命令没有色彩我们也要进行一些修改。
1、给ls命令加上色彩
在真正的Shell中我们执行的ls命令其实是ls --colorautols被我们真正的Shell进行了起别名。 我们在运行我们自己制作的Shell时也可以加上--colorauto。
//此段代码应该在切割字符串之后//argv[0]就是我们的命令名
if(strcmp(argv[0], ls) 0){int pos 0;//寻找指针数组的结尾while(argv[pos]);//在NULL位置加上 --colorautoargv[pos - 1] --colorauto;//将后一个位置置空argv[pos] NULL;}这样以后我们在我们自己制作的Shell中执行ls命令时也会由颜色了
2、支持cd命令
对于cd命令如果让父进程进行执行我们可以调用系统调用chdir我们只需要传递一个参数路径字符串当执行成功时会返回0执行失败会返回-1并设置错误码。 //此段代码应该在ls添加颜色之后else if(strcmp(argv[0], cd) 0){//argv[1]里面存放的是路径字符串if(argv[1] NULL){printf(没有正确的路径\n);//设置错误码last_status -1;continue;}//执行系统调用改变父进程的工作目录chdir(argv[1]);continue;}3、支持export命令
export命令可以将一个本地变量加入到环境变量表中我们让我们自己制作的Shell完成expoprt命令可以用C语言提供的函数putenv函数但是在向环境变量表加入新的环境变量时我们要维护好我们加入到环境变量这个环境变量不能够被轻易的覆盖否则环境变量表在找我们的环境变量时就会找不到所以我们还要创建一个我们自己维护的二维数组。
//在全局域中定义
// 自己维护的二维数组最多能向环境变量表几个自定义的环境变量
#define MAX 64//main函数内部while循环上面定义
//指向下一个要添加的环境变量的位置int env_index 0;
//要维护的二维数组char envstr[MAX][N];//此段代码应该在ls添加颜色之后else if(strcmp(argv[0], export) 0){//声明putenv函数否则会编译器会有警告extern int putenv(char *string); //argv[1]位置应该是环境变量if(argv[1] NULL){printf(没有输入变量\n);last_status -1;continue;}//将argv[1]位置的环境变量拷贝到env_str中否则下一次解析的命令会覆盖环境变量strcpy(envstr[env_index], argv[1]);//将环境变量导入环境变量表putenv(envstr[env_index]);}4、支持env命令
对于env命令我们只需要写一个打印环境变量表的函数就能完成此命令了。
//main函数的外部 定义一个打印环境变量表的函数void showEnv()
{extern char** environ;int i 0;while(environ[i]){printf(%d : %s\n, i, environ[i]);}
}//此段代码应该在ls添加颜色之后else if(strcmp(argv[0], env) 0){showEnv();continue;}5、支持echo命令
echo命令可以用于打印环境变量也可以打印退出码这取决于$后面是不是?是?我们就可以打印last_status不是我们就用getenv命令拿到环境变量的内容。
//此段代码应该在ls添加颜色之后
else if(strcmp(argv[0], echo) 0){if(*argv[1] $){if(*(argv[1] 1) ?){printf(process exit code %d\n, last_status);continue;}else{char* str getenv(argv[1] 1);printf(%s\n,str);continue;}}}