深圳做网站的公司哪个好,公司网站制作范文,装修网站免费设计,部队网站建设方案Linux编写一个极简版本的Shell #x1f4df;作者主页#xff1a;慢热的陕西人 #x1f334;专栏链接#xff1a;Linux #x1f4e3;欢迎各位大佬#x1f44d;点赞#x1f525;关注#x1f693;收藏#xff0c;#x1f349;留言 本博客主要内容在Linux环境下#xff…Linux编写一个极简版本的Shell 作者主页慢热的陕西人 专栏链接Linux 欢迎各位大佬点赞关注收藏留言 本博客主要内容在Linux环境下简易实现了一个Shell顺便讲解和实现了一些内建命令 文章目录 Linux编写一个极简版本的Shell①读取命令行②父子进程框架③切割命令行④子进程借用分割的结果来替换程序⑤优化⑥内建命令(重要) 首先我们观察到
bash的命令行提示符[用户名主机名 当前目录]
[milavm-5wklnbmaja demo1] 所以我们无限循环去打印这个命令行提示符
#includestdio.h
#includeunistd.h
int main()
{ while(1) { printf([xuptmy_machine currpath]#); //这里因为我们不能加换行所以得刷新缓冲区 fflush(stdout); sleep(1); } return 0;
} 运行效果 ①读取命令行
接下来我们就要获取命令输入的命令行参数
我们创建一个字符数组用来专门存放用户输入的命令行
#define MAX 1024 //因为命令行最长支持到1024
char commondstr[MAX] {0};我们用fgets来获取命令行
fgets(commondstr, sizeof(commondstr), stdin); 我们测试一下
结果正常但是我们的命令重新被打印的时候多打印了一个换行符因为fgets读取了换行符并且存储到了commondstr中了.
[milavm-5wklnbmaja demo1]$ ./myshell
[xuptmy_machine currpath]#ls -a
ls -a
解决方案
commondstr[strlen(commondstr) - 1] \0;//处理fget获取了换行符的问题 运行结果 ②父子进程框架
这个时候我们就需要用到子进程了因为执行命令行的时候需要用到程序替换那么如果我们用父进程的话直接就全崩掉了。
每次输入命令都把命令交给子进程去执行而父进程去等待子进程就好了 pid_t id fork(); assert(id 0); (void) id; //和上面的处理原因一样 if(id 0) { //child } int status 0; waitpid(id, status, 0); 在子进程执行之前我们先要将用户输入进来的命令行进行拆分
③切割命令行
切割的原理很简单我们只需要把命令行中间的空格变成\0即可。
ls -a -l ---- ls\0-a\0-l;
这个时候我们要引入一个C库提供的函数strtok,它是一个专门用来分隔字符串的函数。
我们需要封装一下这个函数来达到为我们分割命令行的目的
注意strtok函数第二次切割的时候只需要传入NULL即可。
int split(char* commondstr, char* argv[])
{ assert(commondstr); assert(argv); argv[0] strtok(commondstr, SEP); int i 1; while((argv[i] strtok(NULL, SEP)));
// {
// argv[i] strtok(NULL, SEP);
// if(argv[i] NULL) break;
// i;
// } //表示切割成功 return 0;
} main函数内部这样去调用分割函数 int n split(commondstr, argv);//等于0表示切割成功if(n ! 0) continue;//DebugPrint(argv); 我们再设计一个函数来打印我们切割的结果查看我们切割的结果是否正确
void DebugPrint(char* argv[])
{for(int i 0; argv[i]; i){printf(%d : %s\n, i, argv[i]);}
}
运行结果 ④子进程借用分割的结果来替换程序
因为我们用split函数将命令行分装到argv字符串指针数组内部了所以我们只能用带v的加载函数。
另外因为我们不能固定路径所以我们也只能用带p的。
所以综上我们的加载函数就选择到了execvp函数
在子进程内部调用 if(id 0) { //child execvp(argv[0], argv); exit(0); } 那么这时候我们在运行一下 ⑤优化
我们看到我们在用bash提供的ls的时候它产生的结果是带有颜色的。 但是我们自己实现的简易Shell是没有颜色的那么这到底是为什么
我们which ls查看一下原来系统在ls后边面追加了一个参数--colorauto; 那么我们也可以对我们的简易Shell进行一些优化让他支持这样的显示
我们只需要在代码中特判一下即可 if(strcmp(argv[0], ls) 0) { //先找到末尾 int pos 0; while(argv[pos]) pos; //追加color参数 argv[pos] (char*)--colorauto; //安全处理 pos; argv[pos] NULL; }运行效果 ⑥内建命令(重要)
1内建命令的概念
—首先我们先明确一下内建命令/内置命令的概念,就是让我们bash自己执行的命令我们称之为内建命令/内置命令。
2cd命令
当我们在我们的简易Shell中切换目录时
我们发现不论我们怎么切换目录结果都是目录没有变化**原因是我们是在子进程中运行这些命令行的**进程具有独立性。其实我们切换目录是切换了子进程的目录但是父进程也就是我们pwd显示的目录却没有任何变化,并且这里其实pwd的也是子进程的当前目录但是因为子进程在执行完cd命令后就被exit了。当我们再执行pwd的时候是一个新的子进程在帮我们完成这个命令因为我们之前cd没有改变父进程的当前目录那么新创建的子进程的目录也就变成了和父进程一样的所以看起来我们就是没有改变当前目录一样。 所以这里的cd命令我们要在父进程中交给一个函数chdir()来让我们的bash来执行:
代码 //当我们输入cd命令的时候 if(strcmp(argv[0], cd) 0) { if(argv[1] ! NULL) chdir(argv[1]); continue; } 运行结果 3export命令
此外不止我们的cd包括我们当时去在bash中执行我们的export添加环境变量的时候实际上是添加到我们的bash内部的那么如果我们的简易Shell去把这个命令交给我们的子进程去执行了那么就不太合适了应该让我们的父进程自行去执行这个命令
所以我们依旧采用内建命令的方式 //当我们输入export命令时 if(strcmp(argv[0], export) 0) { //我们把这个环境变量存储在我们自己设定的数组内部 if(argv[1] ! NULL) { strcpy(myenv[env_index], argv[1]); //再将数组内部的环境变量放到父进程的环境变量中 putenv(myenv[env_index]); } } 我们尝试测试一下 最终我们找到了 但是我们的env打印的好像是子进程的环境变量这似乎不是我们想要的我们应该想要的是父进程的环境变量所以我们再做一下处理
我们自行实现一个函数去打印我们的环境变量 void PrintEnv(){extern char **environ;for(int i 0; environ[i]; i){printf(%d:%s\n,i, environ[i]);}}//当我们查看环境变量的时候if(strcmp(argv[0], env) 0){PrintEnv();continue; } 运行效果 所以其实我们之前学习的几乎所有的环境变量相关的命令都是内建命令。
我们在将echo支持成内建命令 //当我们echo的时候if(strcmp(argv[0], echo) 0){//先确认一下echo后面第一个跟的是$if(argv[1][0] $){char* env_ret getenv(argv[1] 1); if(env_ret ! NULL){printf(%s%s\n, argv[1] 1, env_ret);}}continue;}
运行结果 既然支持了环境变量的查询我们再来顺便支持一下进程退出码的支持也就是我们的echo $? //当我们echo的时候 if(strcmp(argv[0], echo) 0) { //先确认一下echo后面第一个跟的是$ if(argv[1][0] $) { if(argv[1][1] ?) { printf(%d\n, last_exit); continue; } else { char* env_ret getenv(argv[1] 1); if(env_ret ! NULL) printf(%s%s \n, argv[1] 1, env_ret); } } int status 0;pid_t ret waitpid(id, status, 0);if(ret 0){ last_exit WEXITSTATUS(status);//last_exit我们放在main函数里但不要放在循环里他要长期保留。}
测试结果 ⑦代码汇总
#includestdio.h
#includeunistd.h
#includeassert.h
#includestring.h
#includestdlib.h
#includesys/types.h
#includesys/wait.h//因为命令行最长支持到1024
#define MAX 1024
//限制最多切割为64段
#define ARGC 64#define SEP int split(char* commondstr, char* argv[])
{assert(commondstr);assert(argv);argv[0] strtok(commondstr, SEP);int i 1;while((argv[i] strtok(NULL, SEP)));
// {
// argv[i] strtok(NULL, SEP);
// if(argv[i] NULL) break;
// i;
// }//表示切割成功return 0;
}void PrintEnv()
{extern char **environ;for(int i 0; environ[i]; i){printf(%d:%s\n,i, environ[i]);}
}void DebugPrint(char* argv[])
{for(int i 0; argv[i]; i){ printf(%d : %s\n, i, argv[i]);}
}int main()
{int last_exit 0; //存储上一个进程的退出码int env_index 0; //环境变量数组的下标char myenv[32][64];while(1){//每次进来都初始化一下char commondstr[MAX] {0};char* argv[ARGC] {NULL};printf([xuptmy_machine currpath]#);fflush(stdout);//这里因为我们不能加换行所以得刷新缓冲区char* s fgets(commondstr, sizeof(commondstr), stdin); assert(s);(void)s;//保证在release发布的时候因为assert去掉而导致s没有被使用过而产生的告警什么都没做充当一次使用commondstr[strlen(commondstr) - 1] \0; //解决了fgets读入换行符的问题int n split(commondstr, argv);//等于0表示切割成功if(n ! 0) continue;//DebugPrint(argv);//当我们输入export命令时if(strcmp(argv[0], export) 0){//我们把这个环境变量存储在我们自己设定的数组内部if(argv[1] ! NULL)strcpy(myenv[env_index], argv[1]); //再将数组内部的环境变量放到父进程的环境变量中putenv(myenv[env_index]);}}//当我们查看环境变量的时候if(strcmp(argv[0], env) 0){PrintEnv();continue; }//当我们echo的时候if(strcmp(argv[0], echo) 0){//先确认一下echo后面第一个跟的是$if(argv[1][0] $){ if(argv[1][1] ?){printf(%d\n, last_exit); continue;}else{char* env_ret getenv(argv[1] 1);if(env_ret ! NULL) printf(%s%s \n, argv[1] 1, env_ret);}}continue;}//当我们输入cd命令的时候if(strcmp(argv[0], cd) 0){if(argv[1] ! NULL) chdir(argv[1]);continue;} //当我们输入ls命令的时候if(strcmp(argv[0], ls) 0){//先找到末尾int pos 0;while(argv[pos]) pos;//追加color参数argv[pos] (char*)--colorauto;//安全处理pos;argv[pos] NULL;}pid_t id fork();assert(id 0);(void) id; //和上面的处理原因一样if(id 0) {//childexecvp(argv[0], argv);exit(0);}int status 0;pid_t ret waitpid(id, status, 0);if(ret 0){last_exit WEXITSTATUS(status);} // printf(%s\n, commondstr);} return 0;
} 到这本篇博客的内容就到此结束了。 如果觉得本篇博客内容对你有所帮助的话可以点赞收藏顺便关注一下 如果文章内容有错误欢迎在评论区指正