【Linux】自己实现一个bash进程

devtools/2024/9/25 11:15:25/

目录

读入用户输入

对字符串进行分割

处理内建命令

cd命令

export

echo

处理重定向

代码汇总


bash就是命令行解释器,就是Linux操作系统让我们看到的,与用户进行交互的一种外壳(shell),当然了bash也是一个进程,它有时候就是通过创建子进程来执行我们输入的命令的。这无疑就离不开我们上篇博客所说的进程程序替换,就是让子进程去替换我们的命令进程,知道了它的原理,我们就可以试着自己写一个bash进程。

我们可以大体上对于整个过程来细分一下:

1.bash就是先打印命令行提示符,就是下面这个东西,获取用户输入的命令

命令行提示符由  [用户名@主机名 路径]#  这些构成

2.对于用户输入的字符串进行分割成命令

3.执行这个命令

其实大体上就是这三步,下面还有很多的细节问题,我们遇到再说

读入用户输入

我们目前写的这个代码的结果是

为什么打印了两个换行?我们了解一下fgets这个函数

因为fgets会读入一个换行,就是我们敲完abc后的换行,它是不应该有的,我们要给它去掉

这样就可以把换行改成0了。

因为bash就是一只在等待用户输入指令,所以我们要把我们的程序写成一个循环。

还有一个问题,就是当我们什么都不输入,直接回车时,我们其实就没必要在执行下边了,直接回到循环最开始就可以,于是我们可以做一个判断就是如果输入的命令的长度为0,那么就直接回到循环最开始处,这里的长度可以让Interactive的返回值来给。

对字符串进行分割

下一步就是对于字符串进行分割:

我们分割的话,函数可以将收到的字符串分割后放到一个全局变量中,方便后续的使用,我们C语言有一个分割字符串的函数,叫strtok

分割完了之后,我们就可以开始执行命令了,就是让子进程执行,父进程等待。我们这里先是一些普通的需要子进程去执行的命令,因为有的命令需要父进程去执行,比如cd,需要父进程去切换路径,子进程是不行的

处理内建命令

那么截止到现在,我们的程序已经可以像bash一样处理一些命令了,但是还不够,因为有一些内建命令,就是需要父进程去执行的:

这些命令,我们要在代码被子进程执行前先行判断,如果是内建命令,那么让父进程去执行,否则再让子进程去执行

cd命令

我们这里先以cd命令为例,如果判断出来了用户就是要输入cd命令,如果后边什么都没有,那么默认是回到家目录,如果有,那就chdir到那个目录,并且不要忘记命令行提示符可是一直通过环境变量PWD来打印我们当前所在目录的,所以我们还要通过putenv把PWD环境变量改一下,它默认是会覆盖的

export

之后我们要知道export也是一个内建命令,export的作用是给自己设置一个环境变量,如果给子进程设置,那显然是不合理的,所以我们也需要处理一下。

echo

下一个就是echo,我们的echo通常会有这么几种用法:

1.echo后什么都不加

2.后加$?表示打印最近一次进程的退出码

3.后随便打印一串字符

4.后加$环境变量,就打印环境变量的内容

处理重定向

我们应该如何处理重定向呢?

首先,在分割命令时,我们就应该判断这个命令中是否有重定向,包括输入重定向(<),输出重定向(>),追加重定向(>>),我们做好标记

然后,在执行命令的时候如果有上面的标记,那么就用dup2系统调用改掉相对应的文件描述符表中的内容

我们测试输入和追加重定向好测试,测试输入重定向可以写一个代码,这个代码负责从stdin中读入并输出数据,这时我们用输入重定向就可以将stdin改成普通文件,这时就会打印出普通文件中的内容

代码汇总

下面是所有的代码,有不足的可以添加:

