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

news2024/11/27 10:39:34

需要云服务器等云产品来学习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.coloradmin.cn/o/1318972.html

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

相关文章

Mistral MOE架构全面解析

从代码角度理解Mistral架构 Mistral架构全面解析前言Mistral 架构分析分词网络主干MixtralDecoderLayerAttentionMOEMLP 下游任务因果推理文本分类 Mistral架构全面解析 前言 Mixtral-8x7B 大型语言模型 (LLM) 是一种预训练的生成式稀疏专家混合模型。在大多数基准测试中&…

Android 架构 - MVVM

一、概念 概念基于观察者模式&#xff0c;数据的变化会自动更新到UI。通信 View→ViewModel&#xff1a;View作为观察者&#xff0c;监听ViewModel中数据&#xff08;LiveData、Flow&#xff09;的变化从而自动更新UI。 ViewModel→Model&#xff1a;ViewModel调用Model获取数据…

FPGA设计与实战之时钟及时序简介1

文章目录 一、时钟定义二、基本时序三、总结一、时钟定义 我们目前设计的电路以同步时序电路为主,时钟做为电路工作的基准而显得非常重要。 简单的接口电路比如I2C、SPI等,复杂一点接口比如Ethernet的MII、GMII等接口,它们都有一个或多个时钟信号。 那么什么是时钟信号?它…

【华为】文档中命令行约定格式规范(命令行格式规范、命令行行为规范、命令行参数格式、命令行规范)

文章目录 命令行约定格式**粗体&#xff1a;命令行关键字***斜体&#xff1a;命令行参数*[ ]&#xff1a;可选配置{ x | y | ... } 和 [ x | y | ... ]&#xff1a;选项{ x | y | ... }* 和 [ x | y | ... ]*&#xff1a;多选项&<1-n>&#xff1a;重复参数#&#xff…

算法-动态规划

动态规划算法 应用场景-背包问题 介绍 动态规划(Dynamic Programming)算法的核心思想是&#xff1a;将大问题划分为小问题进行解决&#xff0c;从而一步步获取最优解的处理算法动态规划算法与分治算法类似&#xff0c;其基本思想也是将待求解问题分解成若干个子问题&#xff0…

算法:最短路径

文章目录 Dijkstra算法Bellman-Ford算法Floyd-Warshall 本篇总结的是图当中的最短路径算法 Dijkstra算法 单源最短路径问题&#xff1a;给定一个图G ( V &#xff0c; E ) G(V&#xff0c;E)G(V&#xff0c;E)&#xff0c;求源结点s ∈ V s∈Vs∈V到图中每个结点v ∈ V v∈V…

H266/VVC标准的编码结构介绍

概述 CVS&#xff1a; H266的编码码流包含一个或多个编码视频序列&#xff08;Coded Video Swquence&#xff0c;CVS&#xff09;&#xff0c;每个CVS以帧内随机接入点&#xff08;Intra Random Access Point&#xff0c; IRAP&#xff09;或逐渐解码刷新&#xff08;Gradual …

力扣题:数字与字符串间转换-12.18

力扣题-12.18 [力扣刷题攻略] Re&#xff1a;从零开始的力扣刷题生活 力扣题1&#xff1a;38. 外观数列 解题思想&#xff1a;进行遍历然后对字符进行描述即可 class Solution(object):def countAndSay(self, n):""":type n: int:rtype: str""&quo…

小程序静默登录-登录拦截实现方案【全局loginPromis加页面拦截】

实现效果&#xff1a; 用户进入小程序访问所有页面运行onload、onShow、onReady函数时保证业务登录态是有效的 实现难点&#xff1a; 由于小程序的启动流程中&#xff0c;页面级和组件级的生命周期函数都不支持异步阻塞&#xff1b;因此会造成一个情况&#xff0c;app.onLau…

【从零开始学习--设计模式--策略模式】

返回首页 前言 感谢各位同学的关注与支持&#xff0c;我会一直更新此专题&#xff0c;竭尽所能整理出更为详细的内容分享给大家&#xff0c;但碍于时间及精力有限&#xff0c;代码分享较少&#xff0c;后续会把所有代码示例整理到github&#xff0c;敬请期待。 此章节介绍策…

