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

👦个人主页:Weraphael
✍🏻作者简介:目前正在学习c++和算法
✈️专栏:Linux
🐋 希望大家多多支持,咱一起进步!😁
如果文章有啥瑕疵,希望大佬指点一二
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍
目录
- 前言
- 一、用户输入
- 二、指令分割
- 三、程序替换
- 3.1 外部命令
- 3.2 内建命令
- 3.2.1 cd
- 3.2.2 export
- 3.2.3 echo
- 四、总结及源码
前言
简单回顾一下往期知识,命令行解释器bash只是一个”外壳程序",而操作系统则称为“内壳程序”,这是因为操作系统不相信用户,因此我们用户只能通过“外壳程序”将指令进行翻译给操作系统,操作系统再将结果通过“外壳程序”返回给用户。

以上图片来自于【往期博客】
由于目前学习到的知识有限,后面会慢慢更新相关接口 ~
一、用户输入
首先命令行bash需要提示类似于:[用户名@主机名 当前目录]$。我们可以使用以下系统调用接口来获取它们:
- 获取用户名
#include <unistd.h>
char *getlogin();
- 获取主机名
#include <unistd.h>
int gethostname(char *name, size_t len);
其中:
-
该函数的功能是将主机名复制到
name指向的缓冲区中(字符数组),注意name缓冲区应该足够大以容纳主机名。 -
第二个参数
len是缓冲区的长度(数组长度)。 -
返回值:
- 成功返回
0,并将主机名复制到name指向的缓冲区中。 - 失败返回
-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,我们需要手动更新。(以上环境变量只截取了部分)
步骤如下:
-
调用
getcwd函数更新pwd数组 -
将
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]表示export,argv[1]表示x=333)
如果你是以上这样做法导致第一次添加可能成功,但第二次添加后,第一次添加的就没了。这是因为argv[1] 中的内容是不断变化的,第二次添加就覆盖了第一次添加。
正确做法:
-
一般用户自定义的环境变量,在
bash中需要用户自己维护一个字符指针数组。 -
先将待添加的环境变量拷贝至指针数组
-
再从中读取,并调用
putenv函数添加至环境变量表


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

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

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



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

