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

news/2024/11/19 12:35:07/

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

相关文章

通话状态监听-Android13

通话状态监听-Android13 1、Android Telephony 模块结构2、监听和广播获取通话状态2.1 注册2.2 通话状态通知2.3 通话状态 3、通知状态流程* 关键日志 frameworks/base/core/java/android/telephony/PhoneStateListener.java 1、Android Telephony 模块结构 Android Telephony…

[ACTF2020 新生赛]Include (文件包含漏洞)

打开题目&#xff1a; 就一个tips 看看源码&#xff1a; 奥&#xff0c;fileflag.php 而且再看题目&#xff1a;include 应该是文件包含漏洞&#xff0c;是一道php伪协议题目 -.-PHP伪协议-.-&#xff1a; 我们通过 php://filter 来获取源码&#xff1a; 构造payload: …

上海亚商投顾:沪指冲高回落 医药医疗股全线下挫

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 沪指12月15日高开低走&#xff0c;深成指、创业板指冲高回落&#xff0c;科创50指数跌近1%。医药股全线下挫&a…

Eolink Apikit 如何进行 Websocket 接口测试?

什么是 websocket &#xff1f; WebSocket 是 HTML5 下一种新的协议&#xff08;websocket协议本质上是一个基于 tcp 的协议&#xff09;。 它实现了浏览器与服务器全双工通信&#xff0c;能更好的节省服务器资源和带宽并达到实时通讯的目的 Websocket 是一个持久化的协议。…

RabbitMQ插件详解:rabbitmq_recent_history_exchange【RabbitMQ 七】

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 [TOC](rabbitmq_recent_history_exchange) ## 前言 RabbitMQ作为一款强大的消息中间件&#xff0c;提供了许多有趣且功能强大的插件。其中&#xff0c;rabbitmq_recent_history_exchange插件以其独特…

晚期食管癌肿瘤治疗线程分类

文章目录 1、肿瘤治疗的线数1.1 基础概念1.2 线程定义1.3 如何计算治疗线数 2 食管癌治疗指南2.1 食管癌诊疗指南2.1 CSCO 本文前半部分主要来源于参考文件1&#xff0c;其余部分来源于官方指南。无原创内容&#xff0c;全部为摘要。 1、肿瘤治疗的线数 1.1 基础概念 抗肿瘤药…

《微信小程序开发从入门到实战》学习四十九

4.5 实现投票小程序服务端功能 4.5.2 完成获取投票信息功能 修改pages/vote/vote.js文件中getVoteDataFromServer函数&#xff0c;代码如下&#xff1a; getVoteDataFromServer(voteID) { const db wx.cloud.database() db.collection(votes).doc(voteID).get().then(res &…

屏幕超时休眠-Android13

屏幕超时休眠-Android13 1、设置界面1.2 属性值1.2.1 默认值1.2.2 最小值限制 1.3 属性值疑问 Settings.System.SCREEN_OFF_TIMEOUT 2、超时灭屏2.1 锁定屏幕的超时2.2 屏幕灭屏的超时 3、永不休眠* 关键日志 1、设置界面 packages/apps/Settings/src/com/android/settings/dis…