Linux一学就会——编写自己的shell

news2024/10/5 10:22:46

编写自己的shell

进程程序替换

替换原理

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数
以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动
例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

在这里插入图片描述

替换函数

其实有几种以exec开头的函数,统称exec函数:

#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);

解释
exec是函数替换的开头,后面跟的都是多加的功能:
l:list的简写,表示参数采用列表。
p:path的简写,就是自动搜索并添加环境变量。可以使用环境变量PATH,无需写全路径。
v:vector的简写,是可以用参数数组。
e:environment的简写,就是环境变量。就是带e都要自己组装环境变量,而且是数组形式传入。

这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
如果调用出错则返回-1
所以exec函数只有出错的返回值而没有成功的返回值。

在这里插入图片描述
可变参数
我们刚刚可以看到int execl(const char *path, const char *arg, …);
比如:

int func(int, ... )  {
   .
   .
   .
}
 
int main() {
   func(2, 2, 3);
   func(3, 2, 3, 4);
}

函数 func() 最后一个参数写成省略号,即三个点号(…),省略号之前的那个参数是 int,代表了要传递的可变参数的总数。为了使用这个功能,您需要使用 stdarg.h 头文件,该文件提供了实现可变参数功能的函数和宏。具体步骤如下:
定义一个函数,最后一个参数为省略号,省略号前面可以设置自定义参数。
在函数定义中创建一个 va_list 类型变量,该类型是在 stdarg.h 头文件中定义的。
使用 int 参数和 va_start() 宏来初始化 va_list 变量为一个参数列表。宏 va_start() 是在 stdarg.h 头文件中定义的。
使用 va_arg() 宏和 va_list 变量来访问参数列表中的每个项。
使用宏 va_end() 来清理赋予 va_list 变量的内存。

也就是说可变参数是放在传入参数最后,放在中间必须在输入结束之后再输入一个NULL,而且可变参数和前面放的参数类型一致。
exec调用举例:

#include <unistd.h>
int main()
{
char *const argv[] = {"ps", "-ef", NULL};
char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
execl("/bin/ps", "ps", "-ef", NULL);
// 带p的,可以使用环境变量PATH,无需写全路径
execlp("ps", "ps", "-ef", NULL);
// 带e的,需要自己组装环境变量
execle("ps", "ps", "-ef", NULL, envp);
execv("/bin/ps", argv);
// 带p的,可以使用环境变量PATH,无需写全路径
execvp("ps", argv);
// 带e的,需要自己组装环境变量
execve("/bin/ps", argv, envp);
exit(0);
}

事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在
man手册第3节。这些函数之间的关系如下图所示。

在这里插入图片描述

开始写自己的shell

用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表,它随着时间的流逝从左
向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结
束。在这里插入图片描述
每当输入一个命令时,bash就会创建一个子进程来实现的要的命令进程,上述就是ls,等待子进程退出,主进程继续等待命令输入和读取命令,再创建子进程等…

第一步创建一个界面然后让他一直死循环

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include  <ctype.h>

#define MAX_CMD 1024
char command[MAX_CMD];
int myshell_face()
{
    memset(command, 0x00, MAX_CMD);
    printf("[Tomshell$]#");
    fflush(stdout);
    if (scanf("%[^\n]%*c", command) == 0)
    {
        getchar();
        return -1;
    }
    return 0;
}
int main(int argc, char *argv[])
{
    while (1) // shell主循环
    {
        myshell_face()}
    return 0;
}

当然这个界面是可以输入命令的,但是你怎么输入都没用。

接下来是解析你输入的命令了。
把刚刚输入的命令行分析出来,比如遇到空格就会再次push_back命令行数组,当有空格就跳过空格,知道遇到NULL为止。

char **do_parse(char *command)
{
    int argc = 0;
    static char *argv[32];
    char *ptr = command;
    while (*ptr != '\0')
    {
        if (!isspace(*ptr))//如果不是空格就一直读取命令,直到遇到空格
        {
            argv[argc++] = ptr;
            while ((!isspace(*ptr)) && (*ptr) != '\0')//#include  <ctype.h>   isspace检测是否遇到空格
            {
                ptr++;              //如果不是空格就一直读取命令,直到遇到空格
            }
        }
        else
        {
            while (isspace(*ptr))//如果命令前几个是空格就消除空格
            {
                //*ptr = '\0';//这句就不用加了
                ptr++;
            }
        }
    }
    argv[argc] = NULL;
    return argv;
}

解析完之后返回的是命令行参数数组指针

开始创建子进程并且用execvp替换子进程。

int do_exec(char *command)//进程替换函数=》用的就是exec
{
    char **argv = {NULL};
    int pid = fork(); // 一切形式的进程都让子进程去办,子进程就是白手套。
    if (pid == 0)
    {
        argv = do_parse(command);
        if (argv[0] == NULL)
        {
            exit(-1);
        }
        execvp(argv[0], argv); // 进程替换函数,可以添加环境变量p(path),参数格式是数组v(vector)
    }                          // 可以把exec当作call(goto)函数,exit当作return函数。
    else
    {
        waitpid(pid, NULL, 0);
    }
    return 0;
}

