详解简单的shell脚本 --- 命令行解释器【Linux后端开发】


首先附上完整代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
//命令行解释器
//shell 运行原理:通过让子进程执行命令,父进程等待&&解析命令//保存完整的命令行字符串 -- 充当缓冲区
#define NUM 1024
char cmd_line[NUM];//保存切割之后的字符串
#define SIZE 32
char* g_argv[SIZE];#define SEP " "int main()
{//0.命令行解释器,一定是一个常驻内存的进程 --- 不退出(死循环)while(1){//1打印出提示信息 -- [wxq@VM-4-9-centos shell]$ printf("[dwr@VM-1-1-test shell]$ ");fflush(stdout);//2.获取用户的键盘输入[输入的是各种指令和选项"ls -a -l"]memset(cmd_line, 0, sizeof cmd_line);if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL){continue;}//输入的回车键设为\0  ls -a -l \n \0cmd_line[strlen(cmd_line)-1]  = '\0';// printf("echo:%s\n", cmd_line);   //debug//3.命令行字符串进行解析 "ls -a -l -s"  --->  "la" "-a" "-l" "-s"g_argv[0] = strtok(cmd_line, SEP); //第一次调用,传入原始的字符串int index = 1;while(g_argv[index++] = strtok(NULL, SEP)); //第二次调用,如果还要分割原始字符串,传入NULL//简单配置ls的颜色int i = 1;if(strcmp(g_argv[0], "ls") == 0){g_argv[i++] = "--color=auto";}//识别别名 - 主要是测试,一般是有接口的if(strcmp(g_argv[0], "ll") == 0){g_argv[0] = "ls";g_argv[i++] = "-l";g_argv[i++] = "--color=auto";}//debug
//        for(index = 0 ; g_argv[index]; index++)
//        {
//            printf("g_argv[%d]: %s\n", index, g_argv[index]);
//        }//4.TODO 内置命令:让父进程(shell)自己执行的命令。我们叫做内置命令,内建命令//内建命令本质就是shell中的一个函数调用if(strcmp(g_argv[0], "cd") == 0) //not child execute; father execute;{if(g_argv[1] != NULL){chdir(g_argv[1]); //cd  cd ..}continue;}//5.创建子进程进行程序替换pid_t id = fork();//childif(id == 0){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));}}return 0;
}

效果:

命令行解释器  

       
shell 运行原理:通过让子进程执行命令,父进程等待&&解析命令

步骤1. 命令行解释器,一定是一个常驻内存的进程 --- 这意味着它是不退出的(死循环)

代码示例:

#include <stdio.h>
int main()
{while(1){;}return 0;
}

步骤2.打印出提示信息

代码示例:

#include <stdio.h>
int main()
{while(1){printf("[dwr@VM-1-1-test shell]$\n"); }return 0;
}

这样打印出来我们会发现:

所以我们是不能在后面加上 '\n' 的,但是因为有缓冲区的存在,那么应该怎么办呢?

这里需要用到一个函数 --- fflush():刷新缓冲区

代码示例:

#include <stdio.h>
int main()
{while(1){printf("[dwr@VM-1-1-test shell]$");fflush(stdout); }return 0;
}

步骤3.获取用户的键盘输入[示例 :  输入的是各种指令和选项"ls -a -l" ]

思路:

①需要一个数组来模拟缓冲区 - - - 提取用户输入的字符

②使用fgets读取用户在键盘上的输入,如果读取失败,continue重新进入循环,重新读取,重新打印。

③使用printf测试一下

代码示例:

#include <stdio.h>
#include <string.h>
#define NUM 1024
char cmd_line[NUM];int main()
{while(1){printf("[dwr@VM-1-1-test shell]$");fflush(stdout); memset(cmd_line, 0, sizeof cmd_line);if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL){continue;}printf("echo:%s\n", cmd_line);}return 0;
}

但是我们会发现打印出来的结果是:

       这是一个需要注意的小细节的地方,因为在输入的时候,当我们最终输入字符结束的时候,会输入一个“回车”键盘,它会被缓冲区拿到并被识别为“\n”。

示例:输入 ls -l -a    缓冲区读取 ls -l -a \n  

所以这里我们需要把  \n   设置为 \0 作为字符串的结束标志

代码示例:

#include <stdio.h>
#include <string.h>
#define NUM 1024
char cmd_line[NUM];int main()
{while(1){printf("[dwr@VM-1-1-test shell]$");fflush(stdout); memset(cmd_line, 0, sizeof cmd_line);if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL){continue;}cmd_line[strlen(cmd_line)-1]  = '\0';    //printf("echo:%s\n", cmd_line);    //测试辅助}return 0;
}

输出结果:

步骤4.命令行字符串解析 [ 示例: " ls -a -l  -s" --->  "ls" "-a" "-l" "-s"]

思路:

①可以把空格定为分割符,然后切割为一个一个的子串

②定义一个指针数组保存切割下来的子串

代码示例:

#include <stdio.h>
#include <string.h>
#define NUM 1024
#define SIZE 32
#define SEP " "
char cmd_line[NUM];
char* g_argv[SIZE];int main()
{while(1){printf("[dwr@VM-1-1-test shell]$");fflush(stdout); memset(cmd_line, 0, sizeof cmd_line);if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL){continue;}cmd_line[strlen(cmd_line)-1]  = '\0';    //printf("echo:%s\n", cmd_line);    //测试辅助g_argv[0] = strtok(cmd_line, SEP);int index = 0;while(g_argv[index++] = strtok(NULL, SEP));//测试辅助//for(index = 0; g_argv[index]; index++)//printf("g_argv[%d]:%s\n", index, g_argv[index]);}return 0;
}

步骤5.创建子进程进行程序替换

注:进程等待和进程替换后续会更新详细解说

代码示例:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/types.h>
#define NUM 1024
#define SIZE 32
#define SEP " "
char cmd_line[NUM];
char* g_argv[SIZE];int main()
{while(1){printf("[dwr@VM-1-1-test shell]$");fflush(stdout); memset(cmd_line, 0, sizeof cmd_line);if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL){continue;}cmd_line[strlen(cmd_line)-1]  = '\0';    //printf("echo:%s\n", cmd_line);    //测试辅助g_argv[0] = strtok(cmd_line, SEP);int index = 0;while(g_argv[index++] = strtok(NULL, SEP));//测试辅助//for(index = 0; g_argv[index]; index++)//printf("g_argv[%d]:%s\n", index, g_argv[index]);pid_t id = fork();//child processif(id == 0){execvp(g_argv[0],g_argv);exit(-1);}                                                                                               //father process                                                                                        int status = 0;                                                                                 pid_t ret = waitpid(id, &status, 0); //阻塞等待                                                 if(ret > 0)                                                                                     {                                                                                               printf("exit code:%d\n",WEXITSTATUS(status));                                               }   }return 0;
}

其实写到这里一些简单的命令就已经可以跑了

示例:

退出自己写的shell脚本是 ctrl + c

步骤6.内置命令

但是上述代码有一些小问题,就是我们自己写的shell脚本它并没有让我们的路径发生变化

示例:

        原因是因为,当前我们自己写的shell,无论我们写的任何指令,都是交给了子进程 , 子进程进行进程替换帮助我们来完成的指令,那么指令就只会影响子进程,而不会影响父进程。所以当我们 cd 回到上级目录的时候,父进程根本没有变化,但是可能子进程所在的路径一直在回到上一层路径。

        那么我们想要的是shell脚本所在的路径发生变化,所以我们想要进行判断命令,如果是所谓cd这样的命令,那么我们不能创建子进程,而是直接交给父进程。

  • 内置命令:让父进程(shell)自己执行的命令。我们叫做内置命令,内建命令
  • 内建命令本质就是shell中的一个函数调用
     

代码示例:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/types.h>
#define NUM 1024
#define SIZE 32
#define SEP " "
char cmd_line[NUM];
char* g_argv[SIZE];int main()
{while(1){printf("[dwr@VM-1-1-test shell]$");fflush(stdout); memset(cmd_line, 0, sizeof cmd_line);if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL){continue;}cmd_line[strlen(cmd_line)-1]  = '\0';    //printf("echo:%s\n", cmd_line);    //测试辅助g_argv[0] = strtok(cmd_line, SEP);int index = 0;while(g_argv[index++] = strtok(NULL, SEP));//测试辅助//for(index = 0; g_argv[index]; index++)//printf("g_argv[%d]:%s\n", index, g_argv[index]);if(strcmp(g_argv[0], "cd") == 0) //not child execute; father execute;{if(g_argv[1] != NULL){chdir(g_argv[1]); //cd  cd ..}continue;}pid_t id = fork();//child processif(id == 0){execvp(g_argv[0],g_argv);exit(-1);}                                                                                               //father process                                                                                        int status = 0;                                                                                 pid_t ret = waitpid(id, &status, 0); //阻塞等待                                                 if(ret > 0)                                                                                     {                                                                                               printf("exit code:%d\n",WEXITSTATUS(status));                                               }   }return 0;
}

 

补充说明:

fflush()

#include<stdio.h>
int main()
{int fflush( FILE *stream );return 0;}

定义:冲洗一个流

头文件:<stdio.h>

注释:如果缓冲区已成功刷新,则Fflush返回0。

 关键字:continue:作用是跳过本次循环continue后面的代码,直接去判断部分,看是否进行下一次循环

