自定义实现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.rhkb.cn/news/299577.html

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

相关文章

GIT版本管理使用示例

一、创建好远程代码仓库后&#xff0c;复制远程仓库的地址 二、新建一个文件夹&#xff0c;在文件夹里打开Git Bash Here 三、输入git clone 远程仓库地址&#xff0c;仓库就初始化完成了 四、新建一个文本文件&#xff0c;假设这是我们开发的代码 五、打开文本&#xff0c;假设…

【深度学习】海洋生物数据集,图片分类

文章目录 任务描述数据收集数据处理模型训练指标评测web app代码和帮助 任务描述 收集9种以上的海洋生物图片&#xff0c;然后基于深度学习做一个分类模型&#xff0c;训练完成后&#xff0c;分类模型就可以对未知图片进行分类。 在之后随便传一张图片&#xff0c;分类模型就…

《乡土中国》中国基层传统社会里的一种体系,支配着社会生活的各方面 - 三余书屋 3ysw.net

乡土中国 大家好&#xff0c;今天我们要解读的是费孝通先生的经典著作《乡土中国》。这本书的中文版大约有10万字&#xff0c;我将用30分钟左右的时间为你解读书中的精髓。为什么说中国的根基在于乡土社会&#xff1f;我们应该从哪些方面来理解乡土社会的特征及其重要性&#…

解压缩软件哪个好用 Mac免费解压软件哪个好 解压软件推荐 beeterzip免费下载

解压缩软件在Mac办公中是必不可少的&#xff0c;不仅能够节省时间和内存&#xff0c;更能提升传输效率。虽然Mac自带的解压缩软件归档实用工具可以对zip文件进行解压&#xff0c;但是对于他格式文件就无能为力了。 因此&#xff0c;想要满足多类型文件解压缩需求&#xff0c;可…

HarmonyOS4.0 ArkTS基本语法

一、项目目录结构 二、HelloWorld 当我们创建一个新的page时&#xff0c;IDE会给我们生成一个HelloWorld的模板 新建page 开启预览器 三、ArkTS基本组成 装饰器&#xff1a; 用于装饰类、结构、方法以及变量&#xff0c;并赋予其特殊的含义。如上述示例中Entry、Component…

centos7部分桥接网络ping不通解决方案

更改网络模式 将部分网络模式改为NAT 查看NAT之后的ip地址 我们可以在虚拟网络配置里面看 或者在我们的cmd里面 输入 ipconfig&#xff0c;进行查看 更改网络配置文件 就是去寻找新的地址&#xff0c;然后&#xff0c;这里注意我们 网关和我们DNS地址一样 网关和我们DNS…

自动驾驶_交通标志识别:各目标检测算法评测

自动驾驶|交通标志识别&#xff1a;各目标检测算法评测 论文题目&#xff1a;Evaluation of Deep Neural Networks for traffic sign detection systems 开源代码&#xff1a;https://github.com/aarcosg/traffic-sign-detection 附赠自动驾驶学习资料和量产经验&#xff1a;…

【C语言自定义类型之----结构体,联合体和枚举】

