【Linux 进程】 自定义shell

目录

关于shell

1.打印提示符&&获取用户命令字符​编辑

2.分割字符串

3.检查是否为内建命令

cd命令

export命令

echo命令

1.输出最后一个执行的命令的状态退出码(返回码)

2.输出指定环境变量

4.执行外部命令


关于shell

Shell 是计算机操作系统的一种命令行解释器,它允许用户与操作系统进行交互,执行各种操作和任务。Shell 接受用户输入的命令,并将其解释成操作系统能够理解的形式,然后将这些命令发送给操作系统内核执行。

Shell 的作用包括但不限于:

  1. 命令执行: 用户可以使用 Shell 来执行各种命令,包括系统命令、应用程序命令、脚本等,以完成各种任务和操作。

  2. 文件操作: 用户可以使用 Shell 进行文件和目录的创建、复制、移动、删除等操作,以及文件内容的查看、编辑等操作。

  3. 系统管理: 用户可以使用 Shell 进行系统资源的管理,包括进程管理、用户管理、权限管理等。

  4. 环境配置: 用户可以使用 Shell 来配置系统环境,包括设置环境变量、执行初始化脚本等。

  5. 脚本编程: 用户可以使用 Shell 编写脚本,实现自动化任务和流程控制,提高工作效率。

总的来说,Shell 提供了一个灵活而强大的界面,使用户能够通过简单的命令和脚本来与操作系统进行交互和控制,从而完成各种任务和操作。

1.打印提示符&&获取用户命令字符

首先,要自定义shell就必须要接收命令,可以看到我们在系统的shell中提示符有三部分:1.用户名 2.主机名 3.所在目录。所以我们自己的shell就必须先把这三个提示符打印出来,然后是接收用户输入的命令字符。而考虑到接收命令是一直持续的,所以我们用死循环来控制。

那如何在程序中获取用户名、主机名以及所在目录呢?这就需要环境变量了。

环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数,是操作系统为了满足不同的应用场景预先在系统内预先设置的一大批全局变量。

在操作系统里,我们可以输入env、printenv、echo等命令来打印出环境变量:

可以看到系统输出了很多我们不认识的东西,但我们只需要找到需要的用户名(USER)、主机名(HOSTNAME)以及当前目录(PWD)。

然后我们就可以编写程序了。

从程序中获取环境变量,我们需要用到一个函数

char *getenv(const char *name);

这个函数将返回我们传给的 name,然后它将环境列表中搜索名为name的环境变量,如果name不是环境列表的一部分,它将返回 NULL,否则返回具有所求环境变量值的C字符串。

 const char *getUsername(){const char *name = getenv("USER");if(name) return name;else return NULL;}const char *getHostname(){const char* hostname = getenv("HOSTNAME");if(hostname) return hostname;else return NULL;}const char *getCwd(){const char* cwd = getenv("PWD");if(cwd) return cwd;else return NULL;}     

我们编写了上面三个函数,分别可以返回用户名、主机名和当前目录路径。

下面我们创建一个函数getUserCommand,使其能够打印出提示符信息并获取用户命令字符,在此之前,我们需要在主函数中创建一个字符数组username以便存放用户命令字符和后续操作。

#define NUM 1024
char usercommand[NUM];

那么如何从进程中接收用户输入的字符呢?有的人可能会想到用scanf,但是我们要直到,scanf碰到空格会自动停止,所以我们需要用到fgets,以便从输入流中截取字符。

fgets函数返回值为我们传过去的s字符指针,如果获取字符失败,则返回NULL,参数中第一个元素是我们存储字符的字符串,第二个是要截取的字符串的长度,第三个是标准流,我们需要的是标准输入流stdin,这样getUserCommand函数的需要的参数也就确定了,一个是存放字符的数组usercommand,另一个是数组的长度sizeof(usercommand)

可这样就结束了吗?我们打印出获取的字符串会发现,总是会多一个换行符,这是因为每次输入的回车也被接收了,只需要将字符数组中的最后一个元素换成'\0'就好,然后可以返回字符数组的长度,以便判断是否成功获取到命令字符。

int getUserCommand(char *command,int num){printf("[%s@%s %s] $",getUsername(),getHostname(),getCwd());char * r = fgets(command,num,stdin);if(r == NULL) return -1;command[strlen(command)-1] = '\0';return strlen(command);}
//如果函数返回 -1 或 0 ,则代表获取命令字符失败

2.分割字符串

在C++里,我们通常可以用substr来进行字符串的分割,但是在这我们用的是C,所以只能用strtok来对字符串进行分割。

str是我们想要分割的字符串,delim是我们想要在哪截断的符号,如果没有找到delim或者扫描的字符串到达末尾空字符时,strtok将返回NULL,否则返回指向以delim开头的字符串(不包括delim)。

