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

【Linux】模拟实现bash(简易版)

在这里插入图片描述

👦个人主页:Weraphael
✍🏻作者简介:目前正在学习c++和算法
✈️专栏:Linux
🐋 希望大家多多支持,咱一起进步!😁
如果文章有啥瑕疵,希望大佬指点一二
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍


目录

  • 前言
  • 一、用户输入
  • 二、指令分割
  • 三、程序替换
      • 3.1 外部命令
      • 3.2 内建命令
        • 3.2.1 cd
        • 3.2.2 export
        • 3.2.3 echo
  • 四、总结及源码

前言

简单回顾一下往期知识,命令行解释器bash只是一个”外壳程序",而操作系统则称为“内壳程序”,这是因为操作系统不相信用户,因此我们用户只能通过“外壳程序”将指令进行翻译给操作系统,操作系统再将结果通过“外壳程序”返回给用户。

请添加图片描述

以上图片来自于【往期博客】

由于目前学习到的知识有限,后面会慢慢更新相关接口 ~

一、用户输入

首先命令行bash需要提示类似于:[用户名@主机名 当前目录]$。我们可以使用以下系统调用接口来获取它们:

  1. 获取用户名
#include <unistd.h>
char *getlogin();
  1. 获取主机名
#include <unistd.h>
int gethostname(char *name, size_t len);

其中:

  • 该函数的功能是将主机名复制到 name 指向的缓冲区中(字符数组),注意name 缓冲区应该足够大以容纳主机名。

  • 第二个参数len 是缓冲区的长度(数组长度)。

  • 返回值:

    • 成功返回0,并将主机名复制到 name 指向的缓冲区中。
    • 失败返回 -1
  1. 获取当前工作目录路径
#include <unistd.h>
char *getcwd(char *buf, size_t size);

其中:

  • 该函数的功能是将当前工作目录的绝对路径复制到 buf 指向的缓冲区中,并保证以空字符 \0 结尾。注意:传递给 getcwd() 的缓冲区应该足够大。

  • size 参数表示缓冲区的大小。

  • 函数的返回值:

    • 如果成功, buf 指向的缓冲区地址。
    • 如果失败,返回 NULL

有了以上接口,我们就可以用代码来实现了

请添加图片描述

接下来就应该轮到用户输入指令,本质就是输入字符串。

这里需要注意的是,由于我们输入的指令可以带选项,那么必定是带空格的(如ls -al),而 scanf默认遇到空格或者换行就不读取了。除非你使用修饰符配合scanf函数

char str[100];
scanf("%[^\n]", str); // 可以读空格和换行

除以上方法以外,fgets函数也可以读取空格和换行

#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);

其中:

  • 第一个参数:用于存储从输入流中读取的数据。一般是一个字符数组。
  • 第二个参数:计算整个字符数组的大小。
  • 第三个参数:这个参数指定了从哪个文件流中读取数据。在大多数情况下,我们使用标准输入流,即键盘输入,因此会传递stdin
  • 返回值:读取数据成功时返回一个指向目标缓冲区的指针,如果读取失败或者到达文件结尾时返回NULL

请添加图片描述

请添加图片描述

以上程序还有缺陷,那就是当我们输入完一条指令后,bash把结果返回给我们后,会继续重复提示我们输入指令。而我们目前写的程序执行完一条指令后就退出进程。因此,以上程序应当是一个循环。

请添加图片描述

请添加图片描述

二、指令分割

当用户输入完指令,我们要进行指令分割为后面【程序替换】做准备。

由于一开始我们使用fgets函数将最后的回车\n给读取到了command数组,因此我们要将其去掉(置为'\0'即可)

请添加图片描述

接下来我们进行分割命令行参数,C语言提供了字符串分割函数 strtok。当然你也可以自己手撕一个hh

#include <string.h>
char *strtok(char *str, const char *delim);

其中:

  • str:要分割的字符串,第一次调用时传入待分割的字符串,后续调用传NULL继续分割该字符串,函数会继续在上一次调用的字符串中查找下一个标记的位置。
  • delim:分隔符的字符串,即用来确定标记边界的字符集合。
  • strtok() 函数返回一个指向分割后的标记的指针,如果没有找到标记,则返回NULL

请添加图片描述

请添加图片描述

三、程序替换

3.1 外部命令

对于外部命令,shell则会创建一个子进程,并在子进程中进行程序替换来执行这些命令。在执行完成后,Shell会等待子进程退出,并获取子进程的退出码。

