Linux下自定义shell(简单shell)

一.操作流程及原理

既然要自定义shell,那么咱们自定义的shell肯定要有与LInux的shell有相同的功能,那么接下来说说怎么操作及其原理,咱们首先运用fgets函数来获取stdin输入流,即获取你要输入的命令,然后把你输入的命令进行解析,不过在解析命令前要先把命令进行组织起来,这是咱们可以通过数组来把命令进行组织(下文会说明为什么要进行组织),然后就是解析,在Linux中咱们输入命令的过程是不会终止的,即当你输入一个命令并按下回车键后,咱们还可以接着输入命令,那为了实现这个功能,咱们可以通过循环语句还有进程的特性(独立性)来进行持续性解析命令,当咱们把输入的命令组织到一个数组中后(如args),咱们可以通过exec函数这个系统调用函数来进行解析,具体的解析过程其实很简单,咱们只需要把args中的元素传入exec函数即可(因为咱们组织的时候咱们输入的命令和参数已经输入到了args数组中),例如:首先输入命令cmd:ls -a -l,然后经过组织后数组args[]={“ls”,“-a”,"-l"},然后咱们使用exec函数中的execvp函数来进行解析,咱们只需execvp(args[0],args);即可

二.问题

问题一:为什么要进行组织?

eg:咱们在命令行 输入命令 ls -a -l 或者 ls -a -l 时大家可以发现在Linux下它为你启动的bash中这两种命令的输入方式有明显的不同,但是最终输出的结果都是一样的,这是为什么?这是因为在bash上述两种命令最后都被组织成了一种数组的形式 如 args[] = {"ls","-a","-l"} ,而为什么要进行组织就是为了如果有人把命令输入成 ls -a -l 这种形式后,操作系统还可以对命令进行成功的解析,增强了操作系统中命令行输入的健壮性。

问题二:具体解析的过程是怎样的?

所谓的解析其实就是利用exec这类系统调用函数来解析命令,在咱们组织完成之后,其实只需要把包涵命令的数组args传入exec函数即可,但是这里面有一个问题,那就是exec函数是一种进程替换函数,在你使用exec这个函数后,你当前进程会被替换成你调用命令的进程,即你这个进程的代码与数据都会被替换,而且在替换完这个进程后,这个进程会直接结束,如果在当前进程,即咱们运行敲代码的这个进程(咱们把它称为父进程),直接使用exec函数的话,那么在exec函数替换完成后,这个进程就结束了,那么意味着咱们这个shell只能输入一次命令,因为输入完命令再调用exec函数后该进程直接结束,但是咱们在Linux的bash中可以持续的输入命令,即一个命令执行完成后,可以再执行别的命令,所以为了完善咱们的自制shell,咱们就必须要解决这个问题。当然其实解决方法也很简单,咱们只需要在这个父进程中再创建一个子进程,并且利用子进程来执行组织和解析的过程即可。

问题三:什么是进程替换函数(什么是exec函数族)?

我们通常把exec函数称为进程替换函数,它的作用:exec函数族提供了一个在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新的进程替换了(以上摘自百度百科)。简而言之,当一个进程调用exec函数来替换进程的时候,当前进程的代码与数据都会被替换进程的代码与数据替代,并且页表所映射的物理地址也会被替代,但是呢这个过程并没有创建新的进程,所以在使用exec函数调换前后该进程的id(包括pid,进程组号等等)没有改变,改变的只是代码与数据还有页表的映射关系。

ps:exec函数的原型:

int execl(const char * path,const char * arg,…);

int execle(const char * path,const char * arg,char * const envp[]);

int execlp(const char * file,const char * arg,…);

int execv(const char * path,char * const argv[]);

int execve(const char * path,char * const argv[],char * const envp[]);

int execvp(const char * file,char * const argv[]);

其中一些参数:

l - list 参数采用列表,即把参数直接一个一个传入exec函数。

v - vector 参数采用数组,即把这些参数都加入到一个数组中去,然后再把数组传入函数。

p - path 有p自动搜索环境变量PATH,即如果你带了p选项,那么就可以不用传入命令所在的路径

