linux 用C语言编写自己的myshell

devtools/2024/11/14 23:23:17/

学习完基本指令、开发环境、进程的概念和创建进程等内容,我们就可以写自己的shell了


文章目录

目录

文章目录

前言

一、myshell构思

二、前缀字符串的打印

三、获取命令行和分割命令

分割命令

四、调用指令 

五、内建指令(特殊指令)

六、完整代码

总结


前言

基本指令

开发环境

进程的概念

进程的状态

环境变量

进度的创建

我们用的shell和指令都是一些进程所构建起来的就,当我们学习完这些东西以后,我们也可以写一个自己的shell了。


一、myshell构思

当我们打开xshell连接当终端以后我们会看到如图内容

1、左右方括号,一个是用户名,@标识符,远端的机器,当前工作目录还有$符,这么一个字符串,那么我们就可用c语言和一些指令来获取这些字符串然后打印出来。 

2、光标卡在那里等待我们输入指令。那么我们可以定义一个字符串用来存储我们要输入的指令。

3、拿到指令应给怎么做?

4、遇到特殊指令应给怎么特殊处理。

二、前缀字符串的打印

所用的宏和全局变量,头文件

#include<iostream>
#include<unistd.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
#include<iostream>
#include<sys/wait.h>
using namespace std;#define LINE_SIZE 1024
#define LEFT "["
#define RIGHT "]"
#define RED "$"
#define ARGV_SIZE 32
#define BLEAK " \t"int lastcode=0;
int argc=0;
char* argv[ARGV_SIZE];
char s[LINE_SIZE];
char buf[LINE_SIZE];
char commandline[LINE_SIZE];

我们通过指令env查看到的环境变量中有这些所要信息。

 

这些都是我们需要获取到的信息,然后打印

我们学习过getenv这个库函数,我们man 3 getenv查看参数和返回值。

 可以看到这是用来获取环境变量的函数,其中参数为需要获取的变量的字符串名;返回值为char*

也就是说我们返回的也是一个字符串,这个字符串也就是环境变量

实例:

获取终端:

char * gethostname()
{return getenv("HOSTNAME");
}

获取用户名: 

char* getusername()
{return getenv("LOGNAME");//return getcwd(buf,sizeof(buf));
}

现在已经获取到了两个我们需要打印的字符串了,还有一个当前工作路径,我们也可以这样来获取,但是当我们后面工作路径改变了,环境变量表不会改变,我们需要修改环境变量表,所以我们想先把pwd给保存起来,所以我们用getcwd,这个也是用来获取当前路径的

 

 这个函数的作用是将路径保存到buf这个数组当中。

实例

void getpwd()
{getcwd(buf,sizeof(buf));}

现在我们需要打印的前缀字符串基本想要的东西就都出来了,接下来就是打印出来。

 而我们的pwd不需要那么长,只需要当前的文件夹名,所以我们截取一下

char* Strsuffix(char sr[])
{//取buf的最后size_t len=strlen(buf)-1;while(len){if(buf[len-1]!='/')len--;else break;}  //char s[LINE_SIZE];//char* s[LINE_SIZE];strncpy(sr,&buf[len],len);return sr;
}
#define LINE_SIZE 1024
#define LEFT "["
#define RIGHT "]"void interact(char line[],int size)
{getpwd();printf(LEFT"%s@%s %s"RIGHT RED" ",getusername(),gethostname(),Strsuffix(s));//cout<<LEFT<<*getusername()<<"@"<<*gethostname()<<" "<<buf<<RIGHT;//获取输入指令char* ss = fgets(line,size,stdin);//获取指令assert(ss);(void)ss; line[strlen(line)-1]='\0';//加入终止符//cout<<line;
}

 这就是前缀的打印

编译运行一下

一模一样 

三、获取命令行和分割命令

当我们打印完前缀时就要立马让光标停下来,也就是在进程的状态中所提到的进程阻塞。

我们需要一个fgets这个在c语言时所用的函数。以上面的前缀打印中已经写入了。

分割命令

在我们学习环境变量的时候,我们当时就提了argv,argc 和env这三个main函数中的参数。

argv是用来记录我们记录我们指令的个数,argc用来保存指令名,env为父进程继承下来的环境变量。

回归myshell,既然我们获取到了命令的,它以字符串的形式保存在

char commandline[LINE_SIZE]

这个字符串当用,那么我现在就可以在这个字符串当中把指令名一个一个的分割出来放在我们自己定义的argv当中,然后更新一下argv(个数)

这就是一个简单的分割,我们可以用双指针或者滑动窗口来解决,我们用string这个库函数中自带的strtok这个函数

实例:

 

int spliststring(char* line,char *_argv[])
{//获取命令int i=0;argv[i++]=strtok(line,BLEAK);while(_argv[i++]=strtok(NULL,BLEAK));return i-1;
}测试
for(int i = 0; argv[i]; i++) printf("[%d]: %s\n", i, argv[i]);

