【Linux】模拟实现shell命令行解释器

需要云服务器等云产品来学习Linux的同学可以移步/–>腾讯云<–/官网,轻量型云服务器低至112元/年,优惠多多。(联系我有折扣哦)

文章目录

  • 1. 主要思路
  • 2. 流程图
  • 3. 实现过程
    • 3.1 初步实现
    • 3.2 当前路径
    • 3.3 内建命令/外部命令
    • 3.4 Shell 的最终实现

1. 主要思路

一个Shell是一直在运行着的,为了保证Shell本身的运行稳定,所以每次在接收到指令的时候,会派生子进程,把子进程替换成要执行的指令,执行完毕之后子进程被回收,然后再次回到等待指令的时候。

主要步骤:

  • 输出命令提示符
  • 从终端获取命令行输入
  • 解析命令行输入信息;
  • 创建子进程;
  • 进程程序替换;
  • 进程等待;

2. 流程图

通过上述的分析,我们可以画出以下的流程图:

image-20231208174726993

3. 实现过程

3.1 初步实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <errno.h>#define NUM 1024//输入缓冲区大小
#define OPT_NUM 64//命令参数最大个数
char lineCommand[NUM];//输入缓冲区
char* argv[OPT_NUM];
int main()
{while(1)//死循环,因为Shell要一只运行着{//打印输出命令提示符printf("[用户名@主机名 当前路径]$ ");fflush(stdout);//由于打印命令提示符的时候没有换行,所以这里手动刷新缓冲区//获取输入char* str = fgets(lineCommand, sizeof(lineCommand) - 1, stdin);//最后一个位置用于在极端情况下保证字符串内有'\0'assert(str);//判断合法性(void)str;lineCommand[strlen(lineCommand) - 1] = '\0';//消除行命令中的换行//命令解析(字符串切割)argv[0] = strtok(lineCommand, " ");int i = 1;while(argv[i++] = strtok(NULL, " "));//使用字符串切割函数依次拿到每个参数//创建子进程pid_t id = fork();if(id == -1){perror("fork");exit(errno);}else if(id == 0){//child//进程程序替换execvp(argv[0], argv);//执行到此处的时候,证明进程替换错误perror("exec:");exit(errno);}else{//parent//进程等待int status = 0;//退出状态pid_t ret = waitpid(id, &status, 0);//阻塞等待if(ret == -1){perror("wait fail");exit(errno);}}}return 0;
}

image-20231208234529296

可以看到,这里我们自己的Shell已经能够执行Linux的基本指令啦,但是我们的ls好像是没有颜色的,那么这里我们在Shell里面加上特判

if(argv[0] != NULL && strcmp(argv[0], "ls") == 0)  //ls颜色显示
{argv[i++] = (char*)"--color=auto";
}

image-20231208235302681

image-20231208235206197

3.2 当前路径

我们知道,在进程运行起来之后,在/proc目录下会有一个内存级的目录存放进程的相关信息,我们可以在这里找到我们运行的进程。

image-20231218003355065

可以看到,这里有两个路径。

  • exe:代表可执行文件在磁盘中的路径
  • cwd:current work directory,当前进程的工作路径,即我们常说的当前路径

当前路径可以更改吗?

可以,当前路径可以通过系统调用chdir更改

image-20231218004724666

path:想要更改的路径

return value:调用成功返回0,失败返回-1

image-20231218010027170

可以看到,cwd已经被更改。


在我们之前实现的Shell中,如果想要使用cd命令更改当前路径,似乎是做不到的

image-20231218010431478

这是因为,按照我们之前的思路,每次都是派生子进程来执行命令,那么子进程的工作目录会切换到我们指定的路径,但是子进程执行完毕之后就被回收了,我们在父进程看不见路径的切换。

所以有了上述chdir的前置知识,那么就很好解决这个问题了:在命令解析之后,如果发现遇到了cd命令,就不要派生子进程,直接使用父进程执行系统调用chdir

image-20231218011359336

image-20231218011520668

3.3 内建命令/外部命令