植物分类-PlantsClassification

一、模型配置 一、backbone resnet50 二、neck GlobalAveragePooling 三、head fc 四、loss type‘LabelSmoothLoss’, label_smooth_val0.1, num_classes30, reduction‘mean’, loss_weight1.0 五、optimizer lr0.1, momentum0.9, type‘SGD’, weight_decay0.0001 六、sche…

磁力计LIS2MDL开发(3)----九轴姿态解算

磁力计LIS2MDL开发.3--九轴姿态解算 概述视频教学样品申请完整代码下载使用硬件欧拉角万向节死锁四元数法姿态解算双环PI控制器偏航角陀螺仪解析代码 概述 LIS2MDL 包含三轴磁力计。 lsm6ds3trc包含三轴陀螺仪与三轴加速度计。 姿态有多种数学表示方式&#xff0c;常见的是四元…

【运维笔记】mvware centos挂载共享文件夹

安装mvware-tools 这里用的centos安装 yum install open-vm-tools 设置共享文件夹 依次点击&#xff1a;选项-共享文件夹-总是启用-添加&#xff0c;安装添加向导操作添加自己想共享的文件夹后。成功后即可在文件夹栏看到自己共享的文件夹 挂载文件夹 临时挂载 启动虚拟机&…

lvs-nat部署

LVS负载均衡群集部署——NAT模式 实验环境&#xff1a; 负载调度器&#xff1a;内网关 lvs&#xff0c;ens33&#xff1a;172.16.23.10&#xff1b;外网关&#xff1a;ens36&#xff1a;12.0.0.1 Web服务器1&#xff1a;172.16.23.11 Web服务器2&#xff1a;172.16.23.12 NFS…

【Spring】09 BeanClassLoaderAware 接口

文章目录 1. 简介2. 作用3. 使用3.1 创建并实现接口3.2 配置 Bean 信息3.3 创建启动类3.4 启动 4. 应用场景总结 Spring 框架为开发者提供了丰富的扩展点&#xff0c;其中之一就是 Bean 生命周期中的回调接口。本文将聚焦于其中的一个接口 BeanClassLoaderAware&#xff0c;介…

数据仓库与数据挖掘小结

更加详细的只找得到pdf版本 填空10分 判断并改错10分 计算8分 综合20分 客观题 填空10分 判断并改错10分--错的要改 mooc中的--尤其考试题 名词解释12分 4个&#xff0c;每个3分 经常碰到的专业术语 简答题40分 5个&#xff0c;每道8分 综合 画roc曲线 …

机器视觉技术与应用实战(开运算、闭运算、细化)

开运算和闭运算的基础是膨胀和腐蚀&#xff0c;可以在看本文章前先阅读这篇文章机器视觉技术与应用实战&#xff08;Chapter Two-04&#xff09;-CSDN博客 开运算&#xff1a;先腐蚀后膨胀。开运算可以使图像的轮廓变得光滑&#xff0c;具有断开狭窄的间断和消除细小突出物的作…

C语言数据结构-----二叉树(3)二叉树相关练习题

前言 前面详细讲述了二叉树的相关知识&#xff0c;为了巩固&#xff0c;做一些相关的练习题 文章目录 前言1.某二叉树共有 399 个结点&#xff0c;其中有 199 个度为 2 的结点&#xff0c;则该二叉树中的叶子结点数为&#xff1f;2.下列数据结构中&#xff0c;不适合采用顺序存…

【MySQL】MySQL表的操作-创建查看删除和修改

文章目录 1.创建表2.查看表结构3.修改表4.删除表 1.创建表 语法&#xff1a; CREATE TABLE table_name (field1 datatype,field2 datatype,field3 datatype ) character set 字符集 collate 校验规则 engine 存储引擎;说明&#xff1a; field 表示列名datatype 表示列的类型…

GitHub推荐:下载工具-Motrix

项目地址 GitHub - agalwood/Motrix: A full-featured download manager. 项目简介 Motrix是一个开源的下载工具&#xff0c;支持BT下载、Magnet下载。且下载支持最高64个线程&#xff0c;基本可以说下载速度的上限取决于你的带宽。是一款很不错的下载工具。 项目截图