自定义实现shell/bash

文章目录

  • 函数和进程之间的相似性
  • shell
    • 打印提示符,以及获取用户输入
    • 分割用户的输入
    • 判断是否是内建命令
    • 执行相关的命令
  • 全部代码

正文开始前给大家推荐个网站,前些天发现了一个巨牛的 人工智能学习网站, 通俗易懂,风趣幽默,忍不住分享一下给大家。[点击跳转到网站]

函数和进程之间的相似性

exec/exit就像call/return一个C程序有很多函数组成。一个函数可以调用另外一个函数,同时传递给它一些参数。被调用的函数执行一定的操作,然后返回一个值。每个函数都有他的局部变量,不同的函数通过call/return系统进行通信。这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux鼓励将这种应用于程序之内的模式扩展到程序之间
在这里插入图片描述
一个C程序可以fork/exec另一个程序,并传给它一些参数。这个被调用的程序执行一定的操作,然后通过exit(n)来返回值。调用它的进程可以通过wait(&ret)来获取exit的返回值。

shell

有了程序替换我们可以让一个进程来调用其他进程,所以我们想如果我们在执行程序替换的时候创建一个子进程让子进程去执行,然后我们主进程(父进程)来和用户交互,我们是不是就可以实现一个简易的shell命令行解释器。

打印提示符,以及获取用户输入

在这里插入图片描述
我们可以看到每次在运行的时候,shell都会先打印前面的一堆东西,并且光标会停留在后面等待用户输入,所以我们父进程可以先把打印以及输入这个工作搞定。
那么这一堆东西如何获取打印呢?我们可以直接写死,但是换个主机可能就不满足要求了,我们可以分析一下,前面的这一堆东西是不是可以通过环境变量来获取呢?我们可以查一下环境变量
在这里插入图片描述

在这里插入图片描述
我们可以发现前面是用户@主机名 然后加当前路径,所以我们可以通过环境变量来动态获取这些内容。
我们可以先来三个函数,一个用来获取用户名,一个用来获取当前路径,一个用来获取主机名。

char *getUsername()
{
    char *name = getenv("USER");
    if (name)
        return name;
    return "none";
}

char *getHostname()
{
    char *name = getenv("HOSTNAME");
    if (name)
        return name;
    return "none";
}

char *getCwd()
{
    char *cwd = getenv("PWD");
    if (cwd)
        return cwd;
    return "none";
}

然后我们可以定义一个缓冲区,用来保存用户的输入,然后把打印起那么的一堆东西和输入放到一个函数里,进行一下封装。

int getComment(char comment[], int num)
{
    printf("[%s@%s %s]# ", getUsername(), getHostname(), getCwd());

    char *ch = fgets(comment, num, stdin);
    if (ch == NULL)
        return -1;
    int n = strlen(comment);
    comment[n - 1] = '\0';
    return n - 1;
}

这里面的细节还是挺多的,不管怎么说用户一定会输入一个回车,所以我们不用担心 comment[n - 1] = ‘\0’ 会越界访问,对于返回值问题,如果用户输入失败或者没有输入了内容返回值就会是一个 <= 0的数,就没必要进行后面创建子进程等的工作,直接跳过此次循环就OK。所以我们只要传进来一个缓冲区,就可以了。这个函数就会把用户输入的内容放到缓冲区中去。

分割用户的输入

用户输入进来的诸如"ls -a -l"等等,都是一整个字符串,但是我们进程程序替换是需要将这些个子串以空格分割进行分割成若干个子串,所以我们可以把这部分在进行封装成一个函数,我们把用户输入的内容传给这个函数,让这个函数把用户输入的内容分割成若干个子串,然后传出去就可以了,分割子串我们可以用到C语言的strtok函数,还是比较简单的。

#define SEP " "
void commentSplit(char *in, char *out[])
{
    int pos = 0;
    out[pos++] = strtok(in, SEP);
    while (out[pos++] = strtok(NULL, SEP));
}

判断是否是内建命令

我们学到的命令由两部分,有一部分是普通命令也就是shell创建子进程让子进程去执行的,还有一部分就是内建命令,如(cd,export,echo等)这些命令是shell的一个函数,是需要shell自己去执行的。所以我们在创建子进程之前,需要判断一下是否是内建命令,如果是的话,就没必要创建子进程了,所以我们可以设计一个返回值来进行区分是否是内建命令。

char cwd[NUM];
char add_env[NUM][NUM];
int envi = 0;
static int lastcode = 0;