需要注意的是,strtok会在内部保留一个静态指针,用于记录当前分割的位置,通过将第一个参数设置为 NULL,来告诉strtok函数接着上次的位置进行分割。

在此之前,我们需要在主函数中创建一个存储字符串指针的数组,以便后续操作,定义SIZE代表这个数组最多能存储多少个命令。

#define SIZE 64
char *argv[SIZE];  

接着我们确定函数的参数,第一个是之前接收的字符数组command,第二个是存储命令的argv数组,这里in代表command,out代表argv。

void commandSplit(char *in,char* out[]){int argc = 0;out[argc++] = strtok(in,SEP);while(out[argc++] = strtok(NULL,SEP));#ifdef DEBUG//用于测试 commandSplit是否生效,如果DEBUG被定义,则运行代码,未定义不运行for(int i = 0;out[i];i++){printf("%d:%s\n",i,out[i]);}#endif}
通过定义DEBUG来测试commandSplit函数能否成功分割字符串。

3.检查是否为内建命令

内建命令是指直接内置在操作系统的命令行解释器(如Bash、CMD)中的命令,而不是外部可执行文件。这些命令不需要从磁盘加载,而是作为解释器的一部分而存在。它们通常提供了一些基本的操作,比如文件系统操作、环境控制等。在linux系统中,常见的内建命令有:cd、echo、pwd、exit、export等。

没有内建命令,我们的shell可以说就是个空格,接下来我们将在我们的shell中中加入内建命令,来让其可以做到一些基本的功能。

int doBuildin(char *argv[]);
//返回值
cd命令

因为正常用户输入的命令都是第一个空格前的字符串代表的是命令,后面的代表命令选项,例如ls -a -l,所以我们需要先判断命令的字符串是否等于"cd",后面的命令也同样如此,如果相等,开始下一步。

因为cd命令后面跟的都是路径,所以我们判断argv中的第二个字符串是否为空,如果为空的话,就让path等于家目录,如果不为空,就让path等于它,然后进行我们的cd命令。

if(strcmp(argv[0],"cd") == 0){char *path = NULL;                                                                                                                                                      if(argv[1] == NULL) path = homepath();else path = argv[1];cd(path);return 1;}

进入自己的cd函数后,我们需要通过一个函数chdir来改变当前的目录地址

然后我们需要记录下已经改变后的目录地址,然后将当前目录地址设置为环境变量

然后我们需要记录下已经改变后的目录地址,然后将当前目录地址设置为环境变量

如此我们就实现了第一个内建命令cd,它可以在改变目录地址的同时,修改环境变量中的PWD变量,使其跟当前目录同步。

export命令

export 是一个命令行命令,用于设置环境变量。在Unix/Linux系统中,环境变量是在操作系统中存储的一组键值对,它们可以影响系统和用户进程的行为。通过使用 export 命令,你可以将一个变量设置为环境变量,使其在当前会话中对所有后续运行的程序可见。

export命令的实现相对简单,只需判断第二个字符是否为空,如果为空的话,直接返回,不为空就设置一个全局变量enval,先将argv[1]拷贝给enval,再用putenv命令将enval中的内容添加到环境变量中。

else if(strcmp(argv[0],"export") == 0){if(argv[1] == NULL) return 1;strcpy(enval,argv[1]);putenv(enval);return 1;}
echo命令

echo 是一个命令行命令,用于在终端输出文本或变量的值。它是一个非常简单但功能强大的命令,常用于脚本编程、调试以及与其他命令的组合使用。

首先要判断第二个字符串的第一个字符是否为$,并且第二个字符串的长度要大于1,如果满足继续向下走,不满足直接输出argv[1]的内容并返回。

向下走:将指针argv[1]+1,使其跳过第一个字符$,然后将剩下的指针赋值给val,然后对val进行判断,如果val的值等于"?",执行下面第一个,否则执行第二个。

1.输出最后一个执行的命令的状态退出码(返回码)
2.输出指定环境变量
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;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;}

但是这个函数不足的点在于,无论用户输入的内建命令成功与否,输入echo $?的结果都是0,除非上一个执行的命令是外部命令。

4.执行外部命令

上面的代码处理了当用户输入的命令是内建命令的情况,那么当用户输入外部命令时应该怎么办呢?

首先,我们创建一个execute函数用来封装处理外部程序,参数为命令字符数组argv,

然后我们可以用exec系列的函数来实现在程序内处理外部命令,那么这几个exec函数应该选哪个呢?

我们选择execvp函数,因为我们可以直接传递命令名称,让execvp去系统里找可执行文件,所以选择'p',而我们的命令名臣存储在了argv这一个数组中,所以选择'v'。