e - env 自己维护环境变量

三,代码:

 

    1 #include <stdio.h>                                                                                                                          
    2 #include <string.h>
    3 #include <unistd.h>
    4 #include <stdlib.h>
    5 
    6 #define MAX_CMD 1024
    7 #define MAX_PAR 32
    8 
    9 char cmd[MAX_CMD];
   10 char* args[MAX_PAR];
   11 
   12 int main()
   13 {
   14   const char* identifier = "[dcly@localhost file]^_^ ";
   15   while(1){
   16     memset(cmd,0x00,MAX_CMD);
   17     printf("%s",identifier);
   18     fgets(cmd,MAX_CMD,stdin);
   19     cmd[strlen(cmd) - 1] = '\0';
   20     args[0] = strtok(cmd, " ");
   21     int i = 1;
   22     do{
   23       args[i] = strtok(NULL," ");
   24       if(args[i++] == NULL){
   25         break;
   26       }
   27     }while(1);
   28 
   29     pid_t id = fork();
   30     if(id < 0){
   31       printf("fork error!\n");
   32       continue;
   33     }
   34     if(id == 0){
   35       execvp(args[0], args);
   36       exit(1);
   37     }
   38     int status = 0;
   39     pid_t ret = waitpid(id, &status, 0);
   40     if(ret > 0 && status & 0x7f == 0){
   41       printf("child status code : %d\n",(status >> 8)&0xff);
   42     }
   43   }
   44   return 0;
   45 }                                           

四,解析代码

在上述代码中,cmd这个数组的作用是接收命令,args这个指针数组的作用是接收组织完成后的命令,MAX_CMD与MAX_PAR这两个宏的作用,前者是用来表示接收命令的字符数量最大值,后者表示最终组织完成后命令及其参数的字符串数量的最大值。identifier就是用来自定义如[dcly@localhost file]$ 这样的命令标识符的,你可以把它设置成你喜欢的样子。 当然大家也可以通过如gethostname 获取本主机名称 getuid 获取用户标识号 等等之类的系统调用命令来完善或者说是丰富大家的命令标识符,比如 dcly - 用户名 localhost - 主机名 file - 当前目录

 

 fgets函数是类似如scanf之类的输入函数。strtok函数是用来把你输入的如 ls  -a  -l 命令进行分解,最终分解成 " ls " " -a " " -l "这三个字符串(如果有不熟悉strtok函数的朋友可以看看这个链接),strtok - C++ Referencehttp://www.cplusplus.com/reference/cstring/strtok/?kw=strtokfork函数用来创建子进程,id 用来接收fork函数的返回值,如果id等于0的时候就代表该进程为子进程,那么就可以进行进程替代了。咱们使用的是execvp函数,这个函数只需要传入命令与命令与参数所在的数组即可(在我们进行组织的时候是从左向右来进行的,也就是说" ls "命令首先被传入到args[ 0 ]了,所以咱们传参的时候把args[0]即命令传入第一个参数位)

ps:这只是一个比较简单比较粗糙的自定义shell,其中本应该有的 > 重定向和 | 管道等等都还没有实现,如果对自定义shell感兴趣的朋友可以关注我,我以后会推出高级版本的自定义shell供大家了解。

ps:这篇文章中有常见的系统调用函数,如 gethostname之类的。

linux常见系统调用函数列表_最爱酸豆角的博客-CSDN博客_linux系统调用函数大全以下是Linux系统调用的一个列表,包含了大部分常用系统调用和由系统调用派生出的的函数。这可能是你在互联网上所能看到的唯一一篇中文注释的Linux系统调用列表,即使是简单的字母序英文列表,能做到这么完全也是很罕见的。按照惯例,这个列表以man pages第2节,即系统调用节为蓝本。按照笔者的理解,对其作了大致的分类,同时也作了一些小小的修改,删去了几个仅供内核使用,不允许用户调用的系统调用,对个别本人稍觉不妥的地方作了一些小的修改,并对所有列出的系统调用附上简要注释。其中有一些函数的作用完全相同,只https://blog.csdn.net/ztp123456/article/details/107845118

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东辰良月2

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值