详解简单的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;是因为其对计算机资源的高需求。 电影特效渲染费时间原…

LangChain - 文本嵌入

文章目录 一、关于 文本嵌入模型二、入门1、设置 OpenAI2、embed_documents3、embed_query 三、集成示例1、DashScope2、OpenAI3、Hugging Face Hub4、Fake Embeddings 本文转载改编自&#xff1a; https://python.langchain.com.cn/docs/modules/data_connection/text_embeddi…

Java对象转型

&#xff08;一&#xff09;向上转型 向上转型&#xff08;自动转型&#xff09;&#xff0c;指子类类型转父类类型 //父类 public class Father {public String fatherAttr "父类属性";public void fatherMethod(){System.out.println("父类成员方法")…

1077:统计满足条件的4位数

1077&#xff1a;统计满足条件的4位数 时间限制: 1000 ms 内存限制: 65536 KB 提交数:79300 通过数: 54638 【题目描述】 给定若干个四位数&#xff0c;求出其中满足以下条件的数的个数&#xff1a;个位数上的数字减去千位数上的数字&#xff0c;再减去百位数上的数…

2024.3.29力扣每日一题——元素和最小的山形三元组1

2024.3.29 题目来源我的题解方法一 暴力解法方法二 规律 题目来源 力扣每日一题&#xff1b;题序&#xff1a;2908 我的题解 方法一 暴力解法 使用三层循环&#xff0c;分别控制左边界、峰值、右边界&#xff0c;依次遍历。 时间复杂度&#xff1a;O( n 3 n^3 n3) 空间复杂度…

Redis的三种部署方案

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

MYSQL数据库故障排除与优化

目录 MySQL 单实例故障排查 MySQL 主从故障排查 MySQL 优化 MySQL 单实例故障排查 故障现象 1 ERROR 2002 (HY000): Cant connect to local MySQL server through socket /data/mysql/mysql.sock (2) 问题分析&#xff1a;以上这种情况一般都…

80V输入1.5A,DC/DC高效率降压型电源芯片--ZCC2480

产品概述&#xff1a; ZCC2480 是一款内部集成有功率 MOSFET 管的降压型开关稳压器。以电流模式控制方式达到快速 环路响应并提高环路的稳定性。宽范围输入电压&#xff08; 4.5 V 至 80V &#xff09;提供最大 1.5A 电流的高效率输出&#xff0c; 可在移动环境输入的条件下实现…

Java编程规范及最佳实践

文章目录 一、命名规范二、代码风格规范三、注释规范四、推荐的编程实践五、类和接口六、异常处理七、可见性八、并发九、代码复用十、代码组织和模块化十一、Java集合框架十二、输入验证十三、资源管理十四、文档和注释十五、测试和代码质量十六、代码可读性十七、性能优化十八…

Django之REST Client插件

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

[C++][C++类型转换]详解

目录 1.C语言中的类型转换2.为什么C需要四种类型转换&#xff1f;3.C强制类型转换1.static_cast2.reinterpret_case3.const_cast4.dynamic_cast 4.RTTI(了解) 1.C语言中的类型转换 在C语言中&#xff0c;如果赋值运算符左右两侧类型不同&#xff0c;或者形参与实参类型不匹配&…

【小白学机器学习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 …

2024.4.6力扣每日一题——树节点的第 K 个祖先

2024.4.6 题目来源我的题解方法一 哈希表 超内存方法二 树上倍增 题目来源 力扣每日一题&#xff1b;题序&#xff1a;1483 我的题解 方法一 哈希表 超内存 使用一个哈希表存储每个节点的祖先节点。 时间复杂度&#xff1a;O(n) 空间复杂度&#xff1a;O( n 2 n^2 n2) class…

hydra九头蛇

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

2024年华为OD机试真题-启动多任务排序-Java-OD统一考试(C卷)

题目描述: 一个应用启动时,会有多个初始化任务需要执行,并且任务之间有依赖关系,例如A任务依赖B任务,那么必须在B任务执行完成之后,才能开始执行A任务。 现在给出多条任务依赖关系的规则,请输入任务的顺序执行序列,规则采用贪婪策略,即一个任务如果没有依赖的任务,则…

蓝桥杯小白入门赛第9场第4题 字典树考试

问题描述 蓝桥学院最近教学了字典树这一数据结构,小蓝是全班的第一名,他不仅掌握了普通字典树,还自学了 01 字典树的使用。为了展示自己的能力,他向全班同学出了以下问题: 给定一个长度为 N N N 的数组 A A A , 你能否求出表达式 ∑ i = 1 N ∑ j = i + 1 N f ( A i …

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

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

基于STM32f103芯片的应用程序在线升级功能框架的实现

目录 基于STM32f103芯片的应用程序在线升级功能框架的实现 一、原理简介 二、KEIL软件主要设置 三、应用程序app部分 四、Bootloader部分 五、补充部分 基于STM32f103芯片的应用程序在线升级功能框架的实现 一、原理简介 我们在使用stm32的过程中&#xff0c;如果需要对…

[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)看成输出图像像素…