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

《Linux从练气到飞升》No.21 Linux简单实现一个shell

🕺作者: 主页

我的专栏
C语言从0到1
探秘C++
数据结构从0到1
探秘Linux
菜鸟刷题集

😘欢迎关注:👍点赞🙌收藏✍️留言

🏇码字不易,你的👍点赞🙌收藏❤️关注对我真的很重要,有问题可在评论区提出,感谢阅读!!!

文章目录

    • 前言
    • 01. 框架搭建
    • 02. 打印提示信息
    • 03. 获取用户键盘输入
      • 如何获取用户在命令行的输入呢?
    • 04. 命令行字符串解析
    • 05. 创建子进程执行命令
      • 怎么知道要调用的程序在哪里呢?
      • 为什么要替换?
      • 环境变量相关的数据,会被替换吗??
    • 06. 内置命令 —— cd
    • 07. 内置命令 —— export
      • shell 执行的命令通常有两种
      • shell的环境变量从哪里来的?(了解)
    • 08. 类似ll这种别名命令无法识别
    • 后记

前言

前面我们讲述了进程的相关知识,包括进程创建、进程等待、进程替换等,这些我们都在Linux上进行了测试,并且通常使用的shell来执行命令,那么我们能不能自己来实现一个简单的shell呢?

我们知道在shell上执行命令时,其原理不过也只是调用和执行文件罢了,也就是创建进程来执行程序,而shell一般是不退出的,那么我们现在开始玩一下

01. 框架搭建

命令行解释器一定是一个常驻内存的进程,不退出,所以我们使用while包起来

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>#define NUM 1024
#define SIZE 32
#define SEP " "//保存完整的命令行字符串
char cmd_line[NUM];//保存打散之后的命令行字符串
char *g_argv[SIZE];// 写一个环境变量的buffer,用来测试
char g_myval[64];// shell 运行原理 : 通过让子进程执行命令,父进程等待&&解析命令
int main()
{extern char**environ;//0. 命令行解释器,一定是一个常驻内存的进程,不退出while(1){}
}

02. 打印提示信息

参考shell的提示信息:
在这里插入图片描述
它们都有各自的含义和获取方式,但是这里为了简化,不考虑这些细枝末节,由大家自己改进!
我这里就写死了哦~

我们可以直接使用printf函数打印,但是会有一个问题,如果设置了\n,它会换行,但是我们使用shell时并不会换行,那么我们就需要用到fflush函数来冲刷缓存区。

 //1. 打印出提示信息 [venus@localhost myshell]# printf("[root@localhost myshell]# ");fflush(stdout);

03. 获取用户键盘输入

如何获取用户在命令行的输入呢?

我们用一个数组来存储命令,使用fgets函数来获取输入
步骤:

  • 先初始化数组
  • 获取存储命令
  • 将最后的回车符号设置为'\0'
//2. 获取用户的键盘输入[输入的是各种指令和选项: "ls -a -l -i"]if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL){continue;}cmd_line[strlen(cmd_line)-1] = '\0';

04. 命令行字符串解析

到这一步,我们已经将命令行的字符串存储到数组中了,接下来就是解析它
步骤:

  • 这里要使用strtok函数来裁剪字符串
  • 将存储的命令和系统内部命令做比对,如果有就执行
g_argv[0] = strtok(cmd_line, SEP); //第一次调用,要传入原始字符串
int index = 1;
if(strcmp(g_argv[0], "ls") == 0)
{g_argv[index++] = "--color=auto";//加入配色
}
while(g_argv[index++] = strtok(NULL, SEP)); //第二次,如果还要解析原始字符串,传入NULL

05. 创建子进程执行命令

怎么知道要调用的程序在哪里呢?

直接使用进程替换,使用execvp函数,它可以直接使用环境变量不用自己写了,也就是直接掉用系统中的指令的程序来使用即可。

为什么要替换?

一切和应用场景有关,我们有时候必须要让子进程执行新的程序

环境变量相关的数据,会被替换吗??

没有!它不会被替换,它会把父进程的环境变量拷贝继承过来,它具有全局属性