Linux下的命令分为两种:内建命令外部命令

  • 内建命令是 shell 程序的一部分,其功能实现在 bash 源代码中,不需要派生子进程来执行,也不需要借助外部程序文件来运行,而是由 shell 进程本身内部的逻辑来完成;
  • 外部命令则是通过创建子进程,然后进行进程程序替换,运行外部程序文件等方式来完成。

我们可以使用type命令来区分内建命令还是外部命令

image-20231218011808977

注:博主这里使用的是汉化过的云服务器,所以结果是中文,翻译上有一点差别

我们上面对cd命令的处理就是内建命令的一种,myshell 遇到 cd 命令时,由自己直接来改变进程工作目录,处理完毕直接 continue,而不会创建子进程。

同时,我们发现 echo 命令也是一个内置命令,这其实也很好的解释了 为什么 “echo $变量” 可以查看本地变量以及为什么 “echo $?” 可以获取最近一个进程的退出码 了:

虽然本地变量只在当前进程有效,但是使用 echo 查看本地变量时,shell 并不会创建子进程,而是直接在当前进程中查找,自然可以找到本地变量;

shell 可以通过进程等待的方式获取上一个子进程的退出状态,然后将其保存在 ? 变量中,当命令行输入 “echo $?” 时,直接输出 ? 变量中的内容,然后将 ? 置为0 (echo 正常退出的退出码),也不需要创建子进程。

那么,我们可以为我们的Shell添加echo命令了。

image-20231218012623823

image-20231218012526544

3.4 Shell 的最终实现

最后,经过一系列的优化,我们最终得到了一个简易的Shell的demo,这里附上源码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <errno.h>#define NUM 1024//输入缓冲区大小
#define OPT_NUM 64//命令参数最大个数
char lineCommand[NUM];//输入缓冲区
char* argv[OPT_NUM];
int EXIT_CODE;
int main()
{while(1)//死循环,因为Shell要一只运行着{//打印输出命令提示符printf("[用户名@主机名 当前路径]$ ");fflush(stdout);//由于打印命令提示符的时候没有换行,所以这里手动刷新缓冲区//获取输入char* str = fgets(lineCommand, sizeof(lineCommand) - 1, stdin);//最后一个位置用于在极端情况下保证字符串内有'\0'assert(str);//判断合法性(void)str;lineCommand[strlen(lineCommand) - 1] = '\0';//消除行命令中的换行//命令解析(字符串切割)argv[0] = strtok(lineCommand, " ");int i = 1;if(argv[0] != NULL && strcmp(argv[0], "ls") == 0)//识别ls,自动加上颜色选项{argv[i++] = (char*)"--color=auto";}while(argv[i++] = strtok(NULL, " "));//使用字符串切割函数依次拿到每个参数if(argv[0] != NULL && strcmp(argv[0], "cd") == 0){if(argv[1] != NULL){chdir(argv[1]);}else{printf("no such file or directory\n");}continue;}if(argv[0] != NULL && strcmp(argv[0], "echo") == 0){if(strcmp(argv[1], "$?") == 0){printf("%d\n", EXIT_CODE);EXIT_CODE = 0;}else{printf("%s\n", argv[1]);}continue;}//创建子进程pid_t id = fork();if(id == -1){perror("fork");exit(errno);}else if(id == 0){//child//进程程序替换execvp(argv[0], argv);//执行到此处的时候,证明进程替换错误perror("exec:");exit(errno);}else{//parent//进程等待int status = 0;//退出状态pid_t ret = waitpid(id, &status, 0);//阻塞等待EXIT_CODE = (status >> 8) & 0xFF;if(ret == -1){perror("wait fail");exit(errno);}}}return 0;
}

本节完

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Harmony4.0鸿蒙应用开发初识+实践小案例

Harmony4.0鸿蒙应用开发初识实践小案例 一、华为的“18N”产品战略 在华为HarmonyOS及全场景新品发布会上&#xff0c;华为介绍了华为“18N”三圈层全场景智慧生态解决方案&#xff0c;从而打造面向未来的全新生态&#xff0c;其中&#xff0c;1指的是手机&#xff0c;8指的是…

