【Linux】手写一个简易命令行解释器

news2024/11/24 0:09:35

文章目录

  • 1. 了解命令行解释器
    • 1.1 命令行解释器是什么?
    • 1.2 我们为什么要尝试手写一个命令行解释器?
  • 2. 命令行解释器的实现
    • 2.1 打印提示符
    • 2.2 获取用户输入
    • 2.3 创建子进程并进行进程程序替换
    • 2.4 内建命令

1. 了解命令行解释器

1.1 命令行解释器是什么?

命令行解释器是一种软件程序,它接收来自用户的命令行输入,并将其解释和执行。它允许用户通过键入特定的命令和参数来与计算机进行交互和控制。
命令行解释器通常提供一个命令行界面,用户可以在该界面中输入命令,并根据命令的要求执行相应的操作。它是与计算机进行交互的一种文本界面,相比于图形用户界面,命令行界面更加灵活和高效。
而这样的软件程序我们常常称其为Shell

1.2 我们为什么要尝试手写一个命令行解释器?

操作系统的进程控制包括进程创建,进程终止,进程等待,进程程序替换等。单单学习这些概念,我们无法真正地理解它们。一个简易的命令行解释器恰好就是这些概念的应用场景,通过自己手写一个简单的命令行解释器,我们可以更好地理解这些概念。

2. 命令行解释器的实现

2.1 打印提示符

在这里插入图片描述
上图是Linux云服务器中的命令行解释器,可以看到,我们每次在键入命令时,前面都有一串字符,标识我们的用户,主机,路径等等。
所以,我们如果要想实现一个命令行解释器,首先得把这一串提示符打印出来。
在这里插入图片描述
注意:这里为了和系统自带的提示符更相似,末位是不加’\n’的。但是如果不加的的话,就会造成缓冲区不会刷新的问题。所以printf之后我们应该主动刷新缓冲区。
在这里插入图片描述

2.2 获取用户输入

获取用户输出,在这里我们使用的是fgets函数。
在这里插入图片描述
第一个参数是用于读取文本的字符数组的指针。
第二个参数是最大读取的字符数(包括换行符和空字符),防止溢出。
第三个参数是要读取的文件流,通常是stdin

首先我们定义一个宏NUM表示最大输出的字符数,然后定义一个全局数组lineCommand用来存储用户的输入。
在这里插入图片描述在这里插入图片描述

这样用户输出的字符串,都保存在了lineCommand中了。
但是,我们要对用户的输入进行解释的话,首先要对字符串进行分割。
比如用户输入"ls -a -l",我们要把它分割成"ls" “-a” “-l”,根据分割成的子串从而对用户的输入进行解释并执行!
我们定义宏和字符指针数组,用于保存分割之后的字符串。
在这里插入图片描述

字符串分割,我们用的是strtok函数
在这里插入图片描述
第一个参数是要进行分割的字符串,第二个参数为用于指定分割子字符串的分隔符字符串。
strtok函数会根据第二个参数指定的分隔符,将第一个参数拆分为多个子字符串,并以此返回每个子字符串的指针。
每次调用strtok函数时,它会返回一个指向下一个子字符串的指针,在后续调用中,将NULL作为第一个参数传递给strtok函数,以便继续上一次返回位置的下一个字符开始查找下一个字符串。
在这里插入图片描述

2.3 创建子进程并进行进程程序替换

Linux操作系统中,常用的Shell是bash,bash通常执行命令往往创建一个子进程再进程程序替换为指定的命令去执行(也有不是的情况,后面再说),这样做的好处就是一旦出现问题,由于进程的独立性,只有子进程会出问题,而bash进程不会受到任何影响。
我们自己写的命令行解释器也是这种思路。
主进程创建子进程,并使用execvp函数进行进程程序替换,最后父进程回收子进程的资源。为了获取子进程的退出信息,定义两个全局变量lastCode和lastSig在这里插入图片描述
在这里插入图片描述
至此,Shell就写的差不多了。但是我们使用Shell总不能每次就输入一次命令就退出吧,Shell本质是一个死循环,我们也要将我们的代码放入一个死循环中。在这里插入图片描述
下面看看基本的运行效果
在这里插入图片描述
有个小细节,当我们在执行系统命令ls的时候是有颜色的,但是在我们自己写的Shell中却没有颜色,下面进行一些修改,让ls在我们自己写的Shell中也有颜色。
在这里插入图片描述
再来试一下
在这里插入图片描述
这样就有颜色了。