pid_t id = fork();
if(id == 0) //child
{printf("下面功能让子进程进行的\n");execvp(g_argv[0], g_argv); // ls -a -l -iexit(1);
}
//father
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret > 0) printf("exit code: %d\n", WEXITSTATUS(status));

此时程序基本功能就已经实现了

但是,我们发现一个问题,使用cd命令时,他的路径不会改变,这是个bug

原因是:

  • 在cd的时候,自己写的shell都会执行execvp,它只会影响子进程的路径

  • 但是我们需要改变父进程的路径,所以像cd这种命令,我们不想让子进程去执行它而让父进程去执行它

  • 这种让父进程自己执行的命令叫做内置命令、内建命令,它的本质是shell中的一个函数调用

我们来修改下功能

06. 内置命令 —— cd

这里可以使用chdir函数来实现

chdir函数可以改变文件路径

我们可以使用下面代码,使得cd命令的使用合理,但是可能其他类似的命令也会出现相似的bug,需要一一比对实现,这里仅针对cd命令

if(strcmp(g_argv[0], "cd") == 0) //not child execute, father execute
{if(g_argv[1] != NULL) chdir(g_argv[1]); //cd path, cd ..continue;
}

07. 内置命令 —— export

上面我们讲了cd的bug,而export也和cd一样,也需要进行处理,export的作用是导入环境变量,我们既不想覆盖父进程的环境变量,又想导入自己的环境变量,该怎么做呢?

代码如下:

//导入环境变量
//比较第一个是不是export
if(strcmp(g_argv[0], "export") == 0 && g_argv[1] != NULL)
{strcpy(g_myval, g_argv[1]);//是的就取出后面的值int ret = putenv(g_myval);//将它导入环境变量中if(ret == 0) printf("%s export success\n", g_argv[1]);//如果导入成功就打印出来continue;
}

shell 执行的命令通常有两种

  1. 第三方提供的对应的在磁盘中具有二进制文件的可执行程序(由子进程执行)

  2. shell内部自己实现的方法,由自己(父进程)来执行,有些命令就是要影响shell本身,如改变路径的(cd、export),shell代表的是用户。

shell的环境变量从哪里来的?(了解)

环境变量是写在配置文件中的,shell启动的时候,通过读取配置文件获得的起始环境变量

08. 类似ll这种别名命令无法识别

ll是ls -l的别名

想要支持就要当识别到ll时执行ls命令即可

if(strcmp(g_argv[0], "ll") == 0)
{g_argv[0] = "ls";g_argv[index++] = "-l";g_argv[index++] = "--color=auto";
}

系统中肯定不是这样实现的,但是大致原理相同

后记

最后我们就实现了一个简易的shell解释器

全部代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>#define NUM 1024
#define SIZE 32
#define SEP " "//保存完整的命令行字符串
char cmd_line[NUM];//保存打散之后的命令行字符串
char *g_argv[SIZE];// 写一个环境变量的buffer,用来测试
char g_myval[64];// shell 运行原理 : 通过让子进程执行命令,父进程等待&&解析命令
int main()
{extern char**environ;//0. 命令行解释器,一定是一个常驻内存的进程,不退出while(1){//1. 打印出提示信息 [whb@localhost myshell]# printf("[root@localhost myshell]# ");fflush(stdout);memset(cmd_line, '\0', sizeof cmd_line);//2. 获取用户的键盘输入[输入的是各种指令和选项: "ls -a -l -i"]if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL){continue;}cmd_line[strlen(cmd_line)-1] = '\0';//3. 命令行字符串解析:"ls -a -l -i" -> "ls" "-a" "-i"// export myval=105g_argv[0] = strtok(cmd_line, SEP); //第一次调用,要传入原始字符串int index = 1;if(strcmp(g_argv[0], "ls") == 0){g_argv[index++] = "--color=auto";}if(strcmp(g_argv[0], "ll") == 0){g_argv[0] = "ls";g_argv[index++] = "-l";g_argv[index++] = "--color=auto";}while(g_argv[index++] = strtok(NULL, SEP)); //第二次,如果还要解析原始字符串,传入NULLif(strcmp(g_argv[0], "export") == 0 && g_argv[1] != NULL){strcpy(g_myval, g_argv[1]);int ret = putenv(g_myval);if(ret == 0) printf("%s export success\n", g_argv[1]);continue;}//4.内置命令, 让父进程(shell)自己执行的命令,我们叫做内置命令,内建命令//内建命令本质其实就是shell中的一个函数调用if(strcmp(g_argv[0], "cd") == 0) //not child execute, father execute{if(g_argv[1] != NULL) chdir(g_argv[1]); //cd path, cd ..continue;}//5. fork()pid_t id = fork();if(id == 0) //child{printf("下面功能让子进程进行的\n");printf("child, MYVAL: %s\n", getenv("MYVAL"));//测试环境变量printf("child, PATH: %s\n", getenv("PATH"));//测试环境变量//环境变量相关的数据,会被替换吗??没有!execvp(g_argv[0], g_argv); // ls -a -l -iexit(1);}//fatherint status = 0;pid_t ret = waitpid(id, &status, 0);if(ret > 0) printf("exit code: %d\n", WEXITSTATUS(status));}
}

