【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会等待子进程退出,并获取子进程的退出码。

  • 获取源码:点击跳转

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/610719.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

redis深入理解之数据存储

1、redis为什么快 1&#xff09;Redis是单线程执行&#xff0c;在执行时顺序执行 redis单线程主要是指Redis的网络IO和键值对读写是由一个线程来完成的&#xff0c;Redis在处理客户端的请求时包括获取(socket 读)、解析、执行、内容返回 (socket 写)等都由一个顺序串行的主线…

权力集中,效率提升,中心化模式的优势与挑战

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 &#x1f680; 转载自热榜文章&#x1f525;&#xff1a;探索设计模式的魅力&#xff1a;权力集中…

Microsoft Project使用简明教程

一.认识Microsoft Project Microsoft Project 是微软公司开发的项目管理软件&#xff0c;用于规划、协调和跟踪项目的进度、资源和预算&#xff0c;如下图所示&#xff0c;左边是任务的显示&#xff0c;右边是一个日程的显示图&#xff0c;最上方的长方形处在我们项目设定日程…

【oracle数据库安装篇三】Linux6.8单机环境oracle11g容灾ADG搭建

说明 DataGuard 是在主节点与备用节点间通过日志同步来保证数据的同步&#xff0c;可以实现数据库快速切换与灾难性恢复。用户能够在对主数据库影响很小的情况下&#xff0c;实现主备数据库的同步。 关联文章 【oracle数据库安装篇一】Linux5.6基于LVM安装oracle11gR2单机 【…

Pandas数据取值与选择

文章目录 第1关&#xff1a;Series数据选择第2关&#xff1a;DataFrame数据选择方法 第1关&#xff1a;Series数据选择 编程要求 本关的编程任务是补全右侧上部代码编辑区内的相应代码&#xff0c;要求实现如下功能&#xff1a; 添加一行数据&#xff0c;时间戳2019-01-29值为…

vue开发网站—①调用$notify弹窗、②$notify弹窗层级问题、③js判断两个数组是否相同等。

一、vue中如何使用vant的 $notify&#xff08;展示通知&#xff09; 在Vue中使用Vant组件库的$notify方法来展示通知&#xff0c;首先确保正确安装了Vant并在项目中引入了Notify组件。 1.安装vant npm install vant --save# 或者使用yarn yarn add vant2.引入&#xff1a;在ma…

自存angular 自定义snackbar

定义 1.自定义样式 2.自定义组件 就在要使用snackbar的组件中 在module中引入该组件&#xff08;重新写一个组件也行的 直接引入就好&#xff09; 打开这个组件 给这个自定义的组件传参 这个自定义组件接参(类似对话框接参) 使用参数 在这个自定义组件中 做了点击如何关闭s…

企业信使运营管理平台功能介绍

企业信使运营管理平台是一种为企业提供内部协同、任务管理、沟通交流、文件共享等功能的综合性管理平台。该平台旨在提高企业内部的工作效率和沟通协作能力&#xff0c;提供便捷的工作管理工具&#xff0c;促进企业的业务发展。 内部协同功能 企业信使运营管理平台首先提供一种…

Navicat Data Modeler Ess for Mac:强大的数据库建模设计软件

Navicat Data Modeler Ess for Mac是一款专为Mac用户设计的数据库建模与设计工具&#xff0c;凭借其强大的功能和直观的界面&#xff0c;帮助用户轻松构建和管理复杂的数据库模型。 Navicat Data Modeler Ess for Mac v3.3.17中文直装版下载 这款软件支持多种数据库系统&#x…

android进阶-AIDL

参考&#xff1a;Android进阶——AIDL详解_android aidl-CSDN博客 AIDL&#xff08;Android 接口定义语言&#xff09;&#xff0c;可以使用它定义客户端与服务端进程间通信&#xff08;IPC&#xff09;的编程接口&#xff0c;在 Android 中&#xff0c;进程之间无法共享内存&…

全视通助力珠海市井岸镇卫生院新院,建设智慧病房

5月6日&#xff0c;位于珠海市斗门区的井岸镇卫生院新院正式启用&#xff0c;面向市民开诊。新院各诊区就医秩序井然&#xff0c;总体情况良好。据统计&#xff0c;截至开诊当天11点30分&#xff0c;新院门诊共接诊347人次&#xff0c;预防接种81人次&#xff0c;儿童体检33人次…

Docker快速搭建NAS服务——NextCloud

Docker快速搭建NAS服务——NextCloud 文章目录 前言NextCloud的搭建docker-compose文件编写运行及访问 总结 前言 本文主要讲解如何使用docker在本地快速搭建NAS服务&#xff0c;这里主要写如下两种&#xff1a; FileBrowser1&#xff1a;是一个开源的Web文件管理器&#xff…

effective python学习笔记_类与接口

用组合类实现多层结构而不用内置类型 例子&#xff1a;成绩单&#xff0c;存储学生各科成绩多个然后加权重&#xff0c;如果用字典类型会导致字典有多层嵌套结构 思想 当用内置类型如字典元组等结构出现超过二层的多层嵌套结构时&#xff0c;读起来会比较难懂&#xff0c;此时…

新能源 锂电池行业创业的财富方案,锂电池回收实战攻略课(36节课)

实战攻略 12年锂电池回收行业经验与坑全收录 课程内容&#xff1a; 001-课程介绍.mp4 002-锂电池的全种类认识.mp4 003-废品锂电池到级片粉末价值估算,mp4 004-锂电池的生产应用回收,mp4 005-梯次回收到粉未提纯全流程,mp4 006-锂电池行业术语,mp4 007-回收所需必备工具…

汉诺塔问题和爬楼梯(递归)

感谢大佬的光临各位&#xff0c;希望和大家一起进步&#xff0c;望得到你的三连&#xff0c;互三支持&#xff0c;一起进步 个人主页&#xff1a;LaNzikinh-CSDN博客 c语言基础_LaNzikinh篮子的博客-CSDN博客 文章目录 一.爬楼梯问题二.汉诺塔问题总结 一.爬楼梯问题 假设你正…

Ansys界面设计:ACT入门

来自官方文档Getting Started with ACT&#xff0c;机翻。 Ansys 提供一流的现成仿真技术。为了最有效地部署普遍模拟&#xff0c;您可能需要更精心策划的体验&#xff0c;以使我们的模拟专业知识与您的用户、公司或行业需求相匹配。 Ansys ACT 使您能够自定义和扩展 Ansys 体验…

java注解全网最细

引言 在java编程中&#xff0c;注解&#xff08;Annotation&#xff09;是一种元数据&#xff0c;它提供了关于程序代码的额外信息。注解不直接影响程序的执行&#xff0c;但可以在运行时提供有关程序的信息&#xff0c;或者让编译器执行额外的检查。 下面笔者通过循序渐进的…

快速上手文心一言指令

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

PHP基础教程

&#x1f40c;博主主页&#xff1a;&#x1f40c;​倔强的大蜗牛&#x1f40c;​ &#x1f4da;专栏分类&#xff1a;PHP &#x1f4da;参考教程&#xff1a;菜鸟\编程网❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、PHP语法 基本的 PHP 语法 PHP 注释 PHP空白不敏…

Kafka分级存储概念(一)

Kafka分级存储及实现原理 概述 Kafka社区在3.6版本引入了一个十分重要的特性: 分级存储,本系列文章主要旨在介绍Kafka分级存储的设计理念、设计细节以及具体的代码实现 背景:为什么要有分级存储? 场景 作为一款具有高吞吐及高性能的消息中间件,Kafka被广泛应用在大数据、…
最新文章