相亲网站上做投资的女生/佛山百度推广电话
作者:@小萌新
专栏:@Linux
作者简介:大二学生 希望能和大家一起进步!
本篇博客简介:使用进程的基础知识和进程控制知识做出一个简单的shell程序
命令行解释器
- 介绍
- 搭架子
- 缓冲区
- 获取命令
- 如何从标准输入中获取字符串
- 解析命令
- strtok分隔字符串
- 进程替换
- 内建命令
- 工作目录
- chdir函数
- 全部代码
介绍
我们从之前的学习可以知道 实际上我们的命令行解释器就是一个进程 bash
我们在执行例如 ls ll这些命令的时候实际上就是 bash 在创建子进程 让子进程执行这些命令 那么我们能不能模仿 bash 做一个简单的命令行解释器呢?
答案显然是可以的
本篇博客会使用到进程相关概念以及进程控制相关知识 如果没有前置知识的储备建议先看我之前写的两篇博客
进程相关概念
进程控制相关
搭架子
我们首先先观察我们自己使用的命令行是什么格式的
我们发现 这里的格式就是 用户名@主机名+地址
我们可以直接复制到我们的程序中使用
int main()
{ while(1) { printf("[root@VM-8-3-centos lesson11]# "); sleep(1); } return 0;
}
我们之后运行这个程序
我们发现 这个程序竟然没有任何输出的内容
这是为什么呢?
缓冲区
我们这里再次介绍下缓冲区的概念
为了提高效率 我们在打印数据的时候并不是直接打印到输出设备当中而是会先把数据存放到一个叫做输出缓冲区的地方 直到我们主动去刷新这个输出缓冲区里面的数据才会打印到输出设备
输入同理
我们平时打印语句的时候一般会在末尾加上一个 \n 换行符 实际上它就具有刷新缓冲区的功能
此外我们还可以使用fflush(stdout) 去主动刷新缓冲区
至于stdout是什么 我们在后面的基础IO部分会着重讲解
加上刷新缓冲区代码之后我们的代码如下
int main(){while(1){printf("[root@VM-8-3-centos lesson11]# ");fflush(stdout);sleep(10); }return 0;}
之后我们再次编译运行便会出现这样的情况
我们现在已经可以基本模拟bash的框架了
当然为了区分我们自己的minishell和bash 我们将最前面的用户改为shy
获取命令
搭好架子之后我们就可以开始模仿bash的行为了
bash的本质是一个命令行解释器
所以说我们必须要先获命令是什么
我们可以使用字符数组来获取我们输入的命令
我们定义一个字符数组 它的默认大小为128
#define NUM = 128
char command[128];
之后我们每次输入命令之后要清空字符串 这里有一种十分简便的方式
command[0] = 0;
因为这个字符数组的第一位被设置为了0 所以说这个字符串就是为空的
如何从标准输入中获取字符串
我们可以使用c语言中的scanf来获取我们输入的字符串吗
答案显然是不行的
因为我们输入的命令是这样子的格式
ls -a -l
我们可以发现 中间使用了空格号分割 但是我们的scanf遇到空格就是停止读取
这样子我们就只能使用另外的函数
这里我们使用fgets这个函数
这个函数具有三个参数 我们下面分别介绍下这三个参数
char* str
是我们要读取数据到哪里
int num
是我们要读取数据的个数
FILE* stream
是我们要从哪个文件读取数据
所以我们可以这么写
fgets(command , NUM , stdin);
它的意思是从标准输入流中读取数据 读取NUM个数据到command中
我们实验下到目前为止的代码
8 #define NUM 1209 10 11 int main()12 {13 char command[NUM];14 while(1)15 {16 command[0] = 0; 17 printf("[shy@VM-8-3-centos lesson11]# ");18 fgets(command, NUM , stdin);19 printf("echo: %s",command);20 fflush(stdout);21 sleep(10);22 }23 return 0;24 }
我们编译运行下试试看
我们输入在缓冲区的数据确实是被打印到屏幕上了
可是为什么这里多打了一个换行呢?(/n)
这里其实是因为我们敲回车的时候往缓冲区多输入了一个/n
要解决这个办法也很简单 我们找到这个/n字符的所在位置 将它置0就好了
它在缓冲区中应该是这个样子的
我们只要将字符串有效字符后一位置0就可以了
我们使用strlen测出长度之后减去一便是\n的下标
将它置0就可以
代码表示如下
command[strlen(str)-1];
这样就能完美获取标准输入中的字符串了
解析命令
我们输入的命令是这样子的格式
要将这一串字符串解析成一个个的命令我们必须要分隔字符串
在c语言中 我们使用strtok命令来分隔字符串
strtok分隔字符串
它的返回值会返回我们分隔字符串的第一个字符位置
它有两个参数
第一个参数 char * str
表示要分割的字符串
第二个参数 const char *delim
表示分隔符
如果我们想要一直获取同一个字符串 则第一个参数在后面的时候都要传入空指针
我们可以写一段代码验证下这个函数的使用
#include <stdio.h>
#include <string.h>int main()
{char str[] = "Hello, World! Welcome to C programming.";const char delim[] = " ,.!"; // 以空格、逗号和句号作为分隔符char *token;// 获取第一个子字符串token = strtok(str, delim);// 依次获取剩下的子字符串while (token != NULL){printf("%s\n", token);token = strtok(NULL, delim);}return 0;
}
它运行的结果如下
我们使用strtok去获取我们的指令到一个字符数组中
24 int i = 1;25 char* delim = " ";26 argv[0] = strtok(command , delim);27 while(argv[i] = strtok(NULL , delim))28 {29 i++;30 }
进程替换
我们这里要想清楚一点 绝对不能使用父进程去进行进程替换
因为如果我们使用父进程进行进程替换的话父进程的代码和数据就会全部被覆盖
这样子的话命令行就成一次性工具了
所以说我们的父进程需要创建一个子进程去做这个任务
父进程在此时等待子进程做完任务回收资源
代码表示如下
32 if (fork() == 0) 33 { 34 // child 35 execvp(aegv[0] , argv); 36 exit(1); 37 } 38 39 waitpid(-1 , NULL , 0);
此时我们的程序就基本完成了
我们运行下试试效果
我们发现它基本上可以模仿bash进程的各种行为了
中途有一次我敲击了 ll 命令系统没有反应是因为我们没有定义这个命令
内建命令
内建命令 就是由 Bash 自身提供的命令
但是我们目前的代码仍然会遇到一些问题
我们敲出下面的命令
我们发现我们使用cd … 命令之后竟然没有回到上一级目录 这是为什么呢
工作目录
实际上我们的进程中有一个叫做工作目录的东西
我们可以在 /proc/进程号中看到
该路径也叫做进程的当前路径
我们使用cd …命令的时候实际上就是对于这个路径在进行操作
回到我们之前的问题 为什么我们使用cd命令的时候系统不能识别呢?
因为我们是通过子进程去调用的cd命令 子进程有着自己的进程目录
它的修改经过写时拷贝不会影响父进程
所以说我们如果是遇到内建命令的话就需要父进程自己去执行之
那么我们如何改变父进程的工作目录呢?
chdir函数
我们可以通过chdir函数来改变 当前进程的工作目录
它的使用方式如下
我们直接在它后面输入我们要改变的路径就可以
知道如何修改之后我们继续编写代码
32 // 内建命令33 if (strcmp(argv[0], "cd") == 0)34 {35 if (argv[1] != NULL)36 {37 chdir(argv[1]);38 }39 40 continue; // 不执行下面了 41 }
这样子我们的所有代码就完成了
全部代码
1 #include <stdio.h>2 #include <stdlib.h>3 #include <unistd.h>4 #include <sys/wait.h>5 #include <sys/types.h>6 #include <string.h>7 8 #define NUM 1209 #define CMD_NUM 4010 11 int main()12 {13 char command[NUM];14 char* argv[CMD_NUM];15 while(1)16 {17 command[0] = 0;18 printf("[shy@VM-8-3-centos lesson11]# ");19 fflush(stdout);20 fgets(command, NUM , stdin);21 command[strlen(command)-1] = 0;22 // printf("echo: %s",command);23 // ½âÎöÃüÁî24 int i = 1;
W> 25 char* delim = " ";26 argv[0] = strtok(command , delim);
W> 27 while(argv[i] = strtok(NULL , delim))28 {29 i++;30 }31 32 // ÄÚ½¨ÃüÁî33 if (strcmp(argv[0], "cd") == 0)34 {35 if (argv[1] != NULL)36 {37 chdir(argv[1]);38 }39 40 continue; // ÏÂÃæ²»Ö´ÐÐÁË 41 }42 43 if (fork() == 0)44 {45 // child 46 execvp(argv[0] , argv);47 exit(1);48 }49 50 waitpid(-1 , NULL , 0);51 }52 return 0;53 }
相关文章:

【Hello Linux】命令行解释器
作者:小萌新 专栏:Linux 作者简介:大二学生 希望能和大家一起进步! 本篇博客简介:使用进程的基础知识和进程控制知识做出一个简单的shell程序 命令行解释器介绍搭架子缓冲区获取命令如何从标准输入中获取字符串解析命令…...

开源一个通用的 HTTP 请求前端组件
像 Postman 这样可视化的 HTTP 请求工具是调试 API 不可或缺的利器。Postman 虽好但也越来越重,而且如果要整合到其他工具中,显然 Postman 又不是一个可行的方案。于是我想打造一个简单的前端组件(widget),它是一个标准…...

等保测评机构资质申请条件是什么?个人可以申请吗?
最近看到不少网友在问,等保测评机构资质申请条件是什么?个人可以申请吗?今天我们小编就来给大家详细回答一下。 等保测评机构资质申请条件是什么?个人可以申请吗? 【回答】:首先需要明确一点的是…...

android 卡顿、ANR优化(1)屏幕刷新机制
前言: 本文通过阅读各种文章和源码总结出来的,如有不对,还望指出 目录 正文 基础概念 视觉暂留 逐行扫描 帧 CPU/GPU/Surface: 帧率、刷新率、画面撕裂 画面撕裂 Android屏幕刷新机制的演变 单缓存(And…...

Landsat8中*_MTL.txt文件详解
01 什么是*_MTL.txt文件?所有的Landsat8 1级数据产品中均包含MTL.txt(Metadata File)文件。Landsat MTL文件包含对数据的系统搜索和归档分类有益的信息。该文件还包含关于数据处理和恶对增强陆地卫星数据有重要价值的信息(例如转换为反射率和辐射亮度&am…...

好的提高代码质量的方法有哪些?有什么经验和技巧?
用于确保代码质量的6个高层策略: 1 编写易于理解的代码 考虑如下这段文本。我们有意地使其变得难以理解,因此,不要浪费太多时间去解读。粗略地读一遍,尽可能吸收其中的内容。 〓ts〓取一个碗,我们现在称之为A。取一…...

yum保留安装包
一. 用downloadonly下载 1.1 处理依赖关系自动下载到/tmp/pages目录,pages这个目录会自动创建 yum install --downloadonly --downloaddir/tmp/pages ceph-deploy注意,如果下载的包包含了任何没有满足的依赖关系,yum将会把所有的依赖关系包下…...

ERP系统哪家比较好?
ERP系统哪家好?在选择ERP系统时,我们可以按照这三个维度,然后再按照需求去选择ERP系统。 市面上ERP软件大概可以分为三大类: ① 标准ERP应用:功能比较固定,难以满足个性化需求,二次开发难度很高…...

Python读写mdb文件的实战代码
大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。喜欢通过博客创作的方式对所学的知识进行总结与归纳,不仅形成深入且独到的理…...

MAC和IP地址在字符串形式、数字形式和byte数组中的转换
MAC地址 mac地址作为网卡的物理地址,有6个byte的长度。在实际表示形式上,以每个字节的16进制,中间用冒号隔开,比如:“01:02:03:04:05:06”。这就是mac地址的字符串形式 而在网络通信传输中,需要对mac地址从字符串形式转换为数字形式或byte数组形式发送。并且网络上传输…...

时间轮来优化定时器
在raft协议中, 会初始化三个计时器是和选举有关的: voteTimer:这个timer负责定期的检查,如果当前的state的状态是候选者(STATE_CANDIDATE),那么就会发起选举 electionTimer:在一定时…...

《和AI交朋友》教学设计——初识人工智能
创新整合点 (1借助编程软件的机器学习扩展,使学生初步体验建立训练模型,让电脑进行学习的过程,进而感受人工智能的核心技术之一。 (2)借助编程软件的人工智能服务, 在编写程序时使用语音交互模块…...

机载雷达的时间简史
从地基起步 蝙蝠,虽然像人一样拥有双眼,但它看起东西来,用到的却不是眼睛。蝙蝠从鼻子里发出的超声波在传输过程中遇到物体后会立刻反弹,根据声波发射和回波接收之间的时间差,蝙蝠就可以轻易地判断出物体的位置。这一工…...

2018年MathorCup数学建模A题矿相特征迁移规律研究解题全过程文档及程序
2018年第八届MathorCup高校数学建模挑战赛 A题 矿相特征迁移规律研究 原题再现: 背景材料: 球团矿具有含铁品位高、粒度均匀、还原性能好、机械强度高、微气孔多等特性, 是高炉炼铁的重要原料之一。近年来国内外普遍认识到球团矿高温状态下冶金性能是评价炉料…...

如何在 Python 中创建对象列表
Python 中要创建对象列表: 声明一个新变量并将其初始化为一个空列表。使用 for 循环迭代范围对象。实例化一个类以在每次迭代时创建一个对象。将每个对象附加到列表中。 class Employee():def __init__(self, id):self.id idlist_of_objects []for i in range(5…...

Canny算法原理和应用
Canny算法的原理使用高斯滤波器滤波使用 Sobel 滤波器滤波获得在 x 和 y 方向上的输出,在此基础上求出梯度的强度和梯度的角度edge为边缘强度,tan为梯度方向上图表示的是中心点的梯度向量、方位角以及边缘方向(任一点的边缘与梯度向量正交&am…...

数据挖掘(2.2)--数据预处理
目录 二、数据描述 1.描述数据中心趋势 1.1平均值和截断均值 1.2加权平均值 1.3中位数(Median)和众数(Mode) 2.描述数据的分散程度 2.1箱线图 2.2方差和标准差 2.3正态分布 3.数据清洗 3.1数据缺失的处理 3.2数据清洗 二、数据描述 描述数…...

JVM堆与堆调优以及出现OOM如何排查
调优的位置——堆 Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的。 类加载器读取了类文件后,一般会把什么东西放到堆中?类,方法,常量,变量~,保存我们所有引用类型的真实对象; 堆内存中…...

Springboot——自定义Filter使用测试总结
文章目录前言自定义过滤器并验证关于排除某些请求的方式创建测试接口请求测试验证异常过滤器的执行流程注意事项资料参考前言 在Java-web的开发领域,对于过滤器和拦截器用处还是很多,但两者的概念却极易混淆。 过滤器和拦截器都是采用AOP的核心思想&am…...

软件测试(进阶篇)(1)
一)如何根据需求来设计测试用例? 1)验证功能的正确性,合理性,无二义性,逻辑要正确 2)分析需求,细化需求,从需求中提取出测试项,根据测试项找到测试点,根据测试点具体的来进行设计测试…...

(七十三)大白话深入探索多表关联的SQL语句到底是如何执行的?(1)
今天我们来继续跟大家聊聊多表关联语句是如何执行的这个问题,上次讲了一个最最基础的两个表关联的语句和执行过程,其实今天我们稍微来复习一下,然后接着上次的内容,引入一个“内连接”的概念来。 假设我们有一个员工表࿰…...

SYSU程设c++(第三周) 对象类、类的成员、类与结构体的区别、类的静态成员
对象&类 类用于指定对象的形式,它包含数据的表示方法和用于处理数据的方法。 • 类中的数据和方法称为类的成员。 • 函数在一个类中也被称为类的成员。 定义一个类,其效果是定义一个数据类型的蓝图。它定义了类的对象包括了什么,以及可…...

Redis管道
目录 1、什么是管道? 2、案例演示 3、注意事项 4、面试题 1、什么是管道? 管道(pipeline)可以一次性发送多条命令给服务端,服务端依次处理完,通过一条响应一次性将结果返回,减少 IO 的次数&…...

conda的共用package[硬链接]@pytorch和tensorflow装在同一个环境里好不好?
文章目录refpackage复用(指定同版本)conda install 比pip install 更可能节省空间pytorch和tensorflow装在同一个环境里?导入依赖导入依赖试验ref python - Can packages be shared across Anaconda environments? - Stack OverflowManaging environments — conda 23.1.0.p…...

「Vue面试题」动态给vue的data添加一个新的属性时会发生什么?怎样去解决的?
一、直接添加属性的问题 我们从一个例子开始 定义一个p标签,通过v-for指令进行遍历 然后给botton标签绑定点击事件,我们预期点击按钮时,数据新增一个属性,界面也 新增一行 <p v-for"(value,key) in item" :key&q…...

Flutter-Scaffold组件
在Flutter开发当中,我们可能会遇到以下的需求:实现页面组合使用,比如说有悬浮按钮、顶部菜单栏、左右抽屉侧边栏、底部导航栏等等效果。Scaffold组件可以帮我们实现上面需求说的效果。这篇博客主要分享容器组件的Scaffold组件的使用ÿ…...

Postman简介及接口测试流程(小菜鸟攻略)
目录 前言 一、常见接口 二、前端和后端 三、什么是接口测试 四、接口组成 1、接口说明 2、调用url 3、请求方法(get\post) 4、请求参数、参数类型、请求参数说明 5、返回参数说明 五、为什么要做接口测试 本章主要介绍如何使用postman做接口…...

kubebuilder注释
标记语法Empty kubebuilder:validation:Optional:空标记像命令行中的布尔标记位-- 仅仅是指定他们来开启某些行为。Anonymous kubebuilder:validation:MaxItems2:匿名标记使用单个值作为参数。Multioption kubebuilder:printcolumn:JSONPath".statu…...

java日志
日志是软件开发的重要组成部分。一个精心编写的日志代码提供快速的调试,维护方便,以及应用程序的运行时信息结构化存储。日志记录确实也有它的缺点。它可以减缓的应用程序Log4jLog4j是Apache的一个开放源代码项目,通过使用Log4j,我…...

研发中台拆分过程的一些心得总结
背景在 21 年,中台拆分在 21 年,以下为中台拆分的过程心得,带有一定的主观,偏向于中小团队中台建设参考(这里的中小团队指 3-100 人的团队),对于大型团队不太适用,毕竟大型团队人中 …...