手把手带你手撕一个shell

news2024/9/23 7:32:04

 

                                                          🎬慕斯主页修仙—别有洞天 

                                                         ♈️今日夜电波:HEART BEAT—YOASOBI

                                                                2:20━━━━━━️💟──────── 5:35
                                                                    🔄   ◀️   ⏸   ▶️    ☰  

                                      💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍


目录

什么是shell?

怎么实现shell?

shell命令提示符的实现

创建子进程执行命令

内建命令的引入

内建命令的实现

shell的拼接

shell的总体代码


什么是shell?

        Shell是一种应用程序,它连接了用户和Linux内核,让用户能够更加高效、安全、低成本地使用Linux内核。 Shell是系统的用户界面,提供了用户与内核进行交互操作的一种接口。 它接收用户输入的命令并把它送入内核去执行。Shell并不是内核的一部分,而是一个建立在内核基础上的应用程序,与QQ、迅雷、Firefox等其它软件类似。

        说大白话:他就是一个进程!作为一个进程,他当然是可以实现的啦,那么我们就简简单单手撕一个简易的进程吧!

怎么实现shell?

shell命令提示符的实现

        Shell提示符是Linux系统中的一种表示形式,它出现在用户登录并启动终端模拟包或从Linux控制台登录后。它是用户与Shell进行交互的重要途径,提示符就象征着通往Shell的大门,用户可以在提示符处输入Shell命令。对于普通用户而言,Base shell的默认提示符就是一个美元符号"$",表示等待用户输入命令。如下:

[amazon@iZ7xvfrafhk3mf5qwrf2gxZ myshell]$

        要实现这个命令提示符,我们需要获取当前的用户、当前主机以及当前路径,因此根据之前所学的知识,写出以下的函数获取对应的数据:

        对于getenv()的回忆—主要用于搜索和返回环境变量的值。这个函数的参数是环境变量的名称,如果对应的环境变量存在,那么getenv函数就会返回一个指向该环境变量值的指针。

//获取用户名
const char* getUsername()
{
    const char* name = getenv("USER");
    if (name) return name;
    else return "none";
}

//获取主机名
const char* getHostname()
{
    const char* hostname = getenv("HOSTNAME");
    if (hostname) return hostname;
    else return "none";
}

//获取当前路径
const char* getCwd()
{
    const char* cwd = getenv("PWD");
    if (cwd) return cwd;
    else return "none";
}

        在得到这些数据后我们就可以组装出一个简易的命令行提示符了!再接收命令行输入的命令就可以完成基本的命令输入。需要注意的是:这里使用fgets是为了将空格也接收进来为什么return strlen(command)呢?这是判断是否有命令输入,如果没有输入,即只是传了一个回车,那么会返回一个0,后续主函数中会用于接收,通过continue跳过后续的函数(总体是一个死循环)。

//获取用户输入命令
int getUserCommand(char* command, int num)
{
    printf("[%s@%s %s]# ", getUsername(), getHostname(), getCwd());
    char* r = fgets(command, num, stdin); // 最终你还是会输入\n
    if (r == NULL) return -1;
    // "abcd\n" "\n"
    command[strlen(command) - 1] = '\0'; // 为了将末尾的\n去掉,注意这样并不会越界 ,因为只要是输入都至少会有个\n
    return strlen(command);
}

创建子进程执行命令

        从上面的程序我们可知,存储的命令是整段的,对此我们需要进行命令的分割,用strtok按空格进行分割。从前面的知识中我们也知道:shell是创建子进程来让他执行任务的!对此,我们也创建一子进程,通过execvp的程序替换来执行对应的命令。而父进程需要执行的任务是waitpid等待子进程完成任务后储存对应的返回值,用于子进程任务完成怎么样的判断,对此shell的基本功能实现就完成了。但是还是需要改进的!

//按照空格分割命令
void commandSplit(char* in, char* out[])
{
    int argc = 0;
    out[argc++] = strtok(in, SEP);
    while (out[argc++] = strtok(NULL, SEP));

}