在execute函数中,我们先用fork创建子进程,让子进程去执行命令,然后父进程用waitpid来等待子进程的退出,并获取其退出码,无论子进程是否正常退出,则其状态退出码将存储在status中,然后用WEXITSTATUS来获取子进程的退出状态码。

int execute(char *argv[])
{pid_t id = fork();if(id < 0) return -1;else if(id == 0)  //child{//exec commandexecvp(argv[0],argv);exit(1);}else    //father{int status;pid_t rid = waitpid(id,&status,0);if(rid>0){lastcode = WEXITSTATUS(status);}                 }
}

总代码

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
#define NUM 1024
#define SIZE 64
#define SEP " "
//#define DEBUG 1
//
char cwd[1024];char enval[1024];  // for testint lastcode = 0; char *homepath()
{char *home = getenv("HOME");if(home)  return home;elsereturn (char*)".";
}
const char *getUsername()
{const char *name = getenv("USER");if(name) return name;else return NULL;
}
const char *getHostname()
{const char* hostname = getenv("HOSTNAME");if(hostname) return hostname;else return NULL;
}
const char *getCwd()
{const char* cwd = getenv("PWD");if(cwd) return cwd;else return NULL;
}
int getUserCommand(char *command,int num)
{printf("[%s@%s %s] $",getUsername(),getHostname(),getCwd());char * r = fgets(command,num,stdin);if(r == NULL) return -1;command[strlen(command)-1] = '\0';return strlen(command);
}
void commandSplit(char *in,char* out[])
{int argc = 0;out[argc++] = strtok(in,SEP);while(out[argc++] = strtok(NULL,SEP));
#ifdef DEBUG//用于测试 commandSplit是否生效,如果DEBUG被定义,则运行代码,未定义不运行for(int i = 0;out[i];i++){printf("%d:%s\n",i,out[i]);}
#endif
}int execute(char *argv[])
{pid_t id = fork();if(id < 0) return -1;else if(id == 0)  //child{//exec commandexecvp(argv[0],argv);exit(1);}else    //father{int status;pid_t rid = waitpid(id,&status,0);if(rid>0){lastcode = WEXITSTATUS(status);}                 }
}
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->error
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;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.分割字符串commandSplit(usercommand,argv);//3.check build-in commandn = doBuildin(argv);if(n) continue;//4.执行对应的命令execute(argv);}
}

这样我们就完成了一个简单的自定义shell程序了,还有很多内容可以进行开发。

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

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

相关文章

免费开源,无需 GPU,本地化部署大语言模型的对话系统

免费开源&#xff0c;无需 GPU&#xff0c;本地化部署大语言模型的对话系统 分类 编程技术 项目名: FreeAskInternet -- 本地化部署大语言模型的对话系统 Github 开源地址&#xff1a; https://github.com/nashsu/FreeAskInternet FreeAskInternet 是一个免费开源的工具&am…

Kotlin: Expecting a ‘>‘

数组值为任意类型&#xff0c;声明报错: Kotlin: Expecting a > var anyArr1: Array<Any?> arrayOf("a", "b", "c", true, 34)原因是&#xff1a; // var anyArr1: Array<Any?> arrayOf("a", "b", "c…

发电厂智能巡检机器人:让发电厂更安全、更高效

在发电厂的众多应用场景中&#xff0c;升压站、化学车间、空冷塔、输煤皮带、综合管廊等&#xff0c;一直以来都是人工巡检的主战场。然而&#xff0c;这些场所环境极为复杂&#xff0c;人工巡检面临着诸多难题&#xff0c;强度大、频率低、间隔长等问题突出。这使得设备在运行…

c++多线程基础

简介 c多线程基础需要掌握这三个标准库&#xff1a;std::thread, std::mutex, and std::async。 1. Hello, world #include <iostream> #include <thread>void hello() { std::cout << "Hello Concurrent World!\n"; }int main() {std::thread…

[NSSCTF]prize_p2

题目 打开是一段js代码 // 导入所需的模块 const { randomBytes } require(crypto); // 导入 crypto 模块&#xff0c;用于生成随机字节 const express require(express); // 导入 Express.js 模块&#xff0c;用于构建 Web 应用程序 const fs require(fs); // 导入文件系…

论文笔记:(Security 22) 关于“二进制函数相似性检测”的调研

个人博客链接 注&#xff1a;部分内容参考自GPT生成的内容 [Security 22] 关于”二进制函数相似性检测“的调研&#xff08;个人阅读笔记&#xff09; 论文&#xff1a;《How Machine Learning Is Solving the Binary Function Similarity Problem》&#xff08;Usenix Securi…

Golang中实现调用Windows API向指定目标发送ARP请求