请添加图片描述

如上所示,有很多替换函数供我们选择,为了方便,尽量不要选择带l,因为我们已经将命令行参数分割好了在字符指针数组argv中,而无需一一列举;另外,我们也不要选择不带p的,因为这样还需要我们自己去写完整的文件路径。

综上,我们可以使用execvpe函数。另外,environ是全局变量它是由标准C库提供的,当用户登录时,shell会读取用户目录下的.bash_profile文件,里面保存了导入环境变量的方式。

请添加图片描述

请添加图片描述

如上所示,我们执行的命令确实起效了,但是还是有些缺陷,比如ls显示出来的文件没有高亮;以及ll(ls -l重命名)没有效果,因此我们的代码还是可以再改造改造。

我们可以先来解决ls显示的文件没有高亮的问题

请添加图片描述

因此,我们只需要对argv数组添加一个命令行参数,也就是--color=auto即可

请添加图片描述

请添加图片描述

最后来解决ll未显示出结果的问题。

请添加图片描述

请添加图片描述

3.2 内建命令

什么是内建命令呢?比如以cd为例,子进程执行cd命令改变了子进程的工作目录,由于父子进程是相互独立的,子进程改变了,而父进程bash却没有影响。因此,内建命令是不需要通过创建子进程来执行。

Linux中有很多内建命令:

请添加图片描述

这里我只挑选一些来完善

3.2.1 cd

我们可以使用系统调用接口chdir函数来改变当前进程的工作目录,并且它对于特殊的路径 .. 也可以完成对应的更改,但除了cd ~cd -,分别是返回家目录和返回最近一次访问的目录,注意:家目录和最近一次访问目录可以通过环境变量来获取。

请添加图片描述

请添加图片描述

但需要注意的是:改变当前进程的工作目录不会直接影响环境变量 PWD,我们需要手动更新。(以上环境变量只截取了部分)

步骤如下:

  1. 调用getcwd函数更新pwd数组

  2. pwd替换掉原来环境变量PWD的值即可

我们可以使用sprintf函数来替换。sprintf 函数是 C 语言中的一个标准库函数,用于将格式化的数据写入一个字符串中。

#include <stdio.h>
int sprintf(char *str, const char *format, ...);

其中:

  • str 是一个指向字符数组的指针,指向需要修改的字符串
  • 后面的参数就和printf函数一样了

请添加图片描述

请添加图片描述

(以上环境变量只截取了部分)

3.2.2 export

