(18)Linux 实现简易版shell

前言:做一个 "会创建,会终止,会等待,会程序替换" 的简易 shell 。

1、显示提示符和获取用户输入

shell 本质就是个死循环,我们不关心获取这些属性的接口,如果要实现 shell:

  • 1:显示提示符 →  #
  • 2:获取用户输入 → fgets
  • 3:将接收到的字符串拆开  →  把 "ls -a -l" 转换成  "ls"  "-a"  "-l" 
  • ……

我们先从简单的入手,先来实现前两步,显示提示符 获取用户输入

  1 #include <stdio.h>
  2 #include <string.h>
  3 #include <stdlib.h>
  4 #include <unistd.h>
  5 #include <sys/wait.h>
  6 #include <sys/types.h>
  7 
  8 #define NUM 1024
  9 
 10 char cmd_line[NUM];   // 用来接收命令行内容
 11 
 12 int main(void)
 13 {
 14   //0.命令行解释器,一定是一个常驻内存的进程,不退出
 15     while (1) {
 16         /* 1:显示提示信息 */
 17         printf("[amx@hecs-0-1  myshell] # ");
 18         fflush(stdout);
 19 
 20         /* 2:获取用户输入,输入的是各种指令和选项 :"ls -a -l "*/
 21         memset (
 22             cmd_line,
 23             '\0',
 24             sizeof(cmd_line)
 25             );
 26        if( fgets(cmd_line, NUM, stdin)==NULL) {
 27           continue;
 28        }/* 从键盘获取,标准输入,stdin 
 29             获取到 C 风格的字符串,默认添加 '\0' */
 30         printf("%s\n", cmd_line);                                                                                                                                  
 31     }
 32 }

我们利用 fgets 函数从键盘上获取,标准输入 stdin,获取到 C 风格的字符串,

注意默认会添加 \0 ,我们先把获取到的结果 cmd_line 打印出来看看:

因为 cmd_line 里有一个 \n——我们输入完指令后会按下回车,printf里面又有一个回车  。我们把它替换成 \0 即可: 

cmd_line[strlen(cmd_line) - 1] = '\0';  //-1的目的是,最后一个字符的下标是长度减一,strlen不包括\n,\0这些 

2、将接收到的字符串拆开

下面我们需要 将接收到的字符串拆开,比如:把 "ls -a -l" 拆成  "ls"  "-a"  "-l"  

因为 exec 函数簇无论是列表传参还是数组传参,一定是要逐个传递的!

我们自己实现的话,就是把这些空格变成\0,让他们输出。

但是我们可以使用 strtok 函数,将一个字符串按照特定的分隔符打散,将子串依次返回:

char* strtok(char* str, const char* delim);

代码:

  1 #include <stdio.h>
  2 #include <string.h>
  3 #include <stdlib.h>
  4 #include <unistd.h>
  5 #include <sys/wait.h>
  6 #include <sys/types.h>
  7  
  8 #define NUM 1024
  9 #define SIZE 32
 10 #define SEP " "//设置分隔符 空格
 11 
 12 //保存打散之后的命令行字符串
 13 char *g_argv[SIZE];
 14 char cmd_line[NUM];   // 用来接收完整的命令行内容
 15  
 16 int main(void)
 17 {
 18   //0.命令行解释器,一定是一个常驻内存的进程,不退出
 19     while (1) {
 20         /* 1:显示提示信息 */
 21         printf("[amx@hecs-0-1  myshell] # ");
 22         fflush(stdout);
 23                                                                                                                                      
 24         /* 2:获取用户输入,输入的是各种指令和选项 :"ls -a -l "*/                                                                     
 25         memset (                                                                                                                     
 26             cmd_line,                                                                                                                
 27             '\0',                                                                                                                    
 28             sizeof(cmd_line)                                                                                                         
 29             );                                                                                                                       
 30        if( fgets(cmd_line, NUM, stdin)==NULL) {                                                                                      
 31           continue;                                                                                                                  
 32        }/* 从键盘获取,标准输入,stdin                                                                                               
 33             获取到 C 风格的字符串,默认添加 '\0' */                                                                                  
 34         cmd_line[strlen(cmd_line) - 1] = '\0';  //-1的目的是,最后一个字符的下标是长度减一,strlen不包括\n,\0这些                    
 35       // printf("%s\n", cmd_line);                                                                                                   
 36       //3.命令行字符串解析:"ls -a -l "->"ls" "-a" "-l"                                                                              
 37       g_argv[0]= strtok(cmd_line, SEP);//第一次调用,要传入原始字符串  
 38       int index=1;
 39     while(g_argv[index++]=strtok(NULL,SEP));//第二次调用,如果还要解析原始字符串,传NULL
 40        //for debug 
 41          for(index=0;g_argv[index];index++){             
 42           printf("g_argv[%d]:%s\n",index,g_argv[index]);                                                                           
 43         }                                                                                                                                           
 44     }                                                                                                                                                            
 45 }                                                                                                                                                         