memset()

#include<string.h>
int main()
{void *memset( void *dest, int c, size_t count );return 0;}

定义:将缓冲区设置为指定字符 / 可以用来初始化字符串

头文件:<string.h>

注释:memset返回dest的地址

fgets()

#include<stdio.h>
int main()
{char *fgets( char *string, int n, FILE *stream );return 0;}

定义:从流中获取字符串

头文件:<stdio.h>

注释:返回的是string。返回NULL表示错误或文件结束条件

strlen()

#include<string.h>
int main()
{size_t strlen( const char *string );return 0;}

定义:返回的是字符串的长度

头文件:<string.h>

注释:strlen只返回'\0'之前字符串的长度

strtok()

#include<string.h>
int main()
{char *strtok( char *strToken, const char *strDelimit );return 0;}

定义:查找字符串中的下一个标记。(常用于切割字符串)

头文件:<string.h>

注释:

sep参数是个字符串,定义了用作分隔符的字符集合

第一个参数指定一个字符串,它包含了0个或者多个由strDelimit字符串中一个或者多个分隔符分割的标记。

strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。

strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。

strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。

如果字符串中不存在更多的标记,则返回 NULL 指针。

chdir()

#include <unistd.h>
int main()
{int chdir(const char *path);return 0;}

定义:更改工作目录

头文件:#include <unistd.h>

返回值:如果成功,则返回0。如果出现错误,则返回-1,并适当地设置errno。


以上就是完整版简单shell脚本的编写,仅供参考

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

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

相关文章

谷歌浏览器插件开发速成指南:弹窗

诸神缄默不语-个人CSDN博文目录 本文介绍谷歌浏览器插件开发的入门教程&#xff0c;阅读完本文后应该就能开发一个简单的“hello world”插件&#xff0c;效果是出现写有“Hello Extensions”的弹窗。 作为系列文章的第一篇&#xff0c;本文还希望读者阅读后能够简要了解在此基…

电影特效渲染为什么费时间?「瑞云渲染」

影视特效渲染过程通常耗时且资源密集&#xff0c;因为它涉及处理复杂的视觉元素和光影效果。瑞云渲染通过云技术提供解决方案&#xff0c;加快渲染速度并降低成本。简而言之&#xff0c;电影特效渲染之所以费时&#xff0c;是因为其对计算机资源的高需求。 电影特效渲染费时间原…

Redis的三种部署方案

文章目录 单机模式主从复制哨兵模式分片集群 在Redis中提供的集群方案总共有三种&#xff1a;单机模式&#xff0c;主从复制集群、哨兵模式&#xff0c;Redis分片集群 单机模式 Redis 只运行在一台服务器上&#xff0c;并且所有的数据都存储在这一台服务器的内存中。 主从复制…

Django之REST Client插件

一、接口测试工具介绍 在开发前后端分离项目时,无论是开发后端,还是前端,基本都是需要测试API接口的内容,而目前我们需要开发遵循RESTFul规范的项目,也是必然的(自己不开发前端页面)。 在网上有很多这样的工具,常用的postman,但还是需要下载安装。在这我们介绍一个VSCod…

【小白学机器学习12】假设检验之3:t 检验 (t检验量,t分布,查t值表等)

目录 1 t 检验的定义 1.1 来自维基百科和百度百科 1.2 别名 1.3 和其他检验的区别 2 适用情况&#xff1a; 2.1 关于样本情况 2.2 适合检查的情况 2.2.1 单样本均值检验&#xff08;One-sample t-test&#xff09; 2.2.2 两独立样本均值检验&#xff08;Independent …

hydra九头蛇

一、hydra简介 Hydra是一款非常强大的暴力破解工具&#xff0c;它是由著名的黑客组织THC开发的一款开源暴力破解工具。Hydra是一个验证性质的工具&#xff0c;主要目的是&#xff1a;展示安全研究人员从远程获取一个系统认证权限。 目前该工具支持以下协议的爆破&#xff1a; A…

【网站项目】农业信息管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

