基本shell功能实现(exec系列程序替换函数练习)

shell

  • 功能描述
  • 思路介绍
    • 1.实现常驻进程功能
    • 2.实现命令读取功能
    • 3. 实现命令解析功能
    • 4.实现子进程执行命令功能
    • 5.完善功能
  • 补充内容
    • 让父进程运行内置命令
    • 实现子进程能够获得父进程的环境变量功能(export命令)
    • shell实现重定向功能
  • 全部代码如下:

功能描述

实现一个类似于shell的命令行解释器。通过让子进程执行命令,父进程等待等待并解析命令,从而可以执行类似于“ls”,“ls -a -l -i”,'pwd"等linux指令。

思路介绍

1.实现常驻进程功能

在这里将要实现一个死循环,并且打印出提示信息。

while(1)
{//命令行解释器一定是一个常驻内存的进程,不退出//打印出提示信息[]23     printf("[xty@localhost myshell]#] ");24     fflush(stdout);
}

运行如图:
在这里插入图片描述

2.实现命令读取功能

使用fgets函数读取输入的内容,注意要注意把回车给删除(因为fgets会把回车也读取进来)

	 #define NUM 1024//保存完整的命令字符串char cmd_line[NUM];//2.获取用户键盘的输入[输入的是各种指令和选项:"ls -a -l -i"]memset(cmd_line, '\0', sizeof(cmd_line));if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL){continue;}cmd_line[strlen(cmd_line)-1] = '\0';//发现有"ls -a -l\n'\o'" 回车换行,我们应该删除这个//printf("echo:%s \n", cmd_line);

3. 实现命令解析功能

需要将我们输入的命令行字符串,变成shell能理解的语言。
将命令行选项使用strtok分开,把 "ls -a -l"变成 “ls”, “-a”, “-l”, “NULL”
为后面的execvp作准备。

#define SIZE 32
//保存打散之后的命令行字符串
char *g_argv[SIZE];//3. 将命令行选项使用strtok分开, "ls -a -l"-> "ls", "-a", "-l", "NULL"
g_argv[0] = strtok(cmd_line, " ");
int index = 1;
while(g_argv[index++] = strtok(NULL, " "));//检查一下g_argv对不对
for(index = 0; g_argv[index];index++)
{//虽然存入的是地址,但是%s,会将它看成字符串打印出来printf("g_argv[%d] = %s\n", index, g_argv[index]);
}

结果如下:
在这里插入图片描述

4.实现子进程执行命令功能

子进程执行命令,父进程等待子进程返回。

     //4.让子进程执行命令,执行完后给父进程返回值pid_t id = fork();if(id==0){//子进程,执行命令 printf("子进程开始执行任务\n");                                                                     execvp(g_argv[0], g_argv);exit(1);}//fatherint status = 0;pid_t ret = waitpid(id, &status, 0);if(ret>0) printf("exit code:%d\n", WEXITSTATUS(status));

5.完善功能

让"ls"有颜色,并且让shell认识"ls"命令。
完整代码如下:

1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include<string.h>
5 #include<sys/wait.h>
6 #include<sys/types.h>
7 
8 #define NUM 1024
9 #define SIZE 32
10 //保存完整的命令字符串
11 char cmd_line[NUM];
12 //保存打散之后的命令行字符串
13 char *g_argv[SIZE];
14 
15 
16 //shell运行原理:通过让子进程执行命令,父进程等待&&解析命令
17 int main()
18 {                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
19   //0.命令行解释器一定是一个常驻内存的进程,不退出
20   while(1)
21   {
22     //1.打印出提示信息[]
23     printf("[xty@localhost myshell]#] ");
24     fflush(stdout);
25 
26     //2.获取用户键盘的输入[输入的是各种指令和选项:"ls -a -l -i"]
27     memset(cmd_line, '\0', sizeof(cmd_line));
28     if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL)
29     {
30       continue;
31     }
32     if(cmd_line[0] == '\n')
33     {
34       continue;
35     }
36     cmd_line[strlen(cmd_line)-1] = '\0';
37     //发现有"ls -a -l\n'\o'" 回车换行,我们应该删除这个
38     //printf("echo:%s \n", cmd_line);
39 
40     //3. 将命令行选项使用strtok分开, "ls -a -l"-> "ls", "-a", "-l", "NULL"
41     g_argv[0] = strtok(cmd_line, " ");
42     int index = 1;
43 
44     if(strcmp(g_argv[0], "ls")==0)
45     {
46       g_argv[index++]="--color=auto";
47     }
48     //还可以让编译器支"ll"
49     if(strcmp(g_argv[0],"ll")==0)
50     {
51       g_argv[0] = "ls";
52       g_argv[index++] = "-l";
53       g_argv[index++] = "--color=auto";
54     }
55 
56     //让ls命令有颜色
57     while(g_argv[index++] = strtok(NULL, " "));
58 
59     //检查一下g_argv对不对
60     for(index = 0; g_argv[index];index++)
61     {
62       //虽然存入的是地址,但是%s,会将它看成字符串打印出来
63       printf("g_argv[%d] = %s\n", index, g_argv[index]);
64     }
65 
66     //4.让子进程执行命令,执行完后给父进程返回值
67     pid_t id = fork();
68     if(id==0)
69     {
70       //子进程,执行命令 
71       printf("子进程开始执行任务\n");
72       execvp(g_argv[0], g_argv);
73       exit(1);
74     }
75 
76     //father
77     int status = 0;
78     pid_t ret = waitpid(id, &status, 0);
79     if(ret>0) printf("exit code:%d\n", WEXITSTATUS(status));
80   }
81   return 0;
82 }

这样我们一个简易的shell就完成了。

补充内容

shell执行的命令,通常有两种:

  1. 第三方提供的对应在磁盘中有具体二进制文件的可执行程序(由子进程执行)
  2. shell内部,自己实现的方法,由(父进程)来执行,有些命令会影响到shell本身,比如:“cd”,"export"命令等。

让父进程运行内置命令

当我们执行cd命令时,发现程序并没有改变目录。如下图:
原因是:cd命令被子进程执行了,子进程的当前目录被修改了,但是子进程立马就退出了。但是并没有影响到父进程的当前目录,所以第二次再次运行的时候,子进程还是在父进程的目录创建的,因此没有改变。
在这里插入图片描述

使用chdir函数改变父进程的工作目录。
代码如下:

   66     //让父进程执行cd 命令67     if(strcmp(g_argv[0], "cd")==0)68     {69       if(g_argv[1]!=NULL) chdir(g_argv[1]); // cd .., cd path70       continue;                                                                                                                                                   71     }    

结果如下图:
在这里插入图片描述

实现子进程能够获得父进程的环境变量功能(export命令)

因export和其他的命令行功能不一样,所以需要再判断一下该功能。并且需要创建新数组保存一下环境变量,因为cmd_line再第二次读取时会清空,会导致getenv时得不到被清空的环境变量(因为存的是指针地址,清空后变成了野指针),因此需要创建数组存储一下!

代码如下:


//myshell.c//写一个环境变量的bufer,用来存储临时的环境变量,防止cmd_line被清空char my_num[64];// export MYNUM=111222333if(strcmp(g_argv[0], "export")==0 && g_argv[1]!=NULL){strcpy(my_num, g_argv[1]);int ret = putenv(my_num);if (ret == 0) printf("%s export sucess\n", my_num);  //查看一下导入的是什么continue;}//env_test.c1 #include<stdio.h>2 #include<stdlib.h>                                                                                                                                                  3 4 int main()5 {6   printf("我是测试环境变量是否成功导入进程\n");7   printf("MYNUM= %s \n",getenv("MYNUM"));8 9 }