相关文章:

《Linux从练气到飞升》No.21 Linux简单实现一个shell

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的…...

【iVX】iVX的低代码未来发展趋势:加速应用开发的创新之路

简介&#xff1a; 随着数字化转型的飞速发展&#xff0c;企业和组织对快速开发和交付高质量应用的需求越来越迫切。低代码开发平台作为一种创新的解决方案&#xff0c;极大地简化了应用程序的开发过程。在这一领域&#xff0c;iVX低代码平台作为领先的创业公司&#xff0c;正在…...

zookee 安装

1、下载安装包 weget https://downloads.apache.org/zookeeper/zookeeper-3.6.3/apache-zookeeper-3.6.3-bin.tar.gz 方案1&#xff1a;wget是一个下载指令&#xff0c;后面可以跟下载连接去从服务器上下载东西。 方案2&#xff1a;也可以先下载到windows上&#xff0c;再通…...

OpenWrt编译自己的应用程序

编译OpenWrt的应用程序可以参考OpenWrt内部其他应用程序的例程&#xff0c;来编写成自己的应用程序 一、OpenWrt源代码获取与编译 1.1、搭建环境 下载OpenWrt的官方源码&#xff1a; git clone https://github.com/openwrt/openwrt.git1.2、安装编译依赖项 sudo apt update…...

MySQL 50 题。

MySQL 50 题。 文章目录 MySQL 50 题。数据库。sql。 数据库。 CREATE SCHEMA new_schema DEFAULT CHARACTER SET utf8mb4 ;Operation failed: There was an error while applying the SQL script to the database. Executing: CREATE SCHEMA new_schema DEFAULT CHARACTER SE…...

强化学习算法总结 (1)

强化学习算法总结 (1) 1.综述 强化学习是通过与环境进行交互&#xff0c;来实现目标的一种计算方法。 s − a 1 − r − s ′ s - a_1 - r- s s−a1​−r−s′ 1.1强化学习优化目标 p o l i c y a r g m a x p o l i c y E ( a , s ) [ r e w a r d ( s , a ) ] policy ar…...

Qt应用开发(基础篇)——向导对话框 QWizard

一、前言 QWizard类继承于QDialog&#xff0c;为有向导界面需求的应用环境提供了一个框架。 对话框窗口 QDialog QWizard向导对话框是一个拥有队列界面的特殊对话框&#xff0c;向导的目的是引导用户一步一步的完成预设的流程。向导常用于软件安装界面向导、硬件线路安装向导、…...

Python类的方法

Python类的方法主要分为实例方法、类方法和静态方法三种。 1 实例方法 以self作为第一个参数的方法&#xff0c;就是类的实例方法。该方法由类的实例调用&#xff0c;Python会把调用该方法的实例对象传递给self。 如下代码定义了一个名为A的类。 class A:def __init__(self…...

变电站自动化监控系统

力安科技变电站自动化监控系统是以箱式变电站为管理对象&#xff0c;加装箱变网关&#xff0c;在完成箱变智能化改造的基础上&#xff0c;依托电易云&#xff0c;构建一体化智慧箱变及运维系统。智能箱式变电站被广泛应用于住宅小区、城市公用变压器、工厂、商场、机场、电站等…...