[C#]OpenCvSharp改变图像的对比度和亮度

目的 访问像素值mat.At<T>(y,x) 用0初始化矩阵Mat.Zeros 饱和操作SaturateCast.ToByte 亮度和对比度调整 g(x)αf(x)β 用α(>0)和β一般称作增益(gain)和偏置(bias)&#xff0c;分别控制对比度和亮度 把f(x)看成源图像像素&#xff0c;把g(x)看成输出图像像素…

身份证实名认证接口的价格一般是多少呢?基于PHP身份核验接口

身份证实名认证接口分为身份证二要素、三要素、三要素人像核验接口&#xff0c;被广泛的应用于婚恋、交友、电商等等一系列行业领域&#xff0c;身份证实名认证需要实时数据&#xff0c;对于数据源来说也需要可靠&#xff0c;那么&#xff0c;身份证实名认证的价格是不是很贵呢…

小小算式(1 + 2) * (3 + 4)背后的大道理

目录 前缀表示法&#xff08;波兰表达式&#xff09; 中缀表达法 后缀表达法&#xff08;逆波兰表达式&#xff09; 三种表达法的相互转换 练习&#xff1a;逆波兰表达式求值 前缀表示法&#xff08;波兰表达式&#xff09; 波兰表示法&#xff08;英语&#xff1a;Polis…

Python学习,记录不熟悉知识点

目录 Set&#xff08;集合&#xff09; 集合内置方法完整列表 根据字符串的表达式计算结果 ​编辑 条件控制&#xff1a; if – elif – else match...case 循环语句&#xff1a; while循环 for循环 在同一行中有多个赋值操作&#xff08;先计算&#xff0c;再赋值&…

【Vue】Vue3中的OptionsAPI与CompositionAPI

文章目录 OptionsAPICompositionAPI对比总结 OptionsAPI 中文名:选项式API通过定义methods,computed,watch,data等属性方法&#xff0c;处理页面逻辑。以下是OptionsAPI代码结构 实例代码: <script lang"ts">// js或者tsimport { defineComponent } from vu…

javaScript手写专题——防抖/节流/闭包/Promise/深浅拷贝

目录 目录 一、 防抖/节流/闭包/定时器 编写一个组件&#xff0c;在input中输入文本&#xff0c;在给定的数据中查找相关的项目&#xff0c;并渲染搜索结果列表 1.新增InputSearch.vue组件 key的作用 2.新增 InputView.vue 3.添加路由 4.效果演示 follow up加上防抖怎么处理 1.…

「51媒体网」邀请媒体采访报道对企业宣传有何意义?

传媒如春雨&#xff0c;润物细无声的&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 邀请媒体采访报道对企业宣传具有多重意义&#xff1a; 提升品牌知名度和曝光度&#xff1a;媒体是信息传播的重要渠道&#xff0c;通过媒体的报道&#xff0c;企业及其活动、产品能够迅…

软考信息处理技术员2024年5月报名流程及注意事项

2024年5月软考信息处理技术员报名入口&#xff1a; 中国计算机技术职业资格网&#xff08;http://www.ruankao.org.cn/&#xff09; 2024年软考报名时间暂未公布&#xff0c;考试时间上半年为5月25日到28日&#xff0c;下半年考试时间为11月9日到12日。不想错过考试最新消息的…

Sketch是免费软件吗?这款软件支持导入!

Sketch 是一款针对网页、图标、插图等设计的矢量绘图软件。Sketch 的操作界面非常简单易懂&#xff0c;帮助全世界的设计师创作出许多不可思议的作品。但是同时&#xff0c;Sketch 也有一些痛点&#xff1a;使用 Sketch 需要安装 InVision、Abstract 、Zeplin 等插件&#xff0…

配置 施耐德 modbusTCP 分布式IO子站 PRA0100

模块官方介绍&#xff1a;https://www.schneider-electric.cn/zh/product/BMXPRA0100 1. 总体步骤 2. 软件组态&#xff1a;在 Unity Pro 软件中创建编辑 PRA 模块工程 2.1 新建项目 模块箱硬件型号如下 点击 Unity Pro 软件左上方【新建】按钮&#xff0c;选择正确的 DIO …

Filter Listener Interceptor

文章目录 第一章 Filter1. 目标2. 内容讲解2.1 Filter的概念2.2 Filter的作用2.3 Filter的入门案例2.3.1 案例目标2.3.2 代码实现2.3.2.1 创建ServletDemo012.3.2.2 创建EncodingFilter 2.4 Filter的生命周期2.4.1 回顾Servlet生命周期2.4.1.1 Servlet的创建时机2.4.1.2 Servle…

git提交代码时报错,提不了

问题 今天在换了新电脑&#xff0c;提交代码时报错 ✖ eslint --fix found some errors. Please fix them and try committing again. ✖ 21 problems (20 errors, 1 warning) husky > pre-commit hook failed (add --no-verify to bypass) 解决 通过 --no-verify 解决&…

程序员如何搞副业

#程序员如何搞副业&#xff1f;# 在快速发展的IT行业中&#xff0c;程序员作为技术骨干&#xff0c;通常拥有扎实的编程能力和丰富的项目经验。然而&#xff0c;随着职业生涯的深入&#xff0c;许多程序员开始思考如何进一步提升自我价值&#xff0c;实现更多的经济收益。副业成…