这样就可以在子进程实现命令行进程了。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include  <ctype.h>

#define MAX_CMD 1024
char command[MAX_CMD];
int myshell_face()
{
    memset(command, 0x00, MAX_CMD);
    printf("[Tomshell$]#");
    fflush(stdout);
    if (scanf("%[^\n]%*c", command) == 0)
    {
        getchar();
        return -1;
    }
    return 0;
}
char **do_parse(char *command)
{
    int argc = 0;
    static char *argv[32];
    char *ptr = command;
    while (*ptr != '\0')
    {
        if (!isspace(*ptr))//如果不是空格就一直读取命令,直到遇到空格
        {
            argv[argc++] = ptr;
            while ((!isspace(*ptr)) && (*ptr) != '\0')//#include  <ctype.h>   isspace检测是否遇到空格
            {
                ptr++;              //如果不是空格就一直读取命令,直到遇到空格
            }
        }
        else
        {
            while (isspace(*ptr))//如果命令前几个是空格就消除空格
            {
                //*ptr = '\0';//这句就不用加了
                ptr++;
            }
        }
    }
    argv[argc] = NULL;
    return argv;
}
int do_exec(char *command)//进程替换函数=》用的就是exec
{
    char **argv = {NULL};
    int pid = fork(); // 一切形式的进程都让子进程去办,子进程就是白手套。
    if (pid == 0)
    {
        argv = do_parse(command);
        if (argv[0] == NULL)
        {
            exit(-1);
        }
        execvp(argv[0], argv); // 进程替换函数,可以添加环境变量p(path),参数格式是数组v(vector)
    }                          // 可以把exec当作call(goto)函数,exit当作return函数。
    else
    {
        waitpid(pid, NULL, 0);
    }
    return 0;
}
int main(int argc, char *argv[])
{
    while (1) // shell主循环
    {
        if (myshell_face() < 0)
            continue;
        do_exec(command);
    }
    return 0;
}

最终成果:
在这里插入图片描述

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

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

相关文章

Node.js 是什么?

简介 Node.js入门指南&#xff0c;服务器端JavaScript运行时环境。Node.js是在Google Chrome V8 JavaScript引擎的基础上构建的&#xff0c;它主要用于创建web服务器&#xff0c;但并不局限于此。 实际上Node.js 是把运行在浏览器中的js引擎抽离处理&#xff0c;进行再次封装…

MagicaCloth2安装教程

您可访问官网查看详情&#xff1b; MagicaSoft Unity Assets – Magica Soft 也可通过我的资源文件获得此插件的详细教程&#xff1a; (19条消息) UnityMagicaCloth2插件中文文档&#xff08;机翻/部分&#xff09;-Unity3D文档类资源-CSDN文库 MagicaCloth2是基于ECS开发的…

水质信息监测与管理系统

1.1 系统总体设计 1.1.1 系统组成 水质信息监测与管理系统由水质监测站网管理、水质监测数据管理、水质分析评价、水质监测资料整汇编、水质信息查询、水质信息发布等组成。 水质监测站网管理主要实现对各类监测站网&#xff08;固定监测站网、自动监测站网、动态监测站网&a…

Party Again!转录组+LC代谢组=899/组!

转录组代谢组&#xff0c;是基于代谢组和转录组数据&#xff0c;开展表达基因&#xff08;mRNA&#xff09;与代谢物的相关性分析。可实现差异代谢物与时序表达的差异基因的共表达分析&#xff0c;构建核心调控网络机制&#xff0c;找出其中的关键候选基因&#xff0c;揭示表型…

前端001_初始化数据库管控管理系统

数据库管控管理系统采用 Vue.js ElementUI 来搭建系统的前端。 1、技术栈 技术名说明vue.js前端vuex状态管理器mockjs模拟后台apiaxios拦截器echart图标element-ui组件库vue-element-admin脚手架&#xff0c;原始参照的项目模版mavon-editormarkdown编辑器 2、ElementUI 简…

用DG备库做的rman备份恢复一个数据库

环境描述&#xff1a; 1.因为主库存储空间不足&#xff0c;于是将备份放在dg备库上做。 2.主库因为磁盘空间问题&#xff0c;数据文件有两个目录。 3.dg备库因为主库两个数据文件目录里面有两个同名数据文件&#xff0c;所有dg备库也有两个数据文件目录。 4.主库与备库与测…

Grafana 系列-统一展示-1-开篇

系列文章 Grafana 系列文章 Grafana 简介 Grafana 是 Grafana Labs 的第一款也是最重要的产品。它的定位是可视化, 用于监控展示 和 可观察性. 是当前最为完善、流行的云原生、公有云和企业监控可视化平台。 Dashboard anything. Observe everything 无论你的数据存储在哪…

从 PC 解锁 Android 手机的 6 种有效方法