//总体运行
int execute(char* argv[])
{
    pid_t id = fork();
    if (id < 0) return -1;
    else if (id == 0) //child
    {
        //进程替换
        execvp(argv[0], argv); // cd ..
        exit(1);
    }
    else // father
    {
        int status = 0;//存储返回值,echo $?
        pid_t rid = waitpid(id, &status, 0);
        if (rid > 0) {
            lastcode = WEXITSTATUS(status);//存储返回值,echo $?
        }
    }

    return 0;
}

内建命令的引入

        由上图我们可知,当我们使用ls、pwd等等命令时自定义的shell是能够正常运行的,但是对于cd命令还有export是不能运行的。回过头想想,我们是怎么实现shell的呢?我们运用了进程的替换,我们替换了子进程中的进程而上面的shell中并没有实现!对此我们可以肯定的是:这些命令并不是外部的进程!这也引出了—内建命令。内建命令,如其名称所示,是由Shell自身提供的命令。这些命令已经和shell编译为一体,不需要借助外部程序文件来运行。这意味着它们执行速度更快,效率更高。通俗的讲,内建命令实际上就是shell中的一个函数!

内建命令的实现

        由于内建命令已经和shell编译为一体,不需要借助外部程序文件来运行,因此我们需要一一的实现对应的内建命令,这里就先实现三个,如果要接着实现可以接着else if 实现下去。详细解释见代码:

//获取家目录
char* homepath()
{
    char* home = getenv("HOME");
    if (home) return home;
    else return (char*)".";
}

void cd(const char* path)//用于cd命令
{
    chdir(path);//用于改变当前工作目录
    char tmp[1024];//临时储存,但不能直接使用,需要全局变量,否则栈帧销毁也会销毁
    getcwd(tmp, sizeof(tmp));//获取当前工作目录的绝对路径
    sprintf(cwd, "PWD=%s", tmp); //输出到全局变量中,保证不会失效
    putenv(cwd);//改变或增加环境变量的内容
}

// 什么叫做内键命令: 内建命令就是bash自己执行的,类似于自己内部的一个函数!
// 1->yes, 0->no, -1->err
int doBuildin(char* argv[])
{
    if (strcmp(argv[0], "cd") == 0)//通过改变当前环境变量的内容从而改变->命令行提示符路径
    {
        char* path = NULL;
        if (argv[1] == NULL) path = homepath();//cd 回到家目录
        else path = argv[1];//根据命令到指定路径
        cd(path);
        return 1;
    }
    else if (strcmp(argv[0], "export") == 0)
    {
        if (argv[1] == NULL) return 1;
        strcpy(enval, argv[1]);//同理需要全局变量防止失效
        putenv(enval); // 改变环境变量
        return 1;
    }
    else if (strcmp(argv[0], "echo") == 0)
    {
        if (argv[1] == NULL) {//echo 输出换行
            printf("\n");
            return 1;
        }
        if (*(argv[1]) == '$' && strlen(argv[1]) > 1) {//根据指令输出
            char* val = argv[1] + 1; // $PATH $?
            if (strcmp(val, "?") == 0)
            {
                printf("%d\n", lastcode);
                lastcode = 0;
            }
            else {
                const char* enval = getenv(val);
                if (enval) printf("%s\n", enval);
                else printf("\n");
            }
            return 1;
        }
        else {//只是输出到屏幕
            printf("%s\n", argv[1]);
            return 1;
        }
    }
    else if (0) {}//接下来的内建命令

    return 0;
}

shell的拼接

        shell在Linux中是一直运行的,因此为一个死循环!,我们定义一个usercommand字符数组用于接收储存从命令行收到的初步命令,定义一个字符串数组来接收通过commandSplit分割后的字符串,接下来区分是否为内建命令,按照argv里面的命令进行执行程序替换或者执行内建命令。

int main()
{
    while (1) {
        char usercommand[NUM];
        char* argv[SIZE];
        // 1. 打印提示符&&获取用户命令字符串获取成功
        int n = getUserCommand(usercommand, sizeof(usercommand));
        if (n <= 0) continue;
        // 2. 分割字符串
        // "ls -a -l" -> "ls" "-a" "-l"
        commandSplit(usercommand, argv);
        // 3. check build-in command
        n = doBuildin(argv);
        if (n) continue;
        // 4. 执行对应的命令
        execute(argv);
    }
}