2.4 内建命令

什么是内建命令?

内建命令是指直接内置在操作系统的命令行解释器(如Shell)中的命令,而不是作为外部可执行文件存在。
通俗点说,这个命令不存在于磁盘上,执行它时Shell也不需要再去创建子进程,而是直接在Shell中运行。
常见的内建命令有cd和echo,下面我们尝试实现。

cd命令

现在我们还没有将cd命令设置为内建命令,看看结果是怎样的。
在这里插入图片描述
可以发现,在cd之后,用pwd查看当前路径,发现当前路径是没有发生变化的。
Linux下一切皆文件,正在运行的myshell进程本质上也是一个文件。我们查看这个文件内的内容,找到cwdexecwd保存了myshell的工作路径,当在myshell中pwd时查看到的就是这个路径,exe保存了myshell在磁盘上的路径。
在这里插入图片描述
每次cd的时候,要创建子进程进行cd,子进程进程cd那么也只会改变子进程的cwd,由于进程具有独立性,它不会影响父进程!
如果我们要改变父进程的工作路径,不能创建子进程!
在父进程中对工作路径进行修改,用的是chdir函数。
在这里插入图片描述
传入要改成的路径即可。
在这里插入图片描述

echo命令

使用echo命令,有时候我们要打印本地变量的值。如果创建子进程的话,子进程不能继承Shell内定义的本地变量,那么echo也就不能打印本地变量的值了。并且,echo命令相对简单,不涉及复杂的操作,我们也没必要专门去创建一个子进程去执行它。
在这里插入图片描述

至此,自制简易Shell已全部完毕。下面给出全部的代码!

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>

#define NUM 1024   // 用户输入的最长长度
#define OPT_NUM 64 // 分割后的字符串的最长长度

char lineCommand[NUM]; // 用于存放用户输入的字符串
char *myargv[OPT_NUM]; // 保存分割之后的字符串

int lastCode = 0;
int lastSig = 0;

int main()
{
    while (1)
    {
        // 输出提示符

        printf("[用户名@主机名 当前路径]$ ");
        fflush(stdin); // 清空缓冲区

        // 获取用户输入

        // 第二个参数为什么要减一?要留下一个'\0'的位置!
        fgets(lineCommand, sizeof(lineCommand) - 1, stdin);
        // 但是这样会造成用户输入之后出现一个空行的问题,原因就是把'\n'也读取到了
        // 清除最后的'\n'
        lineCommand[strlen(lineCommand) - 1] = '\0';
        // 将用户的输入字符串进行分割
        myargv[0] = strtok(lineCommand, " "); // 以空格作为分隔符
        int i = 1;                            // i为子字符串的个数
        if (myargv[0] != NULL && strcmp(myargv[0], "ls") == 0)
        {
            myargv[i++] = (char*)"--color=auto"; 
        }
        while (myargv[i++] = strtok(NULL, " "))
            ;
        // cd内建命令
        if (myargv[0] != NULL && strcmp(myargv[0], "cd") == 0)
        {
            if (myargv[1] != NULL) chdir(myargv[1]);
            continue;
        }
        // echo内建命令
        if (myargv[0] != NULL && strcmp(myargv[0], "echo") == 0)
        {
            // 打印上一次运行结果或者具体的文本内容
            if (myargv[1] != NULL && strcmp(myargv[1], "$?") == 0)
                printf("%d %d\n", lastCode, lastSig);
            else   
                printf("%s\n", myargv[1]);
            continue;
        }
        // 创建子进程并进行进程程序替换

        pid_t id = fork(); // 创建子进程
        if (id == 0)
        {
            // 子进程
            execvp(myargv[0], myargv);
            exit(1);
        }
        int status = 0;
        waitpid(id, &status, 0); // 父进程阻塞式地等待子进程,并获取子进程退出信息
    }
    return 0;
}

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

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

相关文章

TiDB-亿级订单数据亚秒响应查询方案