简介 Go库中很多实现的arp都是支持osx/linux/bsd之类的&#xff0c; 但几乎没有支持windows的&#xff0c; 也试了一些方式&#xff0c; 目前还是选用调用windows的API&#xff0c; 记录一下这一次windows的API的调用经验。 实现 代码 package main/* #cgo CFLAGS: -I. #cgo …

Reactor模型详解

目录 1.概述 2.Single Reactor 3.muduo库的Multiple Reactors模型如下 1.概述 维基百科对Reactor模型的解释 The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs.…

OpenCV如何模板匹配(59)

返回:OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;OpenCV如何实现背投(58) 下一篇 &#xff1a;OpenCV在图像中寻找轮廓(60) 目标 在本教程中&#xff0c;您将学习如何&#xff1a; 使用 OpenCV 函数 matchTemplate()搜索图像贴片和输入…

第四篇:记忆的迷宫:探索计算机存储结构的奥秘与创新

记忆的迷宫&#xff1a;探索计算机存储结构的奥秘与创新 1 引言 1.1 计算机存储系统的发展与重要性 在现代计算技术中&#xff0c;存储系统承担着非常关键的角色&#xff0c;它不仅负责信息的持久保存&#xff0c;同时确保高效的数据访问速度&#xff0c;影响着整体系统性能的…

《Fundamentals of Power Electronics》——基础交流建模方法

PWM整流器小信号交流模型建模的主要步骤为&#xff1a; (a)利用小纹波近似的动态版本&#xff0c;建立与电感和电容波形的低频平均值有关的方程&#xff1b; (b)平均方程的扰动和线性化&#xff1b; (c)交流等效电路模型的建立。 以下图buck-boost电路为例进行分析。 首先测…

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《考虑碳捕集和电转气的综合能源系统优化调度》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

SpringBoot与SpringMVC的区别

SpringBoot与SpringMVC的区别是什么&#xff1f; SpringBoot和SpringMVC是Java开发中常用的两个框架&#xff0c;它们都是由Spring框架所提供的&#xff0c;但在功能和使用方式上有着一些区别。本文将分别介绍SpringBoot和SpringMVC的特点和区别。 一、SpringBoot的特点&#…

Qt服务器端与客户端交互

Qt做客户端与服务器端交互第一步引入network 第一步引入network后继续编程首先界面设计 创建server和socket 引入QTcpServer&#xff0c;QTcpSocket MainWindow.h代码如下 #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include <QTcpServer&…

13_Qt中的快捷键

Qt Creator的一些快捷操作&#xff1a; 项目管理&#xff1a; Build&#xff1a;以增量方式构建项目。Rebuild&#xff1a;重新构建项目。Clearn&#xff1a;清除项目构建过程中产生的所有中间文件。Run qmake&#xff1a;使用qmake/cmake重新构建项目。会重新执行UIC、MOC、…

如何面对并发下的bug

整理总结自蒋炎岩老师的b站课程&#xff0c;https://jyywiki.cn/OS/2022/index.html 并发bug与应对 应对bug的方法 在代码里边增加很多检查(加断言) #include "thread.h"unsigned long balance 100;void Alipay_withdraw(int amt) {if (balance > amt) {usleep(…

迎接AI时代:智能科技的社会责任与未来展望

AI智能体的社会角色、伦理挑战与可持续发展路径 引言&#xff1a; 在技术的浪潮中&#xff0c;AI智能体正逐步成为我们生活的一部分。它们在医疗、教育、交通等领域的应用&#xff0c;预示着一个全新的时代即将到来。本文将结合实际案例和数据分析&#xff0c;深入探讨AI智能体…

农作物害虫分类数据集12846张27类别

数据集类型&#xff1a;图像分类用&#xff0c;不可用于目标检测无标注文件 数据集格式&#xff1a;仅仅包含jpg图片&#xff0c;每个类别文件夹下面存放着对应图片 图片数量(jpg文件个数)&#xff1a;12846 分类类别数&#xff1a;27 类别名称:["ants","aphids…

Cisco WLC 2504控制器重启后所有AP掉线故障-系统日期时间

1 故障描述 现场1台WLC 2504控制器掉电重启后&#xff0c;所有AP均无线上线&#xff0c; 正常时共有18个AP在线&#xff0c;而当前为0 AP在线数量为0 (Cisco Controller) >show ap sumNumber of APs.................................... 0Global AP User Name..........…

国内各种免费AI聊天机器人(ChatGPT)推荐(中)

作者主页&#xff1a;点击&#xff01; 国内免费AI推荐(ChatGPT)专栏&#xff1a;点击&#xff01; 创作时间&#xff1a;2024年4月29日15点20分 随着人工智能技术的不断发展&#xff0c;AI聊天机器人已经逐渐融入我们的日常生活。它们可以提供各种服务&#xff0c;例如聊天、…