shell的总体代码

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

#define NUM 1024
#define SIZE 64
#define SEP " "

char cwd[1024];
char enval[1024]; // for test
int lastcode = 0;

//获取家目录
char* homepath()
{
    char* home = getenv("HOME");
    if (home) return home;
    else return (char*)".";
}

//获取用户名
const char* getUsername()
{
    const char* name = getenv("USER");
    if (name) return name;
    else return "none";
}

//获取主机名
const char* getHostname()
{
    const char* hostname = getenv("HOSTNAME");
    if (hostname) return hostname;
    else return "none";
}

//获取当前路径
const char* getCwd()
{
    const char* cwd = getenv("PWD");
    if (cwd) return cwd;
    else return "none";
}

//获取用户输入命令
int getUserCommand(char* command, int num)
{
    printf("[%s@%s %s]# ", getUsername(), getHostname(), getCwd());
    char* r = fgets(command, num, stdin); // 最终你还是会输入\n
    if (r == NULL) return -1;
    // "abcd\n" "\n"
    command[strlen(command) - 1] = '\0'; // 为了将末尾的\n去掉,注意这样并不会越界 ,因为只要是输入都至少会有个\n
    return strlen(command);
}

//按照空格分割命令
void commandSplit(char* in, char* out[])
{
    int argc = 0;
    out[argc++] = strtok(in, SEP);
    while (out[argc++] = strtok(NULL, SEP));

}

//总体运行
int execute(char* argv[])
{
    pid_t id = fork();
    if (id < 0) return -1;
    else if (id == 0) //child
    {
        //进程替换
        execvp(argv[0], argv); // cd ..
        exit(1);
    }
    else // father
    {
        int status = 0;//存储返回值,echo $?
        pid_t rid = waitpid(id, &status, 0);
        if (rid > 0) {
            lastcode = WEXITSTATUS(status);//存储返回值,echo $?
        }
    }

    return 0;
}



void cd(const char* path)
{
    chdir(path);//用于改变当前工作目录
    char tmp[1024];//临时储存,但不能直接使用,需要全局变量,否则栈帧销毁也会销毁
    getcwd(tmp, sizeof(tmp));//获取当前工作目录的绝对路径
    sprintf(cwd, "PWD=%s", tmp); //输出到全局变量中,保证不会失效
    putenv(cwd);//改变或增加环境变量的内容
}

// 什么叫做内键命令: 内建命令就是bash自己执行的,类似于自己内部的一个函数!
// 1->yes, 0->no, -1->err
int doBuildin(char* argv[])
{
    if (strcmp(argv[0], "cd") == 0)
    {
        char* path = NULL;
        if (argv[1] == NULL) path = homepath();
        else path = argv[1];
        cd(path);
        return 1;
    }
    else if (strcmp(argv[0], "export") == 0)
    {
        if (argv[1] == NULL) return 1;
        strcpy(enval, argv[1]);//同理需要全局变量防止失效
        putenv(enval); // ???
        return 1;
    }
    else if (strcmp(argv[0], "echo") == 0)
    {
        if (argv[1] == NULL) {
            printf("\n");
            return 1;
        }
        if (*(argv[1]) == '$' && strlen(argv[1]) > 1) {
            char* val = argv[1] + 1; // $PATH $?
            if (strcmp(val, "?") == 0)
            {
                printf("%d\n", lastcode);
                lastcode = 0;
            }
            else {
                const char* enval = getenv(val);
                if (enval) printf("%s\n", enval);
                else printf("\n");
            }
            return 1;
        }
        else {
            printf("%s\n", argv[1]);
            return 1;
        }
    }
    else if (0) {}

    return 0;
}

int main()
{
    while (1) {
        char usercommand[NUM];
        char* argv[SIZE];
        // 1. 打印提示符&&获取用户命令字符串获取成功
        int n = getUserCommand(usercommand, sizeof(usercommand));
        if (n <= 0) continue;
        // 2. 分割字符串
        // "ls -a -l" -> "ls" "-a" "-l"
        commandSplit(usercommand, argv);
        // 3. check build-in command
        n = doBuildin(argv);
        if (n) continue;
        // 4. 执行对应的命令
        execute(argv);
    }
}

 


                         感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                       

                                                                        给个三连再走嘛~  

 

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

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

