本文主要是介绍demo xshell (程序替换 工作目录 内建命令),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1.程序替换
在学习完一些列的进程替换接口之后我们大概就能知道,我们的环境变量以及命令行参数是如何传递给子进程的,这些参数是我们在调用进程替换时就传给了子进程的数据。
那么如果我们自己要实现一个简单的命令行解释器,我们是不是首先就需要对命令行的参数进行解析? 命令行参数的解析我们首先需要获取一行字符串,然后以空格为间隔将字符串拆分为程序名和选项。
#include<stdio.h>#include<assert.h>#include<string.h>#include<stdlib.h>#include<unistd.h>#include<sys/types.h>#include<sys/wait.h>char stringArg[1024]; //获取命令行输入char* argv[32]; //拆解后的命令行参数数组int main(){stringArg[1023]=0;//printf("%s\n",getenv("HOST"));while(1){memset(stringArg,0,1024); //全部初始化\0 ,便于计算长度memset(argv,0,32*sizeof(char*));//全部初始化为NULL,命令行数组的结尾必须是NULL//打印一行提示信息 用户名@主机名 当前路径$printf("[%s@ %s %s]$ ",getenv("USER"),getenv("HOSTNAME"),getenv("PWD")); //不换行fflush(stdout); //将提示信息打印出来//获取字符串 要读空格,不能用 scanf ,使用fgetsfgets(stringArg,1023,stdin);stringArg[strlen(stringArg)-1]=0; //将最后的 \n 换成 \0 if(strlen(stringArg)==0){//什么也没输入continue;}//拆解字符串 strtokint i=0;argv[i++]=strtok(stringArg," ");while(argv[i++]=strtok(NULL," ")); //最后一次当字符串结束再使用strtok,会返回NULL,刚好作为循环结束条件#ifdef _DEBUG_ARGV//测试切割功能char**p=argv;while(*p)printf("%s ",*p++);printf("\n");#endif}return 0;}
切割字符串的工作可以直接使用 strtok 函数来完成。
完成切割字符串的工作之后,我们就可以将该命令行数组用于程序替换的参数了,这是最基础的shell。
//程序替换pid_t id=fork();if(id<0){perror("fork failed");exit(1);}if(id==0){//程序替换execvp(argv[0],argv);//如果替换失败则会执行下面的代码perror("execvp failed");exit(1);}//父进程等待回收僵尸int status=0;waitpid(id,&status,0);if(WIFEXITED(status))//正常退出{if(WEXITSTATUS(status))//退出码不为0 {printf("运行成功,退出码为 :%d\n", WEXITSTATUS(status));}}else//异常终止{printf("%s\n",strerror(errno));}
2.进程工作目录
在上面的代码实现下还有很多的小问题,比如我们使用cd命令的时候并不能切换目录,这是为什么呢?
要理解这个问题,首先我们需要知道一个概念:进程的工作目录
当我们启动一个进程时,我们打开进程目录 /proc,找到我们的进程,然后进到我们进程的目录中,我们能够看到两个特殊的东西
exe就是我们的二进制可执行程序的位置,这个我们很好理解,而cwd是一串路径,这就是进程的工作目录,当我们启动一个进程时,一般是我们在哪个路径下启动的这个进程,工作路径就是当前所在路径。我们可以在其他路径下降这个程序跑起来观察一下。
当我们在上一级目录下将该程序跑起来,进程的工作目录就变成了执行运行命令时所在的路径,这就是进程的工作路径。
那么进程工作路径可以修改吗?
在命令行中,我们的cd命令就是修改进程工作路径的,也就是我们的shell的工作路径,所以我们经常能够通过cd命令进入不同的路径,我们的环境变量PWD其实严格上来说就是当前进程工作路径。 而在程序中,我们可以使用 chdir 来更改工作目录,
到了这里,我们就能知道为什么我们的shell的cd命令不起作用了 ,因为我们是通过创建子进程然后进行程序替换执行的cd命令,那么修改的就是子进程的工作目录,而子进程工作目录被修改时,发生写时拷贝,不会影响父进程。而我们pwd查的是父进程的工作目录,所以我们的cd命令其实不应该让子进程执行,而是由我们的父进程自己执行,因为cd命令要修改的是我们当前进程也就是父进程的工作目录。
像这种不需要创建子进程来执行的,而是让shell自己执行的命令,叫做内建命令或者内置命令
比如我们前面提到的 echo 命令也是一个内建命令,这就是他为什么能够显示本地变量的原因,因为他不是通过创建子进程来执行的,而是shell自己执行。
如何模拟实现cd命令呢? 其实也很简单,在创建子进程之前判断一下 命令的第一个字串是否为 cd,如果是 cd ,我们就直接使用chdir来更换工作目录。同时我们还要判断一下是否切换成功。
//内建命令if(strcmp("cd",argv[0])==0) //cd{const char*changepath=argv[1];int ret=chdir(changepath);if(ret==-1){ printf("%s\n",strerror(errno));}continue; }
而如果是echo目录,我们也是直接在当前目录下执行,而是直接在父进程打印。但是echo还有一个问题,就是空格我们输入的内容,也需要打印,所以我们需要在切割第一个字串串之后判断是否为echo,如果为echo,后续就先不切割了,然后再判断是否有 $ 符号,也就是在stringArg[5]是否为$ ,如果为$ ,则需要去找我们的变量列表中的变量来打印。但是我们这里只支持环境变量就够了,其他的实现起来太复杂。
3.重定向
重定向的符号无非就是 > >> < ,而且不会出现在第一个子串上,所以只需要在将第一个基础命令切割出来之后,判断一下是否有重定向的符。判断完之后将重定向符号变为空格,以便切割。
同时,重定向只能在子进程中去替换 fd ,也就是在程序替换之前替换,防止父进程也被替换了
//判断是否有重定向int j=0;for(j=0;j<strlen(stringArg);++j){if(stringArg[j]=='<'){//输入重定向MODE=CHANGEIN;stringArg[j]='\0';filename=&stringArg[j+1];break;}else if(stringArg[j]=='>'){stringArg[j]='\0';if(stringArg[j+1]=='>'){j++;MODE=CHANGEAPPEND;stringArg[j]='\0';filename=&stringArg[j+1];break;}else{MODE=CHANGEOUT;filename=&stringArg[j+1];break;}}}
if(id==0){//先检查是否有重定向if(MODE==CHANGEOUT){int fd=open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666);dup2(fd,1);}if(MODE==CHANGEIN){int fd=open(filename,O_RDONLY);dup2(fd,0);}if(MODE==CHANGEAPPEND){int fd = open(filename,O_WRONLY|O_CREAT|O_APPEND,0666);dup2(fd,1);}//程序替换execvp(argv[0],argv);//如果替换失败则会执行下面的代码perror("execvp failed");exit(1);}
为什么要把重定向符号设置为 \0 呢?因为我们不想要在切割字串的时候还将后面的文件名也切割进去,我们默认这些符号后面就是目标的文件名了。
完整代码
#include<stdio.h>2 #include<assert.h>3 #include<string.h>4 #include<stdlib.h>5 #include<unistd.h>6 #include<sys/types.h>7 #include<sys/wait.h>8 #include<errno.h>9 #include<sys/stat.h>10 #include<fcntl.h>11 12 13 char stringArg[1024]; //获取命令行输入14 char* argv[32]; //拆解后的命令行参数数组15 #define CHANGEOUT 1 //输出重定向16 #define CHANGEIN 2 //输入重定向17 #define CHANGEAPPEND 4 //追加重定向18 char*filename; //重定向的目标文件19 int MODE; //记录是否重定向20 21 22 int main()23 {24 stringArg[1023]=0;25 //printf("%s\n",getenv("HOST"));26 27 while(1)28 { 29 //重置errno30 errno=0;31 MODE =0 ;32 33 //获取字符串提取命令行34 35 memset(stringArg,0,1024); //全部初始化\0 ,便于计算长度36 memset(argv,0,32*sizeof(char*));37 //打印一行提示信息 用户名@主机名 当前路径$38 printf("[%s@ %s %s]$ ",getenv("USER"),getenv("HOSTNAME"),getenv("PWD")); //不换行39 fflush(stdout); //将提示信息打印出来40 //获取字符串 要读空格,不能用 scanf ,使用fgets41 fgets(stringArg,1023,stdin);42 stringArg[strlen(stringArg)-1]=0; //将最后的 \n 换成 \0 43 if(strlen(stringArg)==0)44 {45 //什么也没输入46 continue;47 }48 49 //判断是否有重定向50 int j=0; for(j=0;j<strlen(stringArg);++j) 52 {53 if(stringArg[j]=='<')54 {55 //输入重定向56 MODE=CHANGEIN;57 stringArg[j]='\0';58 filename=&stringArg[j+1];59 break;60 }61 else if(stringArg[j]=='>')62 {63 stringArg[j]='\0';64 if(stringArg[j+1]=='>')65 {66 j++;67 MODE=CHANGEAPPEND;68 stringArg[j]='\0';69 filename=&stringArg[j+1];70 break;71 }72 else73 {74 MODE=CHANGEOUT;75 filename=&stringArg[j+1];76 break;77 }78 }79 }80 81 //拆解字符串 strtok82 int i=0;83 argv[i++]=strtok(stringArg," ");84 if(strcmp("echo",argv[0])==0&&MODE==0) //echo85 {86 //检查$87 if(stringArg[5]=='$')88 {89 //只考虑环境变量90 printf("%s\n",getenv(&stringArg[6]));91 }92 else93 printf("%s\n",&stringArg[5]);94 continue;95 }
W> 96 while(argv[i++]=strtok(NULL," ")); //最后一次当字符串结束再使用strtok,会返回NULL,刚好作为结束条件以及命令行参数数组的结尾NULL97 98 99 //内建命令100 if(strcmp("cd",argv[0])==0) //cd{102 const char*changepath=argv[1]; 103 int ret=chdir(changepath);104 if(ret==-1)105 {106 printf("%s\n",strerror(errno));107 }108 continue; 109 110 }111 112 //程序替换113 else114 {115 pid_t id=fork();116 117 if(id<0)118 {119 perror("fork failed");120 exit(1);121 }122 if(id==0)123 {124 //先检查是否有重定向125 if(MODE==CHANGEOUT)126 {127 int fd=open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666);128 dup2(fd,1);129 }130 if(MODE==CHANGEIN)131 {132 int fd=open(filename,O_RDONLY);133 dup2(fd,0);134 }135 if(MODE==CHANGEAPPEND)136 {137 int fd = open(filename,O_WRONLY|O_CREAT|O_APPEND,0666);138 dup2(fd,1);139 }140 //程序替换141 execvp(argv[0],argv);142 //如果替换失败则会执行下面的代码143 perror("execvp failed");144 exit(1);145 }146 147 //父进程等待回收僵尸148 int status=0;149 waitpid(id,&status,0);150 if(WIFEXITED(status))//正常退出{152 if(WEXITSTATUS(status))//退出码不为0 153 {154 printf("运行成功,退出码为 :%d\n", WEXITSTATUS(status));155 }156 }157 else//异常终止158 {159 printf("%s\n",strerror(errno));160 }161 }162 163 164 #ifdef _DEBUG_ARGV//测试切割功能165 char**p=argv;166 while(*p)167 printf("%s ",*p++);168 printf("\n");169 #endif170 }171 172 173 return 0;174 }
这篇关于demo xshell (程序替换 工作目录 内建命令)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!