测试结果

 

四、调用指令 

我们现在已经获取当了指令,并且指令的地址已经保存在了argv这个指针数组当用。

我们学习过exec-什么的函数,查看库函数和参数

我们发现用execvp这个函数比较方便,只需要argv这个数组的第一个元素,和后面的元素 

那么我们就创建一个子进程来调用,如果用父进程来调用,等指令进程跑起来就结束了,因为代码被替换了。

创建进程和调用指令代码:

实例

void NormalExcute()
{pid_t id=fork();if(id<0){perror("fork");return ;}else if(id==0){//子进程调用命令execvp(argv[0],argv);exit(44);}else {int status=0;pid_t ret=waitpid(-1,&status,0);if(ret==id){lastcode=WEXITSTATUS(status);if(lastcode<0){perror("waitpid");return;}else if(lastcode>0){cout<<"指令错误!!"<<endl;}else{}}}
}

 运行结果:

五、内建指令(特殊指令)

当我们cd ..到上一级目录当中时,我们会发现没有反应也没有报错,这是因为我们的环境变量表中的PWD没有改变,因为这个环境变量表是从父进程那里写时拷贝过来的,所以没有更新,我们需要将它给跟新一下。只需要将这个指令给特殊处理一下就可以了。

chdir是当指定的工作目录当中去。

int buildCommand(char* _argv[],int _argc)
{if(_argc==2&&strcmp((_argv[0]),"cd")==0){chdir(_argv[1]);getpwd();sprintf(getenv("PWD"),"%s",buf);return 1;}
}

还有echo当我们输入$?时,如果不特殊处理就会直接打印$?,我们xshell却是将子进程的结束码打印出来,我们的而我们的结束码在父进程waitpid等待到子进程时给写入status里面了,只需要调用WEXITSTATUS(status)这个宏就可以记录下来返回给lastcode;这个在上面的创建子进程时父进程当中有所写到,lastcode是定义的一个全局变量。

代码实例:

int buildCommand(char* _argv[],int _argc)
{if(_argc==2&&strcmp((_argv[0]),"cd")==0){chdir(_argv[1]);getpwd();sprintf(getenv("PWD"),"%s",buf);return 1;}else if(_argc&&strcmp(_argv[0],"echo")==0){if(strcmp(_argv[1],"$?")==0){printf("%d\n",lastcode);lastcode=0;}else if(*_argv[1]=='$'){char* val=getenv(_argv[1]+1);if(val)printf("%s\n",val);}else{printf("%s\n",_argv[1]);}return 1;}else if(_argc==2&&strcmp(_argv[0],"exprot")==0){strcpy(myenv,_argv[1]);putenv(myenv);return 1;}return 0;
}

 因为exprot,添加环境变量时,env表不会改变,使用为我们写时拷贝的父进程env表,所以不会改变,我们只需要自己闯将一个就可以了。

六、完整代码

#include<iostream>
#include<unistd.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
#include<iostream>
#include<sys/wait.h>
using namespace std;#define LINE_SIZE 1024
#define LEFT "["
#define RIGHT "]"
#define RED "$"
#define ARGV_SIZE 32
#define BLEAK " \t"int lastcode=0;
int argc=0;
char* argv[ARGV_SIZE];
char s[LINE_SIZE];
char buf[LINE_SIZE];
char commandline[LINE_SIZE];
//string commandline;
//string buf;char myenv[LINE_SIZE];char* getusername()
{return getenv("LOGNAME");//return getcwd(buf,sizeof(buf));
}
void getpwd()
{getcwd(buf,sizeof(buf));}
char* Strsuffix(char sr[])
{//取buf的最后size_t len=strlen(buf)-1;while(len){if(buf[len-1]!='/')len--;else break;}  //char s[LINE_SIZE];//char* s[LINE_SIZE];strncpy(sr,&buf[len],len);return sr;
}
char * gethostname()
{return getenv("HOSTNAME");
}
void interact(char line[],int size)
{getpwd();printf(LEFT"%s@%s %s"RIGHT RED" ",getusername(),gethostname(),Strsuffix(s));//cout<<LEFT<<*getusername()<<"@"<<*gethostname()<<" "<<buf<<RIGHT;//获取输入指令char* ss = fgets(line,size,stdin);assert(ss);(void)ss; line[strlen(line)-1]='\0';//cout<<line;
}int spliststring(char* line,char *_argv[])
{//获取命令int i=0;argv[i++]=strtok(line,BLEAK);while(_argv[i++]=strtok(NULL,BLEAK));return i-1;
}
void NormalExcute()
{pid_t id=fork();if(id<0){perror("fork");return ;}else if(id==0){//子进程调用命令execvp(argv[0],argv);exit(44);}else {int status=0;pid_t ret=waitpid(-1,&status,0);if(ret==id){lastcode=WEXITSTATUS(status);if(lastcode<0){perror("waitpid");return;}else if(lastcode>0){cout<<"指令错误!!"<<endl;}else{}}}
}
int buildCommand(char* _argv[],int _argc)
{if(_argc==2&&strcmp((_argv[0]),"cd")==0){chdir(_argv[1]);getpwd();sprintf(getenv("PWD"),"%s",buf);return 1;}else if(_argc&&strcmp(_argv[0],"echo")==0){if(strcmp(_argv[1],"$?")==0){printf("%d\n",lastcode);lastcode=0;}else if(*_argv[1]=='$'){char* val=getenv(_argv[1]+1);if(val)printf("%s\n",val);}else{printf("%s\n",_argv[1]);}return 1;}else if(_argc==2&&strcmp(_argv[0],"exprot")==0){strcpy(myenv,_argv[1]);putenv(myenv);return 1;}return 0;
}int main()
{//char a[]="..";//chdir(a);while(1){// 1.创建前缀interact(commandline,sizeof(commandline));// 2. 交互问题,获取命令行, ls -a -l > myfile / ls -a -l >> myfile / cat < file.txtargc= spliststring(commandline,argv);// 3. 子串分割的问题,解析命令行if(argc==0)continue;int n=buildCommand(argv,argc);if(!n)NormalExcute();// 4. 指令的判断 // debug//for(int i = 0; argv[i]; i++) printf("[%d]: %s\n", i, argv[i]);//内键命令,本质就是一个shell内部的一个函数<Paste>}return 0;
}

总结

shell的运行就是就是进程的调用。


http://www.ppmy.cn/devtools/134019.html

相关文章

Java:JVM

1.JVM内存区域的划分 一个Java写的程序跑起来,就得到了一个Java进程 JVM 上面运行的字节码指令; 进程:操作系统资源分配的基本单位; 内存区域的划分: 1.程序计数器 在内存空间里(比较小的空间),保存了下一个要执行的指令的内存地址(元数据区的地址); 这里的"下一条…

Spring Boot MySQL 分库分表

1.首先需要在 pom.xml 中配置相关依赖 <properties><java.version>17</java.version><shardingsphere.version>5.0.0</shardingsphere.version><sharding-jdbc-spring-boot-starter.version>4.1.1</sharding-jdbc-spring-boot-starter…

C++ 中的异常处理机制是怎样的?什么情况下应该使用异常处理?异常处理的优缺点是什么?

1) C 中的异常处理机制是怎样的&#xff1f; 异常是一种处理错误的方式&#xff0c;当一个函数发现自己无法处理的错误时就可以抛出异常&#xff0c;让函数的直接或间接的调用者处理这个错误 throw: 当问题出现时&#xff0c;程序会抛出一个异常。这是通过使用 throw 关键字来…

微服务架构面试内容整理-消息驱动-RocketMQ

RocketMQ 是一个开源的分布式消息中间件,由阿里巴巴开发,具有高可靠性、高性能和可伸缩性的特点。它适用于各种场景的消息传递,包括异步处理、事件驱动架构和微服务之间的通信。以下是 RocketMQ 的主要特点、工作原理和使用场景: 主要特点 1. 高吞吐量和低延迟: RocketMQ…

深入探索 React Hooks:原理、用法与性能优化全解

一、引言 在现代 React 开发领域,Hooks 已成为不可或缺的一部分,赋予函数组件强大功能,使其能胜任复杂任务。本文将全面剖析 React Hooks,助您深入理解并熟练运用。 二、React Hooks 是什么 (一)Hooks 出现的背景 早期 React 主要依赖类组件,其通过this.state管理状…

vue2和vue3的区别详解

vue2 VS vue3 对比vue2vue3配置脚手架cmd命令行可视化方式创建脚⼿架组件通信props、$emit、provide、$arrts、EventBus等props、$emit、provide、inject、arrts等数据监听watch,computedwatch,watchEffect,computed双向绑定Object.definePropertyProxyAPI⽣命周期四个阶段befo…

学习笔记——PLCT汪辰:开发RISC-V上的操作系统(持续更新)

目录 第0章 下载源码 运行环境 构建和使用说明 第1章 记录一个本人没听说过的架构 第2章~第4章 第0章 下载源码 git clone https://gitee.com/unicornx/riscv-operating-system-mooc.git 运行环境 推荐使用 Ubuntu 20.04&#xff0c;Ubuntu 20.04 是目前最新的 Ubun…

element-ui】使用el_upload上传文件无法动态修改action

问题&#xff1a;最近在使用el_upload上传文件时&#xff0c;发现无法动态修改action的值&#xff0c;进行提交时&#xff0c;caseId2还是默认值null 原因&#xff1a;el-upload的先执行上传&#xff0c;后执行action里的响应&#xff0c;也就是赋值等操作。 解决方法&#x…