MySql学习笔记11——DBA命令介绍

DBA命令 数据导入 要进入Mysql 创建数据库 create database database_name;使用数据库 use database_name;初始化数据库 source .sql文件地址&#xff0c;不能加双引号&#xff1b;数据导出 要在windows的dos环境下进行 导出数据库 mysqldump database_name > 存放…...

Webpack 复习小结

nodejs学习参考 node常用命令&#xff1a; node xxx.js 执行js文件 npm init -y 初始化package.json npm i 软件包名 下载软件包到本地 npm i 软件包名 -g 下载软件包到全局 npm uni 软件包名 删除软件包 系统优化CDN使用 CDN for free 需求&#xff1a;开发模式使用本地第三…...

Laravel chunk和chunkById的坑

在编写定时任务脚本的时候&#xff0c;经常会用到chunk和chunkById的API。 一、前言 数据库引擎为innodb。 表结构简述&#xff0c;只列出了本文用到的字段。 字段类型注释idint(11)IDtypeint(11)类型mark_timeint(10)标注时间&#xff08;时间戳&#xff09; 索引&#x…...

从零开始学习 Java:简单易懂的入门指南之泛型及set集合(二十二)

泛型及set集合扩展 1.泛型1.1泛型概述 2.Set集合2.1Set集合概述和特点【应用】2.2Set集合的使用【应用】 3.TreeSet集合3.1TreeSet集合概述和特点【应用】3.2TreeSet集合基本使用【应用】3.3自然排序Comparable的使用【应用】3.4比较器排序Comparator的使用【应用】3.5两种比较…...

JVM----GC(垃圾回收)详解

一、Automatic Garbage Collection&#xff08;垃圾回收&#xff09;简介 Automatic Garbage Collection &#xff08;自动垃圾回收&#xff09;是JVM的一个特性&#xff0c;JVM会启动相关的线程&#xff0c;该线程会轮训检查heap memeory&#xff0c;并确定哪些是未被引用的(…...

数据库的三个范式

数据库的三个范式是关系数据库设计中的一组规范&#xff0c;用于确保数据的有效性和一致性。这三个范式分别是&#xff1a; 第一范式&#xff08;1NF&#xff09;&#xff1a;要求数据库表中的每一列都是不可分割的原子值。换句话说&#xff0c;每个表中的每个字段不能包含多个…...

谷歌浏览器打开白屏 后台还有还有很多google chrome进程在运行

环境&#xff1a; Win10 专业版 谷歌浏览器 版本 116.0.5845.141&#xff08;正式版本&#xff09; &#xff08;64 位&#xff09; L盾加密终端 问题描述&#xff1a; 谷歌浏览器打开白屏 后台还有还有很多google chrome进程在运行&#xff0c;要全部结束谷歌浏览器进程&…...

Java EE 突击 15 - Spring Boot 统一功能处理

Spring Boot 统一功能处理 一 . 统一功能的处理1.1 初级阶段 : 不断重复1.2 中级阶段 : 集成方法1.3 高级阶段 : Spring AOP1.4 超高级阶段 : Spring 拦截器准备工作实现拦截器自定义拦截器将自定义拦截器加入到系统配置 拦截器实现原理扩展 : 统一访问前缀添加 二 . 统一异常的…...

JasperReport定义变量后打印PDF变量为null以及整个pdf文件为空白

问题1: JasperReport打印出来的整个pdf文件为空白文件&#xff1b; 问题2&#xff1a;JasperReport定义变量后打印PDF变量为null&#xff1b; 问题1原因是因为缺少数据源JRDataSource JasperFillManager.fillReport(jasperReport, params,new JREmptyDataSource());如果你打印…...

Python 及 Pycharm 的安装 2023.8

Python 及 PyCharm 的安装 仅适用于 Windows 系统&#xff01; 视频教程&#xff1a;【Python及Pycharm的安装 2023.8】 https://www.bilibili.com/video/BV1A34y1T7Gu 文章目录 Python 及 PyCharm 的安装安装 Python安装 PyCharmHi, PyCharmPyCharm 汉化 安装 Python 进入 …...

