【Hello Linux】命令行解释器
作者:@小萌新
专栏:@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)分析需求,细化需求,从需求中提取出测试项,根据测试项找到测试点,根据测试点具体的来进行设计测试…...
大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...
家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...
NPOI操作EXCEL文件 ——CAD C# 二次开发
缺点:dll.版本容易加载错误。CAD加载插件时,没有加载所有类库。插件运行过程中用到某个类库,会从CAD的安装目录找,找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库,就用插件程序加载进…...
探索Selenium:自动化测试的神奇钥匙
目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...
Unity UGUI Button事件流程
场景结构 测试代码 public class TestBtn : MonoBehaviour {void Start(){var btn GetComponent<Button>();btn.onClick.AddListener(OnClick);}private void OnClick(){Debug.Log("666");}}当添加事件时 // 实例化一个ButtonClickedEvent的事件 [Formerl…...
从面试角度回答Android中ContentProvider启动原理
Android中ContentProvider原理的面试角度解析,分为已启动和未启动两种场景: 一、ContentProvider已启动的情况 1. 核心流程 触发条件:当其他组件(如Activity、Service)通过ContentR…...