~      

运行结果:

3、创建进程 & 程序替换 

下面我们实现 创建进程,执行它。 

 1 #include <stdio.h>
    2 #include <string.h>
    3 #include <stdlib.h>
    4 #include <unistd.h>
    5 #include <sys/wait.h>
    6 #include <sys/types.h>
    7  
    8 #define NUM 1024
    9 #define SIZE 32
   10 #define SEP " "//设置分隔符 空格
   11 
   12 //保存打散之后的命令行字符串
   13 char *g_argv[SIZE];
   14 char cmd_line[NUM];   // 用来接收完整的命令行内容
   15  
   16 int main(void)
   17 {
   18   //0.命令行解释器,一定是一个常驻内存的进程,不退出
   19     while (1) {
   20         /* 1:显示提示信息 */
   21         printf("[amx@hecs-0-1  myshell] # ");
   22         fflush(stdout);
   23  
   24         /* 2:获取用户输入,输入的是各种指令和选项 :"ls -a -l "*/
   25         memset (
   26             cmd_line, 
   27             '\0', 
   28             sizeof(cmd_line) 
   29             );                                                                                                                                                   
   30        if( fgets(cmd_line, NUM, stdin)==NULL) {
   31           continue;                                     
   32        }/* 从键盘获取,标准输入,stdin 
   33             获取到 C 风格的字符串,默认添加 '\0' */
   34         cmd_line[strlen(cmd_line) - 1] = '\0';  //-1的目的是,最后一个字符的下标是长度减一,strlen不包括\n,\0这些
   35       // printf("%s\n", cmd_line);
   36       //3.命令行字符串解析:"ls -a -l "->"ls" "-a" "-l"
   37       g_argv[0]= strtok(cmd_line, SEP);//第一次调用,要传入原始字符串
   38       int index=1;
W> 39       while(g_argv[index++]=strtok(NULL,SEP));                                                                                                                   
   40         //for debug
   41        // for(index=0;g_argv[index];index++){
   42          // printf("g_argv[%d]:%s\n",index,g_argv[index]);
   43        // }
   44       // 4.TODO
   45       // 5.fork()
   46       pid_t id=fork();
   47         if(id==0){//子进程
   48             printf("下面功能让子进程执行的\n");
   49             execvp(g_argv[0],g_argv);//ls -a -l -i
   50             exit(1);
   51         }
   52         else{//父进程
   53           int status=0;                                                                                                                                          
   54           pid_t ret=waitpid(id,&status,0);                        
   55           if(ret>0) printf("exit code: %d\n",WEXITSTATUS(status));
   56          
   57         }
   58      
   59     }
   60 }

完美运行,你还可以输入vim进行编写,基本命令都可以!!!!!!!

但是但是但是:

当我推出到上一级时,他的目录并没有发生什么变化,为什么?

当你在cd的时候,无论什么命令都会执行exec,而执行exec命令只会影响当前的子进程的路径变化,而不是父进程路径变化,运行完子进程,子进程就退出了,所以我们还需要再做一些工作------如果是像cd这样的命令,我们不能创建子进程。

像这样,让父亲在自己去执行的命令我们叫做内置命令\内建命令-----本质就是shell中的函数调用。

4、内建命令:实现路径切换

代码实现:

运行结果:

在上层你看到的是个命令,但是在 shell 内部本质上是由父 shell 自己实现、调用的一个函数(并没有创建子进程),这种就是对应上上层的 内建命令。

内建命令表现是用用户层面的一条命令,本质就是 Shell 内部的一个函数,由父 Shell 自己执行,而不创建子进程。

还可以加颜色:

我们which一下可以发现颜色配色。

运行结果:

全部搞定!!!!!!!!

//除了 ll这个命令

我们看,因为ll本身就是ls的别名,如果想让它成立,我们还需要做一些判断:

至此,圆满结束!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值