相关文章

Underactuated Robotics - 欠驱动机器人学(一)- 全驱动与欠驱动系统

系列文章目录 前言 如今的机器人行动过于保守&#xff0c;只能完成机械性能所能完成的一小部分任务&#xff0c;实现一小部分性能。在某些情况下&#xff0c;我们仍然从根本上受限于在结构化工厂环境中成熟的刚性机械臂控制技术&#xff0c;在这种环境中&#xff0c;可以使用大…

cad快速看图软件免费版(手机在线cad快速看图)

cad快速看图软件免费版(手机在线cad快速看图) 很多机械设计师日常工作过程中涉及到多种格式的cad图纸&#xff0c;cad图纸大多都需要cad设计软件才能打开&#xff0c;然而很多小伙伴并没有下载相应的cad设计软件&#xff0c;这种情况下如何进行cad快速看图呢&#xff1f; 今天…

Chromedriver 下载和安装指南

1. 确定Chrome浏览器版本 首先&#xff0c;在谷歌浏览器中找到当前版本信息。 打开“设置”&#xff0c;点击“关于谷歌”即可看到版本号。确保后续下载的Chromedriver版本与Chrome浏览器版本一致。或者直接跳转网页地址&#xff1a;chrome://settings/help 2. 下载Chromedri…

告别2023,无论何年我都是最幸福的程序员。

序 2023年注定是不平凡的一年&#xff0c;有太多的意外&#xff0c;无论是工作还是家庭。唯一不变的是坚持&#xff0c;岁月的静好&#xff0c;离不开同事们的负重前行&#xff0c;作为一个程序员来说&#xff0c;我是幸运的&#xff0c;也是最幸福的。生活的漫漫长路&#xf…

Flask 会员列表展示

感谢编程浪子师傅的源码信息分享 web/controllers/member/Member.py # -*- coding: utf-8 -*- from flask import Blueprint,request,redirect,jsonify from common.libs.Helper import ops_render,iPagination,getCurrentDate,getDictFilterField,selectFilterObj from comm…

MongoDB数据类型详解

BSON 协议与数据类型 MongoDB 为什么会使用 BSON&#xff1f; JSON 是当今非常通用的一种跨语言 Web 数据交互格式&#xff0c;属 ECMAScript 标准规范的一个子集。JSON &#xff08;JavaScript Object Notation&#xff0c;JS 对象简谱&#xff09;即 JavaScript 对象表示法…

日程安排小程序实战教程

日常中我们经常有一些事情需要提醒自己&#xff0c;使用日历的形式比较符合实际的使用习惯。本篇我们就利用微搭低代码工具带着大家开发一款日程安排的小程序。 1 创建数据源 登录微搭低代码控制台&#xff0c;打开数据模型&#xff0c;点击创建 输入数据源的名称日程安排 …

vue封装组件(一)标签和下拉框组合实现添加数据

背景: 最近接入短剧内容&#xff0c;需要添加短剧合作方。在详情页需要支持添加组件 方案一&#xff1a;标签tag加上输入框实现添加数据。图片见下 这个是刚开始做的&#xff0c;后来产品觉得这样会造成随意修改数据&#xff0c;需要改成下拉框形式添加 方案二&#xff1a;标签…

【人工智能】深入了解人工智能的核心算法与应用实践

人工智能 学习AI要看的第一本书人工智能应当以人为本人工智能&#xff08;第3版&#xff09;通晓六点&#xff0c;明白人工智能是怎么回事基本概念和历史基础知识基于知识的系统高级专题现在和未来安全和编程 人工智能已经是基础学科 学习AI要看的第一本书 人工智能知识对于当…

PLC分段传送带控制示例

一、为了节约用电&#xff0c;将长长的传送带进行分段&#xff0c;哪断上有物品&#xff0c;哪断才运行 二、每一断末尾都有传感器&#xff0c;传感器能感受到物体有没有到传送带的末尾 三、这个传感器是接近开关 四、控制流程 五、IO地址分配 六、按下启动按钮后&#xff0c;…