在这个数字时代&#xff0c;手机已成为我们生活的重要组成部分。我们将它们用于各种用途&#xff0c;从跟踪我们的工作和社交日程到与亲人交流。 然而&#xff0c;有时我们的手机会成为令人沮丧的源头&#xff0c;尤其是当我们不小心将自己拒之门外时。但是您知道可以使用计算…

基于SpringBoot+Vue实现的酒店管理系统

【简介】 基于springbootvue实现的酒店管理系统&#xff0c;用于酒店客房业务管理与酒店内部管理。 【功能结构】 【技术架构】 后端&#xff1a;springbootmybatis 前端&#xff1a;vue element-ui 环境&#xff1a;mysqlmaven node 【代码结构与数据库】 【功能详述】…

LeetCode232. 用栈实现队列

232. 用栈实现队列 描述示例解题思路以及代码 描述 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作&#xff08;push、pop、peek、empty&#xff09;&#xff1a; 实现 MyQueue 类&#xff1a; void push(int x) 将元素 x 推到队列的末尾 int pop()…

UICollectionView 实现整页翻动(每页3个cell)

提示&#xff1a;页面架构是通过UICollectionView做的分页&#xff0c;分页点PageControl使用的是<SDCycleScrollView/TAPageControl.h> &#xff0c;布局架构使用的是Masonry 前言 为了实现UICollectionView无限翻动&#xff0c;连续滑动&#xff0c;主要是利用pagingE…

海豚1.3单节点,多集群设置

最近出差忙项目&#xff0c;一直没更新&#xff0c;现在项目结尾了。回来继续搞集群 公司因为CDH升级为CDP&#xff0c;两套环境数据和任务慢慢迁移&#xff0c;但是调度任务需要同时跑批。 而我们的海豚调度是单节点的&#xff0c;master和worker等服务都在一台节点上。 之前…

基于VBA实现成绩排序的最佳方法-解放老师的双手

作为一名老师&#xff0c;每到期末就要面对一件让人头疼的事情——成绩表统计。 首先&#xff0c;要收集每个学生的考试成绩。这需要花费大量的时间和精力&#xff0c;因为每个学生都有多门科目的成绩需要统计。 其次&#xff0c;要将每个学生的成绩录入到电子表格中。这看起来…

【今天聊聊生产力】提升研发生产力的神器推荐

1、Free Mybatis Tool、MybatisX 用于DAO层和Mapper层之间跳转 Mapper和DAO层跳转&#xff0c;可以用的插件比较多&#xff0c;比较推荐如下两款&#xff0c;功能基本一致&#xff0c;只是样式小有差别。 Free Mybatis Tool&#xff0c;样式为一个绿色箭头&#xff0c;简洁明了…

docker 部署JAVA应用OOM的排障经历——筑梦之路

故障现象&#xff1a; 使用docker部署JAVA的应用&#xff0c;tomcat作为中间件容器&#xff0c;启动应用时总是报错无法创建Java虚拟机&#xff0c;然后就是OOM 报错信息&#xff1a; 不管是从docker容器的日志还是系统日志均未发现有用的信息&#xff0c;也尝试更换过镜像tom…

C++入门3(C++新特性 using string auto)

C入门3 C新特性auto推导规则auto 作为函数的形参类型decltype基于范围for循环 typedef与usingC语言定义变量typedef 在C语言中的写法using在C11中的写法using与template的结合 string的简单使用 C新特性 auto推导规则 auto类型推导: auto定义的变量&#xff0c;可以根据初始化…

开源推荐,超级棒的云原生的Kafka管控平台,清新优雅~~

哈喽&#xff0c;大家好&#xff0c; 之前给大家介绍过很多优秀的后台管理系统&#xff0c;但是都感觉还少&#xff0c;今天再来推荐一个。 最近新接触到一个项目&#xff0c;确实把我惊艳到了&#xff0c;太适合使用了&#xff0c;极大地方便了用户和运维人员的日常使用&…

(三)Kubernetes - 手动部署(二进制方式安装)

Kubernetes - 手动部署 [ 2 ] 1 部署主节点1.1 生成kube-apiserver证书1.1.1 自签证书颁发机构(CA)1.1.2 使用自签CA签发kube-apiserver https证书 1.2 下载k8s-server1.3 解压二进制包1.4 部署kube-apiserver1.4.1 创建配置文件1.4.2 拷贝刚才生成的证书1.4.3 启用TLS bootstr…

部署PHP开源项目SuiteCRM

部署PHP开源项目SuiteCRM 前言部署PHP项目创建站点上传PHP源码安装依赖 SuiteCRM安装安装向导中文语言修改数据库密码 前言 因人力资源部想要开发一套适用于他们方便管理的系统&#xff0c;但无整体构思&#xff0c;在网络中找到了开源企业管理软件SuiteCRM&#xff0c;想要作为…

基于SSM框架疫情之下社区管理系统(spring+springmvc+mybatis+jsp+jquery+bootstrap)

一、项目简介 本项目是一套基于SSM框架疫情之下社区管理系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c…