java中的线程中断

java中的线程中断 1、线程中断 即 线程的取消/关闭的机制2、线程对中断interrupt()的反应2.1、RUNNABLE&#xff1a;线程在运行或具备运行条件只是在等待操作系统调度2.2、WAITING/TIMED_WAITING&#xff1a;线程在等待某个条件或超时2.3、BLOCKED&#xff1a;线程在等待锁&…...

【跟小嘉学 Rust 编程】二十三、Cargo 使用指南

系列文章目录 【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学…...

R Removing package报错(as ‘lib’ is unspecified)

remove.packages(ggpubr) Removing package from ‘/Library/Frameworks/R.framework/Versions/4.0/Resources/library’ (as ‘lib’ is unspecified) 解决办法&#xff1a; > .libPaths() [1] "/Library/Frameworks/R.framework/Versions/4.0/Resources/library&qu…...

金融信创,软件规划需关注自主安全及生态建设

软件信创化&#xff0c;就是信息技术软件应用创新发展的意思&#xff08;简称为“信创”&#xff09;。 相信在中国&#xff0c;企业对于“信创化”这个概念并不陌生。「国强则民强」&#xff0c;今年来中国经济的快速发展&#xff0c;受到了各大欧美强国的“卡脖子”操作的影…...

无重叠区间【贪心算法】

无重叠区间 给定一个区间的集合 intervals &#xff0c;其中 intervals[i] [starti, endi] 。返回 需要移除区间的最小数量&#xff0c;使剩余区间互不重叠 。 class Solution {public int eraseOverlapIntervals(int[][] intervals) {//先排序&#xff0c;按照左边界升序,注…...

nlp系列(7)实体识别(Bert)pytorch

模型介绍 本项目是使用Bert模型来进行文本的实体识别。 Bert模型介绍可以查看这篇文章&#xff1a;nlp系列&#xff08;2&#xff09;文本分类&#xff08;Bert&#xff09;pytorch_bert文本分类_牧子川的博客-CSDN博客 模型结构 Bert模型的模型结构&#xff1a; 数据介绍 …...

Uniapp学习之从零开始写一个简单的小程序demo(新建页面,通过导航切换页面,发送请求)

先把官网文档摆在这&#xff0c;后面会用到的 [uniapp官网文档]: https://uniapp.dcloud.net.cn/vernacular.html# 一、开发工具准备 1-1 安装HBuilder 按照官方推荐&#xff0c;先装一个HBuilder 下载地址&#xff1a; https://www.dcloud.io/hbuilderx.html1-2 安装微信开…...

uniapp微信小程序隐私保护引导新规

1.components中新建组件PrivacyPop.vue <template><view class"privacy" v-if"showPrivacy"><view class"content"><view class"title">隐私保护指引</view><view class"des">在使用当…...

超图嵌入论文阅读2:超图神经网络

超图嵌入论文阅读2&#xff1a;超图神经网络 原文&#xff1a;Hypergraph Neural Networks ——AAAI2019&#xff08;CCF-A&#xff09; 源码&#xff1a;https://github.com/iMoonLab/HGNN 500star 概述 贡献&#xff1a;用于数据表示学习的超图神经网络 (HGNN) 框架&#xf…...

安全运营中心(SOC)技术框架

2018年曾经画过一个安全运营体系框架&#xff0c;基本思路是在基础单点技术防护体系基础上&#xff0c;围绕着动态防御、深度分析、实时检测&#xff0c;建立安全运营大数据分析平台&#xff0c;可以算作是解决方案产品的思路。 依据这个体系框架&#xff0c;当时写了《基于主动…...

并行和并发的区别

从操作系统的角度来看&#xff0c;线程是CPU分配的最小单位。 并行就是同一时刻&#xff0c;两个线程都在执行。这就要求有两个CPU去分别执行两个线程。并发就是同一时刻&#xff0c;只有一个执行&#xff0c;但是一个时间段内&#xff0c;两个线程都执行了。并发的实现依赖于…...