neuq-acm预备队训练week 9 P1330 封锁阳光大学

题目描述 曹是一只爱刷街的老曹&#xff0c;暑假期间&#xff0c;他每天都欢快地在阳光大学的校园里刷街。河蟹看到欢快的曹&#xff0c;感到不爽。河蟹决定封锁阳光大学&#xff0c;不让曹刷街。 阳光大学的校园是一张由 n 个点构成的无向图&#xff0c;n 个点之间由 m 条道…

小程序开发使用vant库

初始化项目步骤就不做阐述。 第一步&#xff1a;安装依赖 vant/weapp npm下载命令&#xff1a;npm i vant/weapp -S --production npm下载命令&#xff1a;yarn add vant/weapp -S --production 第二步 &#xff1a;修改配置 1、找到miniprogram文件下的app.json 将 app.j…

玩转Docker(五):网络

文章目录 〇、关于linux系统网络一、none网络二、host网络三、bridge网络四、user-defined网络 Docker安装时会自动在host上创建三个网络&#xff0c;我们可用docker network ls命令查看&#xff1a; docker network ls那么这几种网络分别有什么含义呢&#xff1f;在回答这个问…

机器视觉技术与应用实战(Chapter Two-03)

2.5 图像滤波和增强 滤波的作用是&#xff1a;图像中包含需要的信息&#xff0c;也包含我们不感兴趣或需要屏蔽的干扰&#xff0c;去掉这些干扰需要使用滤波。 增强的作用是&#xff1a;通过突出或者抑制图像中某些细节&#xff0c;减少图像的噪声&#xff0c;增强图像的视觉效…

Jenkins----基于 CentOS 或 Docker 安装部署Jenkins并完成基础配置

查看原文 文章目录 基于 CentOS7 系统部署 Jenkins 环境基于 Docker 安装部署 Jenkins环境配置 Jenkins 中文模式配置用户名密码形式的 Jenkins 凭据配置 ssh 私钥形式的 Jenkins 凭据配置 Jenkins 执行任务的节点 基于 CentOS7 系统部署 Jenkins 环境 &#xff08;1&#xff…

使用Nginx实现负载均衡的实践指南

目录 前言1 负载均衡简介2 需要实现的效果3 准备2个tomcat服务器4 配置Nginx实现负载均衡5 Nginx的服务器策略5.1 轮询&#xff08;默认&#xff09;5.2 权重&#xff08;weight&#xff09;5.3 IP哈希&#xff08;ip_hash&#xff09;5.4 响应时间公平分配&#xff08;fair&am…

C# DotNetCore AOP简单实现

背景 实际开发中业务和日志尽量不要相互干扰嵌套&#xff0c;否则很难维护和调试。 示例 using System.Reflection;namespace CSharpLearn {internal class Program{static void Main(){int age 25;string name "bingling";Person person new(age, name);Conso…

Day63力扣打卡

打卡记录 寻找最近的回文数&#xff08;模拟&#xff09; 链接 class Solution:def nearestPalindromic(self, n: str) -> str:m len(n)candidates [10 ** (m - 1) - 1, 10 ** m 1]selfPrefix int(n[:(m 1) // 2])for x in range(selfPrefix - 1, selfPrefix 2):y …

[强网杯 2019]Upload

[强网杯 2019]Upload 开放注册直接注册一个账号然后登录进去 先对页面进行简单文件上传测试发现都不存在漏洞对网站进行目录扫描 发现www.tar.gz 打开发现是tp5框架发现源码 这里如果前面信息收集的完整会发现存在反序列化 对注册&#xff0c;登录&#xff0c;上传文件页面分…

STM32F103RCT6开发板M3单片机教程06--定时器中断

前言 除非特别说明&#xff0c;本章节描述的模块应用于整个STM32F103xx微控制器系列&#xff0c;因为我们使用是STM32F103RCT6开发板是mini最小系统板。本教程使用是&#xff08;光明谷SUN_STM32mini开发板&#xff09; STM32F10X定时器(Timer)基础 首先了解一下是STM32F10X…

