当前位置: 首页 > news >正文

【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】命令行解释器

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

开源一个通用的 HTTP 请求前端组件

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

等保测评机构资质申请条件是什么?个人可以申请吗?

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

android 卡顿、ANR优化(1)屏幕刷新机制

前言&#xff1a; 本文通过阅读各种文章和源码总结出来的&#xff0c;如有不对&#xff0c;还望指出 目录 正文 基础概念 视觉暂留 逐行扫描 帧 CPU/GPU/Surface&#xff1a; 帧率、刷新率、画面撕裂 画面撕裂 Android屏幕刷新机制的演变 单缓存&#xff08;And…...

Landsat8中*_MTL.txt文件详解

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

好的提高代码质量的方法有哪些?有什么经验和技巧?

用于确保代码质量的6个高层策略&#xff1a; 1 编写易于理解的代码 考虑如下这段文本。我们有意地使其变得难以理解&#xff0c;因此&#xff0c;不要浪费太多时间去解读。粗略地读一遍&#xff0c;尽可能吸收其中的内容。 〓ts〓取一个碗&#xff0c;我们现在称之为A。取一…...

yum保留安装包

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

ERP系统哪家比较好?

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

Python读写mdb文件的实战代码

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

MAC和IP地址在字符串形式、数字形式和byte数组中的转换

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

时间轮来优化定时器

在raft协议中&#xff0c; 会初始化三个计时器是和选举有关的&#xff1a; voteTimer&#xff1a;这个timer负责定期的检查&#xff0c;如果当前的state的状态是候选者&#xff08;STATE_CANDIDATE&#xff09;&#xff0c;那么就会发起选举 electionTimer&#xff1a;在一定时…...

《和AI交朋友》教学设计——初识人工智能

创新整合点 &#xff08;1借助编程软件的机器学习扩展&#xff0c;使学生初步体验建立训练模型&#xff0c;让电脑进行学习的过程&#xff0c;进而感受人工智能的核心技术之一。 &#xff08;2&#xff09;借助编程软件的人工智能服务&#xff0c; 在编写程序时使用语音交互模块…...

机载雷达的时间简史

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

2018年MathorCup数学建模A题矿相特征迁移规律研究解题全过程文档及程序

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

如何在 Python 中创建对象列表

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

Canny算法原理和应用

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

数据挖掘(2.2)--数据预处理

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

JVM堆与堆调优以及出现OOM如何排查

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

Springboot——自定义Filter使用测试总结

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

软件测试(进阶篇)(1)

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

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...

Leetcode 3576. Transform Array to All Equal Elements

Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接&#xff1a;3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到&#xf…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望

文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例&#xff1a;使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例&#xff1a;使用OpenAI GPT-3进…...

遍历 Map 类型集合的方法汇总

1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)

一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解&#xff0c;适合用作学习或写简历项目背景说明。 &#x1f9e0; 一、概念简介&#xff1a;Solidity 合约开发 Solidity 是一种专门为 以太坊&#xff08;Ethereum&#xff09;平台编写智能合约的高级编…...

Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?

Redis 的发布订阅&#xff08;Pub/Sub&#xff09;模式与专业的 MQ&#xff08;Message Queue&#xff09;如 Kafka、RabbitMQ 进行比较&#xff0c;核心的权衡点在于&#xff1a;简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...

JAVA后端开发——多租户

数据隔离是多租户系统中的核心概念&#xff0c;确保一个租户&#xff08;在这个系统中可能是一个公司或一个独立的客户&#xff09;的数据对其他租户是不可见的。在 RuoYi 框架&#xff08;您当前项目所使用的基础框架&#xff09;中&#xff0c;这通常是通过在数据表中增加一个…...

Java毕业设计:WML信息查询与后端信息发布系统开发

JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发&#xff0c;实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构&#xff0c;服务器端使用Java Servlet处理请求&#xff0c;数据库采用MySQL存储信息&#xff0…...

SQL慢可能是触发了ring buffer

简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...

Selenium常用函数介绍

目录 一&#xff0c;元素定位 1.1 cssSeector 1.2 xpath 二&#xff0c;操作测试对象 三&#xff0c;窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四&#xff0c;弹窗 五&#xff0c;等待 六&#xff0c;导航 七&#xff0c;文件上传 …...