四、总结及源码
所谓的shell也是一个进程,它可以获取用户的输入,然后对用户的输入做分析。对于内建命令,shell会直接调用函数来执行;而对于外部命令,shell则会创建一个子进程,并在子进程中进行进程替换来执行相对应的命令。在执行完成后,shell会等待子进程退出,并获取子进程的退出码。
- 获取源码:点击跳转
相关文章:
【Linux】模拟实现bash(简易版)
👦个人主页:Weraphael ✍🏻作者简介:目前正在学习c和算法 ✈️专栏:Linux 🐋 希望大家多多支持,咱一起进步!😁 如果文章有啥瑕疵,希望大佬指点一二 如果文章对…...
C++ | Leetcode C++题解之第67题二进制求和
题目: 题解: 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文件传输工具有最低稳定的传输速度?
在当前日新月异的数字时代背景下,文件传输工具已经成为我们日常生活与工作中不可或缺的一部分,尤其针对那些频繁涉及即时数据交互与多媒体流通的场景。 UDP协议,以其突出的高速传输与低延迟特性,脱颖而出成为众多用户的首选。不过…...
力扣爆刷第133天之动态规划收尾(距离编辑与回文子串)
力扣爆刷第133天之动态规划收尾(距离编辑与回文子串) 文章目录 力扣爆刷第133天之动态规划收尾(距离编辑与回文子串)一、72. 编辑距离二、647. 回文子串三、516. 最长回文子序列 一、72. 编辑距离 题目链接:https://l…...
List集合中对asList的使用
List<String> sArrays.asList(“qwe”,”cvb”,”mnb”); List<String> s1s.subList(1,2); System.out.Pintln(“s”);//输出结果:[qwe,cvb,mnb] System.out.Pintln(“s1”);//输出结果:[cvb] s1.add(“123qwe”);//报错:java…...
软件测试所有测试方法
β测试_Beta测试 β测试,英文是Beta testing。又称Beta测试,用户验收测试(UAT)。 β测试是软件的多个用户在一个或多个用户的实际使用环境下进行的测试。开发者通常不在测试现场,Beta测试不能由程序员或测试员完成。 …...
linux 下 /usr/local的作用
在Linux系统中,/usr/local目录扮演着特定的角色,它是为用户自安装的软件提供一个标准位置。以下是/usr/local目录的主要用途和特点: 用户级程序目录:该目录用于存放用户自行编译安装的软件或者第三方应用程序,区别于操…...
【web网页制作】html+css旅游家乡河南开封主题网页制作(4页面)【附源码】
HTMLCSS家乡河南主题网页目录 🍔涉及知识🥤写在前面🍧一、网页主题🌳二、页面效果Page1 首页Page2 开封游玩Page 3 开封美食Page4 留言 🌈 三、网页架构与技术3.1 脑海构思3.2 整体布局3.3 技术说明书 🐋四…...
MySQL用命令行导出数据库
问题: 交作业的时候要求交数据文件,因为用的MySQL数据库,就在想怎么用命令行导出数据库,在csdn上找了其他文章,使用MySQL的命令行用下面语句,结果发生报错 mysqldump -u 用户名 -p 数据库名 > 输出地址…...
uniapp video 层级覆盖
层级覆盖 cover-view组件 我这里做了个判断 监听全屏时隐藏按钮 根据项目需求自行更改...
SparkSQL概述
1.1. SparkSQL介绍 SparkSQL,就是Spark生态体系中的构建在SparkCore基础之上的一个基于SQL的计算模块。SparkSQL的前身不叫SparkSQL,而是叫做Shark。最开始的时候底层代码优化、SQL的解析、执行引擎等等完全基于Hive,总是Shark的执行速度要比…...
docker 和 docker-compose
Docker是一种开源的容器化平台,它可以帮助开发人员将应用程序及其所有依赖项打包到一个独立的、可移植的容器中。这意味着您可以在任何地方运行Docker容器,而不需要担心环境差异或依赖项的问题。 Docker Compose是Docker官方提供的一个工具,…...
微信小程序支付(完整版)-ThinkPHP/Uniapp
技术说明 1.前端:uniapp、vue3 2.接口:PHP8、ThinkPHP8、MySQL8.0 3.微信支付- PHP,官方示例文档 4.示例代码的模型及业务自己进行调整,不要一味的复制粘贴!!! 流程说明 1.小程序调用接口…...
同时安装多个nodejs版本可切换使用,或者用nvm管理、切换nodejs版本(两个详细方法)
目录 一.使用nvm的方法: 1.卸载nodejs 2.前往官网下载nvm 3.安装nvm 4.查看安装是否完成 5.配置路径和淘宝镜像 6.查看和安装各个版本的nodejs 7.nvm的常用命令 二.不使用nvm,安装多个版本: 1.安装不同版本的nodejs 2.解压到你想放…...
马化腾用了一年多的时间,告诉所有人,视频号小店是新风口!
大家好,我是电商笨笨熊 当腾讯说出自己要做电商的时候,所有人都在说,根本不可能; 甚至在视频号小店正式推出之后,依旧有人说,腾讯做电商就是笑话; 一个“抄”过来的项目,毫无特色…...
代码随想录算法训练营第36期DAY19
DAY19 104二叉树的最大深度 根节点的高度就是最大深度。 非递归法: /** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode() : val(0), left(nullptr), right(nullptr) …...
C#图像:1.图像区域分割与提取
(1)创建一个名为SplitImage的窗体的应用程序,将窗体改名为FormSplitImage。 (2)创建一个名为ImageProcessingLibrary的类库程序,为该工程添加名为ImageProcessing的静态类 (3)为Imag…...
炸弹使用技巧
掼蛋掼蛋,打的就是炸弹。炸弹是指掼蛋中由4-8张相同牌点的牌组成的牌型,需要注意的是:每局牌中都有两张红桃的牌型为逢人配,可以配除了大小王以外的任意牌,因此掼蛋中牌数最多的炸弹可以达到10张。 两副扑克牌中&#…...
SpringAop详解
文章目录 一、Spring自定义注解1、什么是注解👨🏫2、注解的目的或作用💞3、JDK内置注解💫 【内置元注解 一共八个固定注解】4、元注解 🎯5、自定义注解📸5、Java反射API和类加载过程51、什么是反射基本原…...
对XYctf的一些总结
对XYctf的一些总结 WEB 1.http请求头字段 此次比赛中出现的: X-Forwarded-For/Client-ip:修改来源ip via:修改代理服务器 还有一些常见的字段: GET:此方法用于请求指定的资源。GET请求应该安全且幂等,…...
观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...
云原生安全实战:API网关Kong的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关(API Gateway) API网关是微服务架构中的核心组件,负责统一管理所有API的流量入口。它像一座…...
代码规范和架构【立芯理论一】(2025.06.08)
1、代码规范的目标 代码简洁精炼、美观,可持续性好高效率高复用,可移植性好高内聚,低耦合没有冗余规范性,代码有规可循,可以看出自己当时的思考过程特殊排版,特殊语法,特殊指令,必须…...
论文阅读笔记——Muffin: Testing Deep Learning Libraries via Neural Architecture Fuzzing
Muffin 论文 现有方法 CRADLE 和 LEMON,依赖模型推理阶段输出进行差分测试,但在训练阶段是不可行的,因为训练阶段直到最后才有固定输出,中间过程是不断变化的。API 库覆盖低,因为各个 API 都是在各种具体场景下使用。…...
SQL Server 触发器调用存储过程实现发送 HTTP 请求
文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...