char* getHomePath()
{
    char* path = getenv("HOME");
    if(path) return path;
    return "none";
}
void cd(const char* path)
{
    char tmp[NUM];
    //改变工作目录为当前的path
    chdir(path);
    //获取当前的工作目录
    getcwd(tmp,sizeof tmp);
    sprintf(cwd,"PWD=%s",tmp);
    putenv(cwd);
}
int dobuildcom(char* argv[])
{
    if(strcmp(argv[0],"cd") == 0)
    {
        char* path;
        if(argv[1] == NULL) path = getHomePath();
        else if(strcmp(argv[1], "~") == 0) 
        {
            path = getHomePath();
        }else path = argv[1];

        cd(path);
        return 1;
    }
    else if(strcmp(argv[0],"export") == 0)
    {
        if(argv[1] == NULL) return 1;
        strcpy(add_env[envi],argv[1]);
        putenv(add_env[envi]);
        envi++;
        return 1;
    }
    else if(strcmp(argv[0],"echo") == 0)
    {
        if(argv[0] == NULL) 
        {
            printf("\n");
            return 1;
        }
        char* tmp = argv[1];
        if(tmp[0] == '$')
        {
            tmp++;
            if(strcmp(tmp,"?") == 0) 
            {
                printf("%d\n",lastcode);
            }else
            {
                char* c = getenv(tmp);
                if(c) printf("%s\n",c);
                else printf("\n");

            }
        }
        else{
            printf("%s\n",tmp);
        }
        lastcode = 0;
        return 1;
    }

    return 0;
}

我们这里就实现了3个内建命令,这里面最值的一说的就是环境变量了,所以我们在cd或者使用export添加环境时,是不能使用临时变量的,因为环境变量拥有全局属性,如果使用局部变量的话当函数执行完之后,这个空间就会还给OS,所以我们在导环境环境变量的时候需要定义全局变量,然后把需要导的环境变量拷贝到全局变量中在进行添加环境变量。返回值是1就是内建命令,否则就不是。

执行相关的命令

我们知道了程序替换这个就比较简单了,我们只需要将分割好的子串给这个函数,然后我们有了子串数组我们那选择好程序替换函数就可以了,因为我们直接就有子串的数组,所以我们可以选择execvp这个函数。

void execute(char *argv[])
{
    pid_t id = fork();
    if (id == 0)
    {
        execvp(argv[0], argv);
        exit(0);
    }
    int status = 0;
    waitpid(id, &status, 0);
    lastcode = WEXITSTATUS(status);
}

我们在执行完一个命令后,我们有一个全局变量来记录最后一条命令执行的结果,所以我们可以获取一下退出码赋值给这个变量。

全部代码

到这里我们的自定义shell的整体逻辑差不多就结束了,最后奉上全部代码。

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

#define NUM 1024
#define SEP " "

extern int putenv(char* );

char cwd[NUM];
char add_env[NUM][NUM];
int envi = 0;
static int lastcode = 0;

char *getUsername()
{
    char *name = getenv("USER");
    if (name)
        return name;
    return "none";
}

char *getHostname()
{
    char *name = getenv("HOSTNAME");
    if (name)
        return name;
    return "none";
}

char *getCwd()
{
    char *cwd = getenv("PWD");
    if (cwd)
        return cwd;
    return "none";
}

char* getHomePath()
{
    char* path = getenv("HOME");
    if(path) return path;
    return "none";
}

int getComment(char comment[], int num)
{
    printf("[%s@%s %s]# ", getUsername(), getHostname(), getCwd());

    char *ch = fgets(comment, num, stdin);
    if (ch == NULL)
        return -1;
    int n = strlen(comment);
    comment[n - 1] = '\0';
    return n - 1;
}

void commentSplit(char *in, char *out[])
{
    int pos = 0;
    out[pos++] = strtok(in, SEP);
    while (out[pos++] = strtok(NULL, SEP));
}

void execute(char *argv[])
{
    pid_t id = fork();
    if (id == 0)
    {
        execvp(argv[0], argv);
        exit(0);
    }
    int status = 0;
    waitpid(id, &status, 0);
    lastcode = WEXITSTATUS(status);
}