#define _XOPEN_SOURCE
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<fcntl.h>
#define SIZE 1024
#define MAX_ARGC 30//最大命令行字符串个数
#define SEP " "//设置分隔符为空格
char*argv[MAX_ARGC];
int lastcode;//最近一次进程退出码
enum redirection{No_redir,In_redir,Out_redir,App_redir
};
int redir=No_redir;
char redirfile[30];
void Extractfile(char*str)
{int cur=0;while(str[cur]==' ')cur++;strcpy(redirfile,str+cur);
}
int Interactive(char commandline[],int size)
{printf("[%s@%s %s]$ ",getenv("USER"),getenv("HOSTNAME"),getenv("PWD"));fgets(commandline,size,stdin);commandline[strlen(commandline)-1]='\0';int end=strlen(commandline)-1;while(end>=0){if(commandline[end]=='>'){if(commandline[end-1]=='>'){redir=App_redir;commandline[end-1]='\0';Extractfile(commandline+end+1);break;}else{redir=Out_redir;commandline[end]='\0';Extractfile(commandline+end+1);break;}}else if(commandline[end]=='<'){redir=In_redir;commandline[end]='\0';Extractfile(commandline+end+1);break;}else end--;}return strlen(commandline);
}
void Splitstr(char commandline[])
{int i=0;argv[i++]=strtok(commandline,SEP);while(argv[i++]=strtok(NULL,SEP));if(strcmp("ls",argv[0])==0){argv[i-1]="--color";argv[i]=NULL;}
}
void Execute()
{if(strcmp("ll",argv[0])==0&&!argv[1])//特别处理ll{argv[0]="ls";argv[1]="-l";}pid_t id=fork();if(id==0){if(redir==In_redir){int fp=open(redirfile,O_RDONLY);dup2(fp,0);}else if(redir==Out_redir){int fp=open(redirfile,O_WRONLY|O_CREAT|O_TRUNC,0666);dup2(fp,1);}else if(redir==App_redir){int fp=open(redirfile,O_WRONLY|O_CREAT|O_APPEND,0666);dup2(fp,1); }execvp(argv[0],argv);printf("mybash: ");//只有进程程序替换失败才会走到下边for(int i=0;argv[i];i++)printf("%s ",argv[i]);printf(": not found your command\n");exit(2);}redir=No_redir;int status=0;pid_t rid=waitpid(id,&status,0);if(rid>0)lastcode=WEXITSTATUS(status);
}
int Bulidincmd()
{int ret=0;if(strcmp("cd",argv[0])==0){ret=1;char*target=argv[1];if(!target)target=getenv("HOME");chdir(target);char tmp[SIZE];snprintf(tmp,SIZE,"PWD=%s",target);putenv(tmp);}else if(strcmp("export",argv[0])==0){ret=1;if(argv[1]){char tmp[SIZE];strncpy(tmp,argv[1]+1,strlen(argv[1])-2);putenv(tmp);}}else if(strcmp("echo",argv[0])==0){ret=1;if(argv[1]&&argv[1][0]=='$'){if(argv[1][1]=='?'&&!argv[2]){printf("%d\n",lastcode);lastcode=0;}else{char* tmp=getenv(argv[1]+1);if(tmp)printf("%s\n",tmp);}}else{for(int i=1;argv[i];i++)printf("%s ",argv[i]);printf("\n");}}lastcode=0;return ret;
}
int main()
{while(1){char commandline[SIZE];//打印命令行提示符,获取用户输入的命令字符串int n=Interactive(commandline,SIZE);if(n==0)continue;//分割字符串成命令行参数Splitstr(commandline);//处理内建命令n=Bulidincmd();if(n==1)continue;//执行命令Execute(); }return 0;
}


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

相关文章

【稳定检索/投稿优惠】2024年商务、信息管理与大数据经济国际会议(BIMBDE 2024)

2024 International Conference on Business, Information Management, and Big Data Economy 2024年商务、信息管理与大数据经济国际会议 【会议信息】 会议简称&#xff1a;BIMBDE 2024 大会地点&#xff1a;中国北京 会议官网&#xff1a;www.bimbde.com 会议邮箱&#xff…

AI程序员来了,大批码农要失业

根据GitHub发布的《Octoverse 2021年度报告》&#xff0c;2021年中国有755万程序员&#xff0c;排名全球第二。 ChatGPT的出现&#xff0c;堪比在全球互联网行业点燃了一枚“核弹”&#xff0c;很多人都会担心“自己的工作会不会被AI取代”。 而2024年的AI进展速度如火箭般&am…

C#面:如果不用VisualStudio,用哪个命令行编译C#程序

可以使用命令提示符或者终端来执行编译命令 csc.exe 。 步骤&#xff1a; 打开命令提示符或终端。使用 cd 切换到 C# 程序所在的目录。使用以下命令来编译C#程序&#xff1a; 其中&#xff0c;是你的C#源代码文件的名称&#xff08;包括扩展名.cs&#xff09;。如果编译成功&…

IP地址开启HTTPS方法

可以使用IP地址申请SSL证书&#xff0c;申请之前必须是公网IP地址&#xff0c;不支持内网IP地址申请。 申请过程需要确定IP地址外网可以访问&#xff0c;这里特别注意只是申请过程中可以访问。访问验证过程必须采取80端口、443端口两者选择1个&#xff0c;不可以用其它端口进行…

完全指南:C语言学习资源汇总

C语言是编程学习的基石&#xff0c;无论是为了职业发展还是个人兴趣&#xff0c;掌握C语言都是技术生涯的重要一步。为了帮助初学者和有经验的程序员更好地学习和深化对C语言的理解&#xff0c;我们汇总了一系列优秀的书籍和在线资源。这些资源将帮助你从基础知识到高级概念&am…

2024网络与信息安全管理员职工职业技能竞赛re0220164094

main部分&#xff0c;就是要逆这部分shellcode&#xff0c;程序把data段里面的东西复制到bss段去执行&#xff0c;期间包含解码操作。 v19 0;puts("Please input your flag: ");__isoc99_scanf("%s", s);if ( strlen(s) ! 38 ){puts("Wrong length!&…

java实现jpg转png

在Java中&#xff0c;你可以使用javax.imageio.ImageIO类来实现JPG到PNG的转换。以下是一个简单的方法&#xff0c;它接受JPG文件的路径&#xff0c;读取该文件&#xff0c;转换成PNG格式&#xff0c;并保存到指定的输出路径。 以下是实现代码&#xff1a; import javax.image…

【启明智显分享】国产自主ZX7981P Wi-Fi6 5G-CPE开发板有哪些优势?

在当前竞争激烈的智能设备市场中&#xff0c;高性能与低功耗的开发板已然成为各大产品追求的关键优势。 今天我们从国产自主研发的ZX7981P Wi-Fi6 5G-CPE开发板的特点出发&#xff0c;分析他是否满足市场追求的特点。 主要特点&#xff1a; 1. 强大配置&#xff0c;稳定可靠 …