TiDB-亿级订单数据亚秒响应查询方案 TiDB宣传片 1. 什么是TiDB TiDB 是一个分布式 NewSQL 数据库&#xff0c;它支持水平弹性扩展、ACID 事务、标准 SQL、MySQL 语法和 MySQL 协议&#xff0c;具有数据强一致的高可用特性&#xff0c;是一个不仅适合 OLTP 场景还适合 OLAP 场景…

完全背包问题(二维数组 / 一维数组实现)

完全背包 完全背包的一维和二维dp数组 有 N 件物品和容量为 W 的背包&#xff0c;第 i 件物品的重量是 weight[i]&#xff0c;价值是 value[i] 每件物品都有无限个&#xff0c;即同一物品能够放入背包多次&#xff0c;求背包所能装入物品的最大价值总和 完全背包和 0-1 背包不…

小白到运维工程师自学之路 第四十六集 (mongodb复制集)

一、概述 1、 MongoDB复制集&#xff08;MongoDB Replica Set&#xff09;是MongoDB提供的一种高可用性和数据冗余的解决方案。它由多个MongoDB实例组成&#xff0c;其中一个作为主节点&#xff08;Primary&#xff09;&#xff0c;其他节点则扮演从节点&#xff08;Secondary&…

使用finalshell连接Linux服务器出现的问题