【Linux软件包管理器】yum详解

目录 1、什么是软件包 2、yum的操作 1&#xff09;yum源 2&#xff09;三板斧 ① yum list ② yum install [软键名] ③ yum remove [软件名] 1、什么是软件包 在Linux下安装软件, 一个通常的办法是下载到程序的源代码, 并进行编译, 得到可执行程序. 但是这样太麻烦了,…

自存react crash course(1)

1.创建一个react 项目 确保有node.js 创建名为react-task-tracker的react项目 npx create-react-app react-task-tracker 启动项目 npm start2.项目结构 所有组件都是放在src下面的 3. jsx // jsx语法 和html很像&#xff0c;class用的是className来使用css的样式<div…

【unity】Obi插件架构组成(参数详细解释)——解算器四面板设置、三种更新器、参与者介绍

文章目录 一、架构&#xff08;Architecture&#xff09;1.1 Obi解算器&#xff08;ObiSolver&#xff09;1.2 ObiUpdater1.3 ObiActorBlueprint1.4 Obi参与者&#xff08;ObiActor&#xff0c;如ObiRope等&#xff09; 二、Obi解算器&#xff08;ObiSolver&#xff09;2.1 解算…

vue3学习 【2】vite起步和开发工具基本配置

vite的简介 官方文档 刚起步学习&#xff0c;所以我们只需要按照官方文档的入门流程即可。推荐阅读一下官网的为什么使用vite vite目前需要的node版本是18&#xff0c;可以参考上一篇文章的安装nvm&#xff0c;用来进行多版本的node管理。 vite安装与使用 npm create vitela…

24年初级会计资格考试报名信息采集流程共10大步骤,千万不要搞错

2024年初级会计资格考试报名信息采集流程共10大步骤&#xff0c;不要搞错哦&#xff1b; 第一步&#xff1a;输入证件号、点击登录 第二步&#xff1a;阅读采集须知 第三步&#xff1a;填写个人信息&#xff08;支付宝搜索"亿鸣证件照"或者微信搜索"随时照&q…

C# Onnx Chinese CLIP 通过一句话从图库中搜出来符合要求的图片

目录 效果 生成图片特征 查找踢足球的人 测试图片 模型信息 image_model.onnx text_model.onnx 项目 代码 Form1.cs Clip.cs 下载 C# Onnx Chinese CLIP 通过一句话从图库中搜出来符合要求的图片 效果 生成图片特征 查找踢足球的人 测试图片 模型信息 image_mod…

Java/JDK下载安装与环境配置

Java由Sun Microsystems&#xff08;现在是Oracle的子公司&#xff09;于1995年首次发布。它是一种面向对象的编程语言&#xff0c;广泛应用于Web开发、移动应用程序开发、桌面应用程序开发和企业级应用程序开发等领域。 Java语言的主要特点是跨平台、可移植性强、安全性高和具…

【AI视野·今日Sound 声学论文速览 第三十九期】Tue, 2 Jan 2024

AI视野今日CS.Sound 声学论文速览 Tue, 2 Jan 2024 Totally 7 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Sound Papers Enhancing Pre-trained ASR System Fine-tuning for Dysarthric Speech Recognition using Adversarial Data Augmentation Authors Huimen…

《微机原理与应用》期末考试题库(附答案解析)

第1章 微型计算机概述 1.微型计算机的硬件系统包括___A _____。 A&#xff0e;控制器、运算器、存储器和输入输出设备 B&#xff0e;控制器、主机、键盘和显示器 C&#xff0e;主机、电源、CPU和输入输出 D&#xff0e;CPU、键盘、显示器和打印机 2.微处…

LLM Agent零微调范式 ReAct Self Ask

前三章我们分别介绍了思维链的使用&#xff0c;原理和在小模型上的使用。这一章我们正式进入应用层面&#xff0c;聊聊如何把思维链和工具使用结合得到人工智能代理。 要回答我们为什么需要AI代理&#xff1f;代理可以解决哪些问题&#xff1f;可以有以下两个视角 首先是我们…