一.结构体 1.结构体类型的声明 srruct tag {nemer-list;//成员列表 }varible-list;//变量列表结构体在声明的时候&#xff0c;可以不完全声明。 例如&#xff1a;描述一个学生 struct stu {char name[20];//名字int age;//年龄char sex[20];//性别 };//分号不能省略2.结构体…

44.网络游戏逆向分析与漏洞攻防-角色管理功能通信分析-角色创建服务器反馈数据包分析

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果 现在的代码都是依据数据包来写的&#xff0c;如果看不懂代码&#xff0c;就说明没看懂数据包…

瑞_Redis_商户查询缓存_添加Redis缓存缓存更新策略

文章目录 项目介绍1 短信登录2 商户查询缓存2.1 什么是缓存2.1.1 缓存的应用场景2.1.2 为什么要使用缓存2.1.3 Web应用中缓存的作用2.1.4 Web应用中缓存的成本 2.2 添加Redis缓存2.2.1 背景2.2.2 缓存模型和思路2.2.3 代码实现2.2.4 测试附&#xff1a;IDEA控制台输出自动换行设…

C# 分布式自增ID算法snowflake(雪花算法)

文章目录 1. 概述2. 结构3. 代码3.1 IdWorker.cs3.2 IdWorkerTest.cs (测试) 1. 概述 分布式系统中&#xff0c;有一些需要使用全局唯一ID的场景&#xff0c;这种时候为了防止ID冲突可以使用36位的UUID&#xff0c;但是UUID有一些缺点&#xff0c;首先他相对比较长&#xff0c…

【realme x2手机解锁BootLoader(简称BL)】

realme手机解锁常识 https://www.realme.com/cn/support/kw/doc/2031665 realme手机解锁支持型号 https://www.realmebbs.com/post-details/1275426081138028544 realme x2手机解锁实践 参考&#xff1a;https://www.realmebbs.com/post-details/1255473809142591488 1 下载apk…

【研发管理】产品经理知识体系-数字化战略

导读: 数字化战略对于企业的长期发展具有重要意义。实施数字化战略需要企业从多个方面进行数字化转型和优化&#xff0c;以提高效率和创新能力&#xff0c;并实现长期竞争力和增长。 目录 1、定义 2、数字化战略必要性 3、数字战略框架 4、数字化转型对产品和服务设计的影响…

虚幻UE5数字孪生蓝图开发教程

一、背景 这几年&#xff0c;智慧城市/智慧交通/智慧水利等飞速发展&#xff0c;骑士特意为大家做了一个这块的学习路线。 二、这是学习大纲 1.给虚幻UE5初学者准备的智慧城市/数字孪生蓝图开发教程 https://www.bilibili.com/video/BV1894y1u78G 2.UE5数字孪生蓝图开发教学…

【微信小程序】【小程序样式加载不出来】

微信小程序配置sass 第一步&#xff1a;找配置文件 在项目中找到 project.config.json文件&#xff0c;在setting属性中添加 useCompilerPlugins属性&#xff0c;值为sass即可&#xff0c;若是 less,将数组里面的值改为less即可 "useCompilerPlugins": ["sas…

PyTorch之计算模型推理时间

一、参考资料 如何测试模型的推理速度 Pytorch 测试模型的推理速度 二、计算PyTorch模型推理时间 1. 计算CPU推理时间 import torch import torchvision import time import tqdm from torchsummary import summarydef calcCPUTime():model torchvision.models.resnet18()…

基于微信小程序的外卖管理系统的设计与实现(论文+源码)_kaic

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对高校教师成果信息管理混乱&#xff0c;出错率高&#xff0c;信息安全…

【JavaEE】浅谈线程(一)

线程 前言线程的由来线程是什么线程的属性线程更高效的原因举个例子&#xff08;线程便利性的体现&#xff09; 多线程代码线程并发执行的代码jconsole(观测多线程) 线程的调度问题创建线程的几种方法1&#xff09;通过继承Thread 重写run2&#xff09;使用Runnable接口 重写ru…

金陵科技学院软件工程学院软件工程专业

感兴趣的小伙伴可以私信我哦~~ 是笔者写的各种高质量作业和实验哦~~ 感兴趣的小伙伴可以私信我哦~~ 是笔者写的各种高质量作业和实验哦~~ 感兴趣的小伙伴可以私信我哦~~ 是笔者写的各种高质量作业和实验哦~~ 感兴趣的小伙伴可以私信我哦~~ 是笔者写的各种高质量作业和实验哦…

递归实现组合型枚举(acwing)

题目描述&#xff1a; 从 1∼n 这 n 个整数中随机选出 m 个&#xff0c;输出所有可能的选择方案。 输入格式&#xff1a; 两个整数 n,m ,在同一行用空格隔开。 输出格式&#xff1a; 按照从小到大的顺序输出所有方案&#xff0c;每行 1 个。 首先&#xff0c;同一行内的数…