在这里插入图片描述

shell实现重定向功能

先检查是否有重定向的功能,然后再执行命令。

//检查命令模块//定义标志位的含义                                                                                                                                                                                                                         22 #define INPUT_REDIR 123 #define OUTPUT_REDIR 224 #define APPEND_REDIR 325 #define NONE_REDIR 026 int redir_status = NONE_REDIR;27 28 char *CheckRedir(char *start)29 {30   assert(start);31   char *end = start + strlen(start) - 1;// ls -a -l32 33   //从后往前找34   while(end >= start)35   {36     if(*end == '>')37     {38       if(*(end - 1) == '>')39       {40         redir_status = APPEND_REDIR;41         *(end - 1) = '\0';42         end++;  //重定向后字符串的首地址43         break;44       }45       redir_status = OUTPUT_REDIR;46       *end = '\0';47       end++;   // ls -a>myfile.txt   ->ls -a\0myflie.txt48       break;49     }50     else if(*end == '<')51     {52       redir_status = INPUT_REDIR;53       *end = '\0';54       end++;55       break;56     }57     else{58       end--;59     }60   }61   if(end>=start)62   {63     return end; //要打开的文件名64   }65   else{66     return NULL;  //没有重定向功能67   }68 } //main函数内部,子进程执行命令前,多一个重定向打开文件的逻辑:142     //4.让子进程执行命令,执行完后给父进程返回值143     pid_t id = fork();144     if(id==0)145     {146       //子进程,执行命令 147       printf("子进程开始执行任务\n");148       if(sep!=NULL)149       {150         int fd = -1;151         //有重定向152         switch(redir_status)153         {154           case INPUT_REDIR:155             fd = open(sep, O_RDONLY);156             dup2(fd, 0);157             break;158           case OUTPUT_REDIR:159             fd = open(sep, O_WRONLY | O_TRUNC | O_CREAT, 0666);160             dup2(fd, 1);161             break;162           case APPEND_REDIR:163             fd = open(sep, O_WRONLY | O_APPEND | O_CREAT, 0666);164             dup2(fd, 1);165             break;166           default:167             printf("erro????????\n");168             break;169         }170       }171       172       execvp(g_argv[0], g_argv);173       exit(1);174     }175    

全部代码如下:

    1 #include<stdio.h>2 #include<unistd.h>3 #include<stdlib.h>4 #include<string.h>5 #include<assert.h>6 #include<fcntl.h>7 #include<sys/wait.h>8 #include<sys/stat.h>9 #include<sys/types.h>10 11 #define NUM 102412 #define SIZE 3213 //保存完整的命令字符串14 char cmd_line[NUM];15 //保存打散之后的命令行字符串16 char *g_argv[SIZE];17 18 //写一个环境变量的bufer,用来存储临时的环境变量,防止cmd_line被清空19 char my_num[64];20 21 //定义标志位的含义22 #define INPUT_REDIR 123 #define OUTPUT_REDIR 224 #define APPEND_REDIR 325 #define NONE_REDIR 026 int redir_status = NONE_REDIR;27 28 char *CheckRedir(char *start)29 {30   assert(start);31   char *end = start + strlen(start) - 1;// ls -a -l32   33   //从后往前找34   while(end >= start)35   {36     if(*end == '>')37     {38       if(*(end - 1) == '>')39       {40         redir_status = APPEND_REDIR; 41         *(end - 1) = '\0';42         end++;  //重定向后字符串的首地址43         break;44       }45       redir_status = OUTPUT_REDIR;46       *end = '\0';47       end++;   // ls -a>myfile.txt   ->ls -a\0myflie.txt48       break;49     }50     else if(*end == '<')51     {52       redir_status = INPUT_REDIR;53       *end = '\0';54       end++;55       break;56     }57     else{58       end--;59     }60   }61   if(end>=start)62   {63     return end; //要打开的文件名64   }65   else{66     return NULL;  //没有重定向功能67   }68 } 69 70 //shell运行原理:通过让子进程执行命令,父进程等待&&解析命令71 int main()72 {73   //0.命令行解释器一定是一个常驻内存的进程,不退出74   while(1)75   {76     //1.打印出提示信息[]77     printf("[xty@localhost myshell]#] ");78     fflush(stdout);79 80     //2.获取用户键盘的输入[输入的是各种指令和选项:"ls -a -l -i"]81     memset(cmd_line, '\0', sizeof(cmd_line));82     if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL)83     {84       continue;85     }86     if(cmd_line[0] == '\n')87     {88       continue;89     }90     cmd_line[strlen(cmd_line)-1] = '\0';91     //发现有"ls -a -l\n'\o'" 回车换行,我们应该删除这个92     //printf("echo:%s \n", cmd_line);93     94 95     //在分析指令之前就检查有没有重定向96     char* sep = CheckRedir(cmd_line);97     98     //3. 将命令行选项使用strtok分开, "ls -a -l"-> "ls", "-a", "-l", "NULL"99     g_argv[0] = strtok(cmd_line, " ");100     int index = 1;101 102     if(strcmp(g_argv[0], "ls")==0)103     {