时序预测 | Python实现GRU-XGBoost组合模型电力需求预测

时序预测 | Python实现GRU-XGBoost组合模型电力需求预测 目录 时序预测 | Python实现GRU-XGBoost组合模型电力需求预测预测效果基本描述程序设计参考资料预测效果 基本描述 该数据集因其每小时的用电量数据以及 TSO 对消耗和定价的相应预测而值得注意,从而可以将预期预测与当前…

Linux:超级管理员(root用户)创建用户、用户组

root用户&#xff1a; 拥有最大的系统操作权限,而普通用户在许多地方的权限是受限的。 演示&#xff1a; 1、使用普通用户在根目录下创建文件夹&#xff08;失败&#xff09; 2、切换到root用户后&#xff0c;继续尝试&#xff08;成功&#xff09; 3、普通用户的权限&#…

TCP/IP详解——DNS 流量分析

文章目录 1. DNS 流量分析1.1 DNS 基本概念1.2 DNS 系统特性1.3 DNS 效率问题1.4 域名的组成1.5 域名解析系统1.5.1 域名解析过程 1.6 DNS 记录种类1.7 DNS 的报文格式1.7.1 DNS 报文中的基础结构部分1.7.2 DNS 查询报文中的问题部分1.7.3 DNS 响应报文中的资源记录部分1.7.4 示…

【开源项目】WPF 扩展 -- 多画面视频渲染组件

目录 1、项目介绍 2、组件集成 2.1 下载地址 2.2 添加依赖 3、使用示例 3.1 启动动画 3.2 视频渲染 3.3 效果展示 4、项目地址 1、项目介绍 Com.Gitusme.Net.Extensiones.Wpf 是一款 Wpf 扩展组件。基于.Net Core 3.1 开发&#xff0c;当前是第一个发布版本 1.0.0&am…

Java架构师系统架构内部维度分析

目录 1 导语2.1 安全性维度概述2.2 流程安全性2.3 架构安全性2.4 安全维度总结3 伸缩性维度概述和场景思路3.1 无状态应用弹性伸缩3.2 阿里云Knative弹性伸缩3.3 有状态应用弹性伸缩3.4 伸缩性维度总结想学习架构师构建流程请跳转:Java架构师系统架构设计 1 导语

数据仓库与数据挖掘c5-c7基础知识

chapter5 分类 内容 分类的基本概念 分类 数据对象 元组(x,y) X 属性集合 Y 类标签 任务 基于有标签的数据&#xff0c;学习一个分类模型&#xff0c;通过这个分类模型&#xff0c;可以把一组属性x映射到一个特定的类别y上 类别y 提前设定好的--如&#xff1a;学生…

git 切换远程地址分支 推送到指定地址分支 版本回退

切换远程地址 1、切换远程仓库地址&#xff1a; 方式一&#xff1a;修改远程仓库地址 【git remote set-url origin URL】 更换远程仓库地址&#xff0c;URL为新地址。 git remote set-url https://gitee.com/xxss/omj_gateway.git 方式二&#xff1a;先删除远程仓库地址&…

八股文打卡day2——计算机网络(2)

面试题&#xff1a;讲一下三次握手的过程&#xff1f; 我的回答&#xff1a; 1.客户端发送报文段到服务器&#xff0c;主动建立连接。这个报文段中SYN标志位表示&#xff1a;这个报文段是用于连接的&#xff0c;此时SYN标志位设置为1。其中初始序列号字段包含了客户端的初始序…

华为鸿蒙应用--欢迎页SplashPage+倒计时跳过(自适应手机和平板)-ArkTs

鸿蒙ArkTS 开发欢迎页SplashPage倒计时跳过&#xff0c;可自适应平板和手机&#xff1a; 一、SplashPage.ts import { BreakpointSystem, BreakPointType, Logger, PageConstants, StyleConstants } from ohos/common; import router from ohos.router;Entry Component struct…