这个看似非常简单,比如添加环境变量export x=333,那么直接使用putenv(argv[1])(其中argv[0]表示exportargv[1]表示x=333

如果你是以上这样做法导致第一次添加可能成功,但第二次添加后,第一次添加的就没了。这是因为argv[1] 中的内容是不断变化的,第二次添加就覆盖了第一次添加。

正确做法:

  • 一般用户自定义的环境变量,在 bash 中需要用户自己维护一个字符指针数组

  • 先将待添加的环境变量拷贝至指针数组

  • 再从中读取,并调用 putenv 函数添加至环境变量表

请添加图片描述

请添加图片描述

3.2.3 echo
  • echo首先需要能获取最近一次进程的退出状态

请添加图片描述

本应当返回ls进程的退出状态,而他原原本本返回了$?

请添加图片描述

  • 我们打印环境变量,例如$PATH会出现什么都没输出的现象

请添加图片描述

请添加图片描述

请添加图片描述

  • 输出字符串会带双引号的情况

请添加图片描述

四、总结及源码

所谓的shell也是一个进程,它可以获取用户的输入,然后对用户的输入做分析。对于内建命令,shell会直接调用函数来执行;而对于外部命令,shell则会创建一个子进程,并在子进程中进行进程替换来执行相对应的命令。在执行完成后,shell会等待子进程退出,并获取子进程的退出码。

  • 获取源码:点击跳转

相关文章:

【Linux】模拟实现bash(简易版)

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…...

C++ | Leetcode C++题解之第67题二进制求和

题目&#xff1a; 题解&#xff1a; class Solution { public:string addBinary(string a, string b) {string ans;reverse(a.begin(), a.end());reverse(b.begin(), b.end());int n max(a.size(), b.size()), carry 0;for (size_t i 0; i < n; i) {carry i < a.siz…...

如何确保UDP文件传输工具有最低稳定的传输速度?

在当前日新月异的数字时代背景下&#xff0c;文件传输工具已经成为我们日常生活与工作中不可或缺的一部分&#xff0c;尤其针对那些频繁涉及即时数据交互与多媒体流通的场景。 UDP协议&#xff0c;以其突出的高速传输与低延迟特性&#xff0c;脱颖而出成为众多用户的首选。不过…...

力扣爆刷第133天之动态规划收尾(距离编辑与回文子串)

力扣爆刷第133天之动态规划收尾&#xff08;距离编辑与回文子串&#xff09; 文章目录 力扣爆刷第133天之动态规划收尾&#xff08;距离编辑与回文子串&#xff09;一、72. 编辑距离二、647. 回文子串三、516. 最长回文子序列 一、72. 编辑距离 题目链接&#xff1a;https://l…...

List集合中对asList的使用

List<String> sArrays.asList(“qwe”,”cvb”,”mnb”); List<String> s1s.subList(1,2); System.out.Pintln(“s”);//输出结果&#xff1a;[qwe,cvb,mnb] System.out.Pintln(“s1”);//输出结果&#xff1a;[cvb] s1.add(“123qwe”);//报错&#xff1a;java…...

软件测试所有测试方法

β测试_Beta测试 β测试&#xff0c;英文是Beta testing。又称Beta测试&#xff0c;用户验收测试&#xff08;UAT&#xff09;。 β测试是软件的多个用户在一个或多个用户的实际使用环境下进行的测试。开发者通常不在测试现场&#xff0c;Beta测试不能由程序员或测试员完成。 …...

linux 下 /usr/local的作用

在Linux系统中&#xff0c;/usr/local目录扮演着特定的角色&#xff0c;它是为用户自安装的软件提供一个标准位置。以下是/usr/local目录的主要用途和特点&#xff1a; 用户级程序目录&#xff1a;该目录用于存放用户自行编译安装的软件或者第三方应用程序&#xff0c;区别于操…...

【web网页制作】html+css旅游家乡河南开封主题网页制作(4页面)【附源码】

HTMLCSS家乡河南主题网页目录 &#x1f354;涉及知识&#x1f964;写在前面&#x1f367;一、网页主题&#x1f333;二、页面效果Page1 首页Page2 开封游玩Page 3 开封美食Page4 留言 &#x1f308; 三、网页架构与技术3.1 脑海构思3.2 整体布局3.3 技术说明书 &#x1f40b;四…...

MySQL用命令行导出数据库

问题&#xff1a; 交作业的时候要求交数据文件&#xff0c;因为用的MySQL数据库&#xff0c;就在想怎么用命令行导出数据库&#xff0c;在csdn上找了其他文章&#xff0c;使用MySQL的命令行用下面语句&#xff0c;结果发生报错 mysqldump -u 用户名 -p 数据库名 > 输出地址…...

uniapp video 层级覆盖

层级覆盖 cover-view组件 我这里做了个判断 监听全屏时隐藏按钮 根据项目需求自行更改...

SparkSQL概述

1.1. SparkSQL介绍 SparkSQL&#xff0c;就是Spark生态体系中的构建在SparkCore基础之上的一个基于SQL的计算模块。SparkSQL的前身不叫SparkSQL&#xff0c;而是叫做Shark。最开始的时候底层代码优化、SQL的解析、执行引擎等等完全基于Hive&#xff0c;总是Shark的执行速度要比…...

docker 和 docker-compose

Docker是一种开源的容器化平台&#xff0c;它可以帮助开发人员将应用程序及其所有依赖项打包到一个独立的、可移植的容器中。这意味着您可以在任何地方运行Docker容器&#xff0c;而不需要担心环境差异或依赖项的问题。 Docker Compose是Docker官方提供的一个工具&#xff0c;…...

微信小程序支付(完整版)-ThinkPHP/Uniapp

技术说明 1.前端&#xff1a;uniapp、vue3 2.接口&#xff1a;PHP8、ThinkPHP8、MySQL8.0 3.微信支付- PHP&#xff0c;官方示例文档 4.示例代码的模型及业务自己进行调整&#xff0c;不要一味的复制粘贴&#xff01;&#xff01;&#xff01; 流程说明 1.小程序调用接口…...

同时安装多个nodejs版本可切换使用,或者用nvm管理、切换nodejs版本(两个详细方法)

目录 一.使用nvm的方法&#xff1a; 1.卸载nodejs 2.前往官网下载nvm 3.安装nvm 4.查看安装是否完成 5.配置路径和淘宝镜像 6.查看和安装各个版本的nodejs 7.nvm的常用命令 二.不使用nvm&#xff0c;安装多个版本&#xff1a; 1.安装不同版本的nodejs 2.解压到你想放…...

马化腾用了一年多的时间,告诉所有人,视频号小店是新风口!

大家好&#xff0c;我是电商笨笨熊 当腾讯说出自己要做电商的时候&#xff0c;所有人都在说&#xff0c;根本不可能&#xff1b; 甚至在视频号小店正式推出之后&#xff0c;依旧有人说&#xff0c;腾讯做电商就是笑话&#xff1b; 一个“抄”过来的项目&#xff0c;毫无特色…...

代码随想录算法训练营第36期DAY19

DAY19 104二叉树的最大深度 根节点的高度就是最大深度。 非递归法&#xff1a; /** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode() : val(0), left(nullptr), right(nullptr) …...

C#图像:1.图像区域分割与提取

&#xff08;1&#xff09;创建一个名为SplitImage的窗体的应用程序&#xff0c;将窗体改名为FormSplitImage。 &#xff08;2&#xff09;创建一个名为ImageProcessingLibrary的类库程序&#xff0c;为该工程添加名为ImageProcessing的静态类 &#xff08;3&#xff09;为Imag…...

炸弹使用技巧

掼蛋掼蛋&#xff0c;打的就是炸弹。炸弹是指掼蛋中由4-8张相同牌点的牌组成的牌型&#xff0c;需要注意的是&#xff1a;每局牌中都有两张红桃的牌型为逢人配&#xff0c;可以配除了大小王以外的任意牌&#xff0c;因此掼蛋中牌数最多的炸弹可以达到10张。 两副扑克牌中&#…...

SpringAop详解

文章目录 一、Spring自定义注解1、什么是注解&#x1f468;‍&#x1f3eb;2、注解的目的或作用&#x1f49e;3、JDK内置注解&#x1f4ab; 【内置元注解 一共八个固定注解】4、元注解 &#x1f3af;5、自定义注解&#x1f4f8;5、Java反射API和类加载过程51、什么是反射基本原…...

对XYctf的一些总结

对XYctf的一些总结 WEB 1.http请求头字段 此次比赛中出现的&#xff1a; X-Forwarded-For/Client-ip&#xff1a;修改来源ip via&#xff1a;修改代理服务器 还有一些常见的字段&#xff1a; GET&#xff1a;此方法用于请求指定的资源。GET请求应该安全且幂等&#xff0c…...

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...

云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?

大家好&#xff0c;欢迎来到《云原生核心技术》系列的第七篇&#xff01; 在上一篇&#xff0c;我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在&#xff0c;我们就像一个拥有了一块崭新数字土地的农场主&#xff0c;是时…...

Java 语言特性(面试系列1)

一、面向对象编程 1. 封装&#xff08;Encapsulation&#xff09; 定义&#xff1a;将数据&#xff08;属性&#xff09;和操作数据的方法绑定在一起&#xff0c;通过访问控制符&#xff08;private、protected、public&#xff09;隐藏内部实现细节。示例&#xff1a; public …...

Cesium1.95中高性能加载1500个点

一、基本方式&#xff1a; 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...

Golang dig框架与GraphQL的完美结合

将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用&#xff0c;可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器&#xff0c;能够帮助开发者更好地管理复杂的依赖关系&#xff0c;而 GraphQL 则是一种用于 API 的查询语言&#xff0c;能够提…...

五年级数学知识边界总结思考-下册

目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解&#xff1a;由来、作用与意义**一、知识点核心内容****二、知识点的由来&#xff1a;从生活实践到数学抽象****三、知识的作用&#xff1a;解决实际问题的工具****四、学习的意义&#xff1a;培养核心素养…...

C++八股 —— 单例模式

文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全&#xff08;Thread Safety&#xff09; 线程安全是指在多线程环境下&#xff0c;某个函数、类或代码片段能够被多个线程同时调用时&#xff0c;仍能保证数据的一致性和逻辑的正确性&#xf…...

JS手写代码篇----使用Promise封装AJAX请求

15、使用Promise封装AJAX请求 promise就有reject和resolve了&#xff0c;就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...

(一)单例模式

一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...

Python 训练营打卡 Day 47

注意力热力图可视化 在day 46代码的基础上&#xff0c;对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...