第一次使用finalshell远程连接Linux服务器的过程、遇到的问题及解决方案 首先建立连接 ![在这里插入图片描述](https://img-blog.csdnimg.cn/d8836dcd8a224bf093ebdac031f763d5.png 然后问题来了 出现以下问题&#xff1a; java.net.ConnectException: Connection refused:…

地下污水厂配电能效管理平台设计

安科瑞电气股份有限公司 上海嘉定 201800 摘要&#xff1a;结合某地下污水厂项目&#xff0c;从结构、系统组成、系统功能、控制要求、场景模 式等方面介绍了地下污水厂智能照明控制系统&#xff0c;探索了一套适用于地下污水厂的智能照明控制策略&#xff0c;以确保地下污水…

这里会告诉你音频转换器推荐有什么

音频转换格式技术是一项重要的技术&#xff0c;可以将音频文件从一种格式转换为另一种格式&#xff0c;以适应不同设备、平台或应用程序的需求。今天的文章会向你科普音频转换格式技术和音频转换器推荐有什么。 音频转换格式技术应用于很多场景&#xff1a; 1、音乐制作与后期…

银河麒麟服务器v10 sp1 部署.Net6.0项目后无法访问静态文件

上一篇&#xff1a;银河麒麟服务器v10 sp1 部署.Net6.0 http https_csdn_aspnet的博客-CSDN博客 由于本人项目直接从.NetCore3.1升级到.Net6.0的&#xff0c;请参考文章&#xff1a;NetCore3.1项目升级到Net6.0_vs2022 没有startup_csdn_aspnet的博客-CSDN博客 虽然部署项目后…

mysql根据逗号将一行数据拆分成多行数据,顺便展示其他列

1、原始数据演示 2.处理结果 SQL展示 SELECTa.id,a. NAME,substring_index(substring_index(a.shareholder,,,b.help_topic_id 1),, ,- 1) AS shareholder FROMcompany a JOIN mysql.help_topic b ON b.help_topic_id < (length(a.shareholder) - length(REPLACE (a.share…

uni-app 从零开始第二章:底部 tabBar

pages.json 页面路由 | uni-app官网 一、新建 home页面 找到pages目录&#xff0c;新增一个home的页面&#xff0c;勾选上同时新建文件夹 新建完成后&#xff0c;pages.json 中 会自动添加上刚刚新建的文件信息 二、新增tabBar数据 在 pages.json中新增以下代码 "tabB…

微信保存到本地的视频文件怎么转存到手机笔记?

微信是人们之间交流的重要工具。我们经常会在微信上收到一些珍贵的视频文件&#xff0c;比如亲友们的生活片段、孩子们的成长瞬间等等。但是&#xff0c;随着时间的推移&#xff0c;这些视频文件会越来越多&#xff0c;也会有人担心它们的保存问题。 现在很多人都在使用手机笔…

磁盘0和磁盘1

磁盘0和磁盘1 查看磁盘情况磁盘0和磁盘1的区别 查看磁盘情况 此电脑&#xff0c;右键&#xff0c;管理&#xff0c;然后就可以看到计算机管理这个页面 计算机管理页面&#xff0c;存储&#xff0c;磁盘管理&#xff0c;就可以看到磁盘情况了 磁盘0和磁盘1的区别 磁盘0和磁…

【前端进阶】什么是AST?什么是ESLint?如何快速发布自定义ESLint插件?

文章目录 什么是ASTAST在线可视化网站代码如何转化ASTacorn基本使用 什么是ESLintESLint解析原理如何制作ESLint插件安装yeoman创建插件创建规则目录结构实现警告console.error()方法 npm发布如何注册如何登录发布应用问题 nrm下载查看可用镜像源切换镜像源 结束参考文章 什么是…

ChatGPT会让软件测试人员失业吗?

首先&#xff0c;正视ChatGPT &#xff0c;它只是一款提升测试效率的工具&#xff0c;并不会让测试失业 ChatGPT 本质上就是一个搜索引擎的二次封装&#xff0c;它更能理解你的输入意图&#xff0c;它更精确的帮你拼接返回结果。但它就是一个辅助工具&#xff0c;用好了可以帮…

传统主从配置

传统主从配置 MySQL通过二进制文件写入和恢复数据 主服务器一定要打开二进制日志 必须两台服务器&#xff08;或者是多个实例&#xff09; 从服务器需要一次数据初始化 如果主从服务器都是新搭建的话&#xff0c;可以不做初始化 如果主服务器已经运行了很长时间了&#xff0c;可…

从Word Embedding到Bert模型—自然语言处理中的预训练技术发展史

Bert最近很火,应该是最近最火爆的AI进展,网上的评价很高,那么Bert值得这么高的评价吗?我个人判断是值得。那为什么会有这么高的评价呢?是因为它有重大的理论或者模型创新吗?其实并没有,从模型创新角度看一般,创新不算大。但是架不住效果太好了,基本刷新了很多NLP的任务…

web服务端接收多用户并发上传同一文件,保证文件副本只存在一份(附go语言实现)

背景 对于一个文件服务器来说&#xff0c;对于同一文件&#xff0c;应该只保存一份在服务器上。基于这个原则&#xff0c;引发出本篇内容。 本篇仅阐述文件服务器在同一时间接收同一文件的并发问题&#xff0c;这种对于小体量的服务来说并不常见&#xff0c;但是最好还是要留…

存储协议——FC协议讲解

目录 FC基础概念 FC协议结构 FC通信 FC交换网络工作流程&#xff1a;&#xff08;以封装SCSI协议为例&#xff09; FC拓扑结构 FC协议的端口类型 FC适配器&#xff08;FC HBA卡&#xff09; FC基础概念 FC最开始为一种传输协议&#xff0c;由于其性能较高&#xff0c;逐…

我的小流量“转正”心得 --- 下载下方深度语义重排的实践

目录 一、背景 二、通过数据分析找到的问题 三、迭代流程 迭代一&#xff1a; 迭代二&#xff1a; 迭代三&#xff1a; 迭代成功的原因&#xff1a; 知识扩展 四、hnswlib调优过程 五、附录 5.1 hnsw 超参选择 一、背景 在分发中下载带来的收入占比排列仅次于搜索。…

重磅|2024年浙大MPA提前批面试政策公布:申请三步走

说曹操曹操到&#xff01;昨天还在说浙大MPA提面吃迟迟未公布的事情&#xff0c;晚些时候就来了&#xff01;等待许久的MPA考生们可以开始着手筹划自己的提面备考了&#xff01;提前批面试真题周期较长&#xff0c;但是需要做准备的内容确实也不少&#xff0c;本期专注浙大的杭…

如何区分bin log 、redo log 跟 undo log?

概要 MySQL 日志包含了错误日志、查询日志、慢查询日志、事务日志、二进制日志等&#xff0c;如果存储引擎使用的是 InnoDB &#xff0c;二进制日志(binlog)和事务日志(包括redo log和undo log) 是肯定绕不过去的&#xff0c;本篇接下来详细为大家介绍这三种日志。 redo log 为…