W>104       g_argv[index++]="--color=auto";105     }106     //还可以让编译器支"ll"107     if(strcmp(g_argv[0],"ll")==0)108     {
W>109       g_argv[0] = "ls";
W>110       g_argv[index++] = "-l";
W>111       g_argv[index++] = "--color=auto";112     }113 114     //让ls命令有颜色
W>115     while(g_argv[index++] = strtok(NULL, " "));116     117     检查一下g_argv对不对118     //for(index = 0; g_argv[index];index++)119     //{120     //  //虽然存入的是地址,但是%s,会将它看成字符串打印出来121     //  printf("g_argv[%d] = %s\n", index, g_argv[index]);122     //}123 124 125     // export MYNUM=111222333126     if(strcmp(g_argv[0], "export")==0 && g_argv[1]!=NULL)127     {128       strcpy(my_num, g_argv[1]);129       int ret = putenv(my_num);130       if (ret == 0) printf("%s export sucess\n", my_num);  //查看一下导入的是什么131       continue;132     }133 134 135 136     //让父进程执行cd 命令137     if(strcmp(g_argv[0], "cd")==0)138     {139       if(g_argv[1]!=NULL) chdir(g_argv[1]); // cd .., cd path140       continue;141     }142     //4.让子进程执行命令,执行完后给父进程返回值143     pid_t id = fork();144     if(id==0)145     {146       //子进程,执行命令 147       printf("子进程开始执行任务\n");148       if(sep!=NULL)149       {150         int fd = -1;151         //有重定向152         switch(redir_status)153         {154           case INPUT_REDIR:155             fd = open(sep, O_RDONLY);156             dup2(fd, 0);157             break;158           case OUTPUT_REDIR:159             fd = open(sep, O_WRONLY | O_TRUNC | O_CREAT, 0666);160             dup2(fd, 1);161             break;162           case APPEND_REDIR:163             fd = open(sep, O_WRONLY | O_APPEND | O_CREAT, 0666);164             dup2(fd, 1);165             break;166           default:167             printf("erro????????\n");168             break;169         }170       }171 172       execvp(g_argv[0], g_argv);173       exit(1);174     }175 176     //father177     int status = 0;178     pid_t ret = waitpid(id, &status, 0);179     if(ret>0) printf("exit code:%d\n", WEXITSTATUS(status));180   }181   return 0;182 }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/236280.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

『 C++ 』二叉树进阶OJ题

文章目录 根据二叉树创建字符串 &#x1f996;&#x1f969; 题目描述&#x1f969; 解题思路&#x1f969; 代码 二叉树的层序遍历(分层遍历) &#x1f996;&#x1f969; 题目描述&#x1f969; 解题思路&#x1f969; 代码 二叉树的层序遍历(分层遍历)Ⅱ &#x1f996;&…