void cd(const char* path)
{
    char tmp[NUM];
    chdir(path);
    getcwd(tmp,sizeof tmp);
    sprintf(cwd,"PWD=%s",tmp);
    putenv(cwd);
}
int dobuildcom(char* argv[])
{
    if(strcmp(argv[0],"cd") == 0)
    {
        char* path;
        if(argv[1] == NULL) path = getHomePath();
        else if(strcmp(argv[1], "~") == 0) 
        {
            path = getHomePath();
        }else path = argv[1];

        cd(path);
        return 1;
    }
    else if(strcmp(argv[0],"export") == 0)
    {
        if(argv[1] == NULL) return 1;
        strcpy(add_env[envi],argv[1]);
        putenv(add_env[envi]);
        envi++;
        return 1;
    }
    else if(strcmp(argv[0],"echo") == 0)
    {
        if(argv[0] == NULL) 
        {
            printf("\n");
            return 1;
        }
        char* tmp = argv[1];
        if(tmp[0] == '$')
        {
            tmp++;
            if(strcmp(tmp,"?") == 0) 
            {
                printf("%d\n",lastcode);
            }else
            {
                char* c = getenv(tmp);
                if(c) printf("%s\n",c);
                else printf("\n");

            }
        }
        else{
            printf("%s\n",tmp);
        }
        lastcode = 0;
        return 1;
    }

    return 0;
}
int main()
{
    while (1)
    {
        char usercomment[NUM];
        char *argv[64] = {NULL};
        int n = getComment(usercomment, sizeof usercomment);
        if(n <= 0) continue;
        // 分割字符串
        commentSplit(usercomment, argv);
        //检查并内建命令
        n = dobuildcom(argv);
        if(n) continue;
        // 执行命令
        execute(argv);
    }
    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

人工智能研究生前置知识—jupyter notebook快速上手使用

jupyter notebook快速上手使用 前置说明 使用的前置要求安装了anaconda的环境 特点&#xff1a;以代码块和单元块为基础&#xff0c;可以嵌入Markdown格式的说明文字 通知可以嵌入魔法函数&#xff0c;并导出为指定的格式 格式.ipynb&#xff09;&#xff08;不仅仅可以运行py…

支持多元AI场景应用,宁畅“NEX AI Lab”开放试用预约中

3月29日&#xff0c;宁畅在京举行发布会&#xff0c;正式发布“全局智算”战略&#xff0c;并在会上推出战略性新品“AI算力栈”&#xff0c;旨在有效解决大模型产业落地的全周期问题。 据宁畅CTO赵雷介绍&#xff0c;“AI算力栈”集成了宁畅在AI计算领域的软硬件能力&#xff…

FJSP:蜣螂优化算法( Dung beetle optimizer, DBO)求解柔性作业车间调度问题(FJSP),提供MATLAB代码

一、柔性作业车间调度问题 柔性作业车间调度问题&#xff08;Flexible Job Shop Scheduling Problem&#xff0c;FJSP&#xff09;&#xff0c;是一种经典的组合优化问题。在FJSP问题中&#xff0c;有多个作业需要在多个机器上进行加工&#xff0c;每个作业由一系列工序组成&a…

实现点击用户头像或者id与其用户进行聊天(vue+springboot+WebSocket)

用户点击id直接与另一位用户聊天 前端如此&#xff1a; <template><!-- 消息盒子 --><div class"content-box" :style"contentWidth"><!-- 头像&#xff0c;用户名 --><div class"content-box-top box--flex">&l…

大数据毕业设计Python+Spark知识图谱高考志愿推荐系统 高考数据分析 高考可视化 高考大数据 计算机毕业设计 机器学习 深度学习 人工智能

附件3 文山学院本科生毕业论文&#xff08;设计&#xff09;开题报告 姓名 性别 学号 学院 专业 年级 论文题目 基于协同过滤算法的高考志愿推荐系统的设计与实现 □教师推荐题目 □自拟题目 题目来源 题目类别 指导教师 选题的目的、意义(理论…

C++从入门到精通——初步认识面向对象及类的引入

初步认识面向对象及类的引入 前言一、面向过程和面向对象初步认识C语言C 二、类的引入C的类名代表什么示例 C与C语言的struct的比较成员函数访问权限继承默认构造函数默认成员初始化结构体大小 总结 前言 面向过程注重任务的流程和控制&#xff0c;适合简单任务和流程固定的场…

spring boot学习第十六篇:配置多数据源

1、代码参考&#xff1a; dynamic-ds/spring-boot-dynamic-ds at main veminhe/dynamic-ds GitHub 2、验证 2.1调用POST接口http://localhost:8081/hmblogs/blog/addBlog 2.2改动数据源为BJ 然后调用接口添加数据 然后查看ds0库的博客数据

Open CASCADE学习|放样建模

在CAD软件中&#xff0c;Loft&#xff08;放样&#xff09;功能则是用于创建三维实体或曲面的重要工具。通过选取两个或多个横截面&#xff0c;并沿这些横截面进行放样&#xff0c;可以生成复杂的三维模型。在CAD放样功能的操作中&#xff0c;用户可以选择不同的选项来定制放样…

Redis实战篇-集群环境下的并发问题

实战篇Redis 3.7 集群环境下的并发问题 通过加锁可以解决在单机情况下的一人一单安全问题&#xff0c;但是在集群模式下就不行了。 1、我们将服务启动两份&#xff0c;端口分别为8081和8082&#xff1a; 2、然后修改nginx的conf目录下的nginx.conf文件&#xff0c;配置反向代…

matrix-breakout-2-morpheus 靶机渗透

信息收集&#xff1a; 1.nmap存活探测&#xff1a; nmap -sn -r 192.168.10.1/24 Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-04-06 12:13 CST Nmap scan report for 192.168.10.1 Host is up (0.00056s latency). MAC Address: 00:50:56:C0:00:08 (VMware) Nmap…

深入go泛型特性之comparable「附案例」

写作背景 如果你经常遇到一些操作&#xff0c;比如将 map 转换为 slice&#xff0c;判断一个字符串是否出现在 map 中&#xff0c;slice 中是否有重复元素等等&#xff0c;那你对下面这个库肯定不陌生。 github.com/samber/lo最近抽业余时间在看了源码&#xff0c;底层用了范…

c语言实现2048小游戏

#include <stdio.h> #include <stdlib.h> #include <time.h> #include <conio.h>int best 0 ;// 定义2048游戏的结构体 typedef struct { int martix[16]; // 当前4*4矩阵的数字 int martixPrior[16]; // 上一步的4*4矩阵的数字 int emptyIndex[16…

4.6(信息差)

&#x1f30d; 山西500千伏及以上输电线路工程首次采用无人机AI自主验收 &#x1f30b; 中国与泰国将开展国际月球科研站等航天合作 ✨ 网页版微软 PowerPoint 新特性&#xff1a;可直接修剪视频 &#x1f34e; 特斯拉开始在德国超级工厂生产出口到印度的右舵车 1.马斯克&…

Qt 使用QPropertyAnimation动画效果的图片浏览器

文章目录 效果图功能点代码解析图片切换显示与动画效果图片缩放 总结 效果图 功能点 加载指定路径下的所有图片并显示滑动滑动条查看指定图片&#xff0c;也滚轮切换图片滑动条缩略图加入动画效果图片可以进行缩放移动查看 代码解析 整体来说相对&#xff0c;显示图片的是一…

4.1 JavaScript的使用

JavaScript有两种使用方式&#xff1a;一是在HTML文档中直接添加代码&#xff1b;二是将JavaScript脚本代码写到外部的JavaScript文件中&#xff0c;再在HTML文档中引用该文件的路径地址。 这两种使用方式的效果完全相同&#xff0c;可以根据使用率和代码量选择相应的开发方式。…

【Qt】:常用控件(一:概述和QWidget核心属性)

常用控件 一.概述二.QWidget核心属性1.enabled&#xff08;是否可用&#xff09;2.geometry&#xff08;设置坐标&#xff09;3.WindTitle&#xff08;窗口标题&#xff09;4.windowIcon1.绝对路径2.qrc机制 5.windowOpacity&#xff08;透明度&#xff09; 一.概述 Widget是Q…

Spring源码解析-容器基本实现

spring源码解析 整体架构 defaultListableBeanFactory xmlBeanDefinitionReader 创建XmlBeanFactory 对资源文件进行加载–Resource 利用LoadBeandefinitions(resource)方法加载配置中的bean loadBeandefinitions加载步骤 doLoadBeanDefinition xml配置模式 validationMode 获…

SpringCloud学习(9)-GateWay网关-自定义拦截器

GateWay Filter详细配置说明 gateway Filter官网:Spring Cloud Gateway 作用&#xff1a; 请求鉴权异常处理记录接口调用时长统计 过滤器类别 全局默认过滤器&#xff1a;官网&#xff1a;Spring Cloud Gateway&#xff0c;出厂默认已有的&#xff0c;直接用&#xff0c;作…

【详细讲解0基础如何进入IT行业】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

Deep Image Prior

自监督的开创性工作 从简单分布到复杂分布的映射&#xff0c;本质上是将重建限制到某一流形&#xff0c;在流形上通过观测图像的数据保真项作为监督。 称之为先验也是很准确&#xff0c;流形就是先验。 这个扰动也很关键&#xff0c;本质上一个平滑正则项。直观理解是各种扰动…