自定义实现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];//改变工作目录为当前的pathchdir(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;
}

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

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

相关文章

Day30 线程安全之窗口售票问题(含代码)

Day30 线程安全之窗口售票问题&#xff08;含代码&#xff09; 一、需求&#xff1a; 铁道部发布了一个售票任务&#xff0c;要求销售1000张票&#xff0c;要求有3个窗口来进行销售&#xff0c; 请编写多线程程序来模拟这个效果&#xff08; 注意&#xff1a;使用线程类的方式…

【Qt 学习笔记】详解Qt中的信号和槽

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 详解Qt中的信号与槽 文章编号&#xff1a;Qt 学习笔记 / 12 文章目录…

红黑树的平衡之道:深入解析右旋操作的原理与实践

红黑树的平衡之道&#xff1a;深入解析右旋操作的原理与实践 一、 红黑树旋转的背景二、右旋&#xff08;RIGHT-ROTATE&#xff09;的原理三、右旋&#xff08;RIGHT-ROTATE&#xff09;的算法步骤四、右旋&#xff08;RIGHT-ROTATE&#xff09;的伪代码五、右旋&#xff08;RI…

ctf_show笔记篇(web入门---jwt)

目录 jwt简介 web345&#xff1a; web346&#xff1a; web347&#xff1a; web348: web349&#xff1a; web350&#xff1a; jwt简介 JSON Web Token&#xff08;JWT&#xff09;通常由三部分组成 Header&#xff08;头部&#xff09;&#xff1a;包含了两部分信息&…

蓝桥杯备考3

P8196 [传智杯 #4 决赛] 三元组 题目描述 给定一个长度为 n 的数列 a&#xff0c;对于一个有序整数三元组 (i,j,k)&#xff0c;若其满足 1≤i≤j≤k≤n 并且&#xff0c;则我们称这个三元组是「传智的」。 现在请你计算&#xff0c;有多少有序整数三元组是传智的。 输入格式…

LRU的原理与实现(java)

介绍 LRU的英文全称为Least Recently Used&#xff0c;即最近最少使用。它是一种内存数据淘汰算法&#xff0c;当添加想要添加数据而内存不足时&#xff0c;它会优先将最近一段时间内使用最少的数据淘汰掉&#xff0c;再将数据添加进来。 原理 LRU的原理在介绍中就已经基本说…

机器学习模型——逻辑回归

https://blog.csdn.net/qq_41682922/article/details/85013008 https://blog.csdn.net/guoziqing506/article/details/81328402 https://www.cnblogs.com/cymx66688/p/11363163.html 参数详解 逻辑回归的引出&#xff1a; 数据线性可分可以使用线性分类器&#xff0c;如果…

蓝桥真题--路径之谜DFS解法

路径之谜 思路 前置知识&#xff1a;深度搜索模板搜索所有可以找的路径&#xff0c;将走过的靶子减去一走到最后一个格子的时候&#xff0c;直接去判断所有的靶子只有除最后一个位置的靶子&#xff0c;其余靶子都归零的时候&#xff0c;判断一个最后一个位置横坐标和纵坐标的靶…

尚硅谷html5+css3(1)

1.基本标签&#xff1a; <h1>最大的标题字号 <h2>二号标题字号 <p>换行 2.根标签<html> 包括<head>和<body> <html><head><title>title</title><body>body</body></head> </html> 3…

MATLAB - 用命令行设计 MPC 控制器

系列文章目录 前言 本例演示如何通过命令行创建和测试模型预测控制器。 一、定义工厂模型 本示例使用《使用 MPC Designer 设计控制器》中描述的工厂模型。创建工厂的状态空间模型&#xff0c;并设置一些可选的模型属性&#xff0c;如输入、状态和输出变量的名称和单位。 % co…

正确使用@Resource

目录 1 怎么使用Resource&#xff1f;1.0 实验环境1.1 通过字段注入依赖1.2 bean property setter methods &#xff08;setter方法&#xff09; 2 打破岁月静好&#xff08;Resource takes a name attribute&#xff09;2.1 结论2.2 那我不指定呢&#xff1f;【结论&#xff1…

Seata(分布式事务集成测试和总结)

文章目录 1.集成测试1.集成测试正常下单1.步骤2.浏览器访问 http://localhost:10008/order/save?userId666&productId1&nums1&money1003.注意事项和细节 2.集成测试模拟异常1.步骤1.com/sun/springcloud/controller/StorageController.java 休眠12s&#xff0c;模…

自动驾驶执行层 - 线控底盘基础原理(非常详细)

自动驾驶执行层 - 线控底盘基础原理(非常详细) 附赠自动驾驶学习资料和量产经验&#xff1a;链接 1. 前言 1.1 线控的对象 在自动驾驶行业所谓的“感知-定位-决策-执行”的过程中&#xff0c;在末端的执行层&#xff0c;车辆需要自主执行决策层所给出的指令&#xff0c;具体…

leetcode(HOT100)——链表篇

1、相交链表 本题思路就是定义两指针&#xff0c;指向两链表的同一起跑线&#xff0c;然后共同往前走&#xff0c;边走边判断两链表的节点是否相等&#xff0c; 代码如下&#xff1a; /*** Definition for singly-linked list.* public class ListNode {* int val;* L…

Android14应用启动流程(源码+Trace)

1.简介 应用启动过程快的都不需要一秒钟&#xff0c;但这整个过程的执行是比较复杂的&#xff0c;无论是对手机厂商、应用开发来说启动速度也是核心用户体验指标之一&#xff0c;本文采用Android14源码与perfetto工具进行解析。 源码参考地址&#xff1a;Search trace分析工…

2024.4.5|牛客小白月赛90

2024.4.5|牛客小白月赛90 A.小A的文化节 B.小A的游戏 C.小A的数字 D.小A的线段&#xff08;easy version&#xff09; E.小A的任务 F.小A的线段&#xff08;hard version&#xff09; 心有猛虎&#xff0c;细嗅蔷薇。你好朋友&#xff0c;这里是锅巴的C\C学习笔记&#xff0c…

[报错解决]源服务器未能找到目标资源的表示或者是不愿公开一个已经存在的资源表示。

目录 报错信息解决办法 spring整合mvc时&#xff0c;遇到的404报错&#xff0c;梳理mvc知识供参考供 报错信息 解决办法 Controller RequestMapping("user") public class UserController {//spring整合webmvc// 请求地址 http://localhost:7070/user/quickRequest…

数据字典

文章目录 一、需求分析二、表设计&#xff08;两张表&#xff09;三、功能实现3.1 数据字典功能3.1.1 列表功能3.1.2 新增数据字典3.1.3 编辑数据字典 3.2 数据字典明细3.2.1 列表功能3.2.2 新增字典明细3.2.3 编辑字典明细 3.3 客户管理功能3.3.1 列表功能3.3.2 新增用户3.3.3…

Golang | Leetcode Golang题解之第11题盛最多水的容器

题目&#xff1a; 题解&#xff1a; func maxArea(height []int) int {res : 0L : 0R : len(height) - 1for L < R {tmp : math.Min(float64(height[L]), float64(height[R]))res int(math.Max(float64(res), tmp * float64((R - L))))if height[L] < height[R] {L} el…

【环境变量】基本概念理解 | 查看环境变量echo | PATH的应用和修改

目录 前言 基本概念&理解 注意的点 查看环境变量方法 PATH环境变量 PTAH应用系统指令 PTAH应用用户程序 命令行的修改&#xff08;内存级&#xff09; 配置文件的修改 windows环境变量 大家天天开心&#x1f642; bash进程的流程。环境变量在系统指令和用户…