一篇文章带你了解SpringBoot目录结构

前言 SpringBoot是整合Spring技术栈的一站式框架&#xff0c;是简化Spring技术栈的快速开发脚手架&#xff0c;是一个能够快速构建生产级别的Spring应用的工具。SpringBoot是目前流行的微服务框架&#xff0c;倡导“约定优于配置”&#xff0c;简化Spring项目搭建及开发过程。…

速通Python基础语法--运算符篇

一、算术运算符 优先级&#xff1a; 除法的2个问题&#xff1a; 除零异常&#xff1a; 运行时才出现的错误&#xff0c;叫做“抛出异常” 如果程序运行过程中 抛出异常&#xff0c;程序就会直接终止&#xff0c;后面的代码不会执行。 除法的(不)截断问题&#xff1a; %取模/求…

宝塔面板安装MySQL数据库并通过内网穿透工具实现公网远程访问

文章目录 前言1.Mysql 服务安装2.创建数据库3.安装 cpolar3.2 创建 HTTP 隧道 4.远程连接5.固定 TCP 地址5.1 保留一个固定的公网 TCP 端口地址5.2 配置固定公网 TCP 端口地址 前言 宝塔面板的简易操作性,使得运维难度降低,简化了 Linux 命令行进行繁琐的配置,下面简单几步,通…

微信小程序使用--如何生成二维码

一、生成二维码 1.获取token 参照官方文档说明&#xff1a; https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getAccessToken.html 其中grant_type是写死的&#xff0c;appid和secret是注册小程序的时候获取的&#xff0c;然后会得到一个默认两小…

MyBatis-Plus全套笔记

一、MyBatis-Plus 1.简介 MyBatis-Plus (opens new window)&#xff08;简称 MP&#xff09;是一个 MyBatis (opens new window)的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 我们的愿景是成为 MyBatis 最好的搭档&…

c jpeg 理论霍夫曼 DC AC表,c程序实现正向逆向转换

此4张表是理论表&#xff0c;不是针对某张图片的特定表。如编码程序不统计生成某图片的专用霍夫曼表&#xff0c;应该也可用理论表代用编码。 1.亮度DC表 左边第一列是二进制位数&#xff0c;就是对此位数编码 中间一列是生成比特流的位数&#xff0c;右边是生成的比特流。 …

本地MinIO存储服务如何创建Buckets并实现公网访问上传文件

文章目录 前言1. 创建Buckets和Access Keys2. Linux 安装Cpolar3. 创建连接MinIO服务公网地址4. 远程调用MinIO服务小结5. 固定连接TCP公网地址6. 固定地址连接测试 前言 MinIO是一款高性能、分布式的对象存储系统&#xff0c;它可以100%的运行在标准硬件上&#xff0c;即X86等…

Seata:打造行业首个分布式事务产品

作者&#xff1a;季敏&#xff0c;阿里云分布式事务产品负责人、Seata 开源项目创始人 微服务架构下数据一致性的挑战 微服务开发的痛点 在 2019 年&#xff0c;我们基于 Dubbo Ecosystem Meetup&#xff0c;收集了 2000 多份关于“在微服务架构&#xff0c;哪些核心问题是开…

SparkSQL的编程模型(DataFrame和DataSet)

1.2 SparkSQL的编程模型(DataFrame和DataSet) 1.2.1 编程模型简介 主要通过两种方式操作SparkSQL&#xff0c;一种就是SQL&#xff0c;另一种为DataFrame和Dataset。 SQL SQL不用多说&#xff0c;就和Hive操作一样&#xff0c;但是需要清楚一点的时候&#xff0c;SQL操作的是…

企业需要哪些数字化管理系统?

企业需要哪些数字化管理系统&#xff1f; ✅企业引进管理系统肯定是为了帮助整合和管理大量的数据&#xff0c;从而优化业务流程&#xff0c;提高工作效率和生产力。 ❌但是&#xff0c;如果各个系统之间不互通、无法互相关联数据的话&#xff0c;反而会增加工作量和时间成本…

【递归 回溯】LeetCode-226. 翻转二叉树

226. 翻转二叉树。 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1]示例 2&#xff1a; 输入&#xff1a;root [2,1,3] 输出&#xf…

写给测试同学的福利 | 招募

一、简介 寻找空闲时间有能力进行有偿协助测试的人员&#xff0c;协助大厂产品进行测试优化产品工作 二、要求 1.安卓设备2.具备熟练使用手机的能力 三、你可以得到 1.优先体验相关产品新功能、了解产品走向2.任务有偿&#xff1a;每次任务5-30米 四、需要了解事项 1.嫌…

生于越南,“开源改变了我的人生!”

注&#xff1a;本文精选自《新程序员 007&#xff1a;大模型时代的开发者》&#xff0c;欢迎点击订购。 作者 | 王启隆 责编 | 唐小引 出品 | 《新程序员》编辑部 随着人工智能浪潮的席卷&#xff0c;开源不再仅仅是计算机领域的一个话题&#xff0c;而是成为推动技术创新…

让测试效率起飞的8款浏览器兼容性测试工具,你get了吗?

浏览器的兼容性问题&#xff0c;是指不同浏览器使用内核及所支持的 HTML 等网页语言标准不同&#xff0c;用户客户端的环境不同造成的显示效果不能达到理想效果。 对于用户而言&#xff0c;无论使用哪款浏览器&#xff0c;期望看到的效果是正常的统一的。市面上发布的浏览器版本…

JVS低代码和智能BI(自助式数据分析)12.19更新功能说明

低代码更新功能 新增: 1、表单组件&#xff1a;标题、分割线、按钮等非数据组件增加小程序端隐藏设置&#xff1b; 隐藏设置允许开发者对表单组件中的非数据组件进行隐藏&#xff0c;例如&#xff0c;可能只想展示表单的部分内容&#xff0c;或者希望在特定条件下显示或隐藏…

<JavaEE> 网络编程 -- 网络编程和 Socket 套接字

目录 一、网络编程的概念 1&#xff09;什么是网络编程&#xff1f; 2&#xff09;网络编程中的基本概念 1> 收发端 2> 请求和响应 3> 客户端和服务端 二、Socket套接字 1&#xff09;什么是“套接字”&#xff1f; 2&#xff09;Socket套接字的概念 3&…

整数比较(比较4个数并从小到大输出)C语言xdoj94

描述&#xff1a; 从键盘输入四个整数&#xff0c;要求按由小到大的顺序输出。 输入说明&#xff1a; 输入四个整数&#xff0c;以空格间隔。 输出说明&#xff1a; 输出排序后的整数&#xff0c;以空格间隔。 输入样例 样例1输入 -99 9 99 -9 输出样例 样例1输出 -99 -9 9 99 …

关于“Python”的核心知识点整理大全32

目录 12.6.4 调整飞船的速度 settings.py ship.py alien_invasion.py 12.6.5 限制飞船的活动范围 ship.py 12.6.6 重构 check_events() game_functions.py 12.7 简单回顾 12.7.1 alien_invasion.py 12.7.2 settings.py 12.7.3 game_functions.py 12.7.4 ship.py …

JavaGUI(但期末速成版)之JFrame和JDialog

前言 学到期末发现越来越没时间来细写这些东西了&#xff0c;毕竟蒟蒻博主的发展方向主要需要学的不是Java&#xff0c;但为了期末高分通过&#xff0c;也不得不花一些精力上来&#xff0c;于是有了这样一篇速成GUI&#xff0c;本篇会以十分精简的语言来学习&#xff0c;主打一…