湖北建设监理协会网站,婚礼策划公司排名,河北网站建设模板,广州天河区医院#x1f57a;作者#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 #x1f618;欢迎关注#xff1a;#x1f44d;点赞#x1f64c;收藏✍️留言 #x1f3c7;码字不易#xff0c;你的#x1f44d;点赞#x1f64c;收藏❤️关注对我真的… 作者 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 欢迎关注点赞收藏✍️留言 码字不易你的点赞收藏❤️关注对我真的很重要有问题可在评论区提出感谢阅读 文章目录 前言01. 框架搭建02. 打印提示信息03. 获取用户键盘输入如何获取用户在命令行的输入呢 04. 命令行字符串解析05. 创建子进程执行命令怎么知道要调用的程序在哪里呢为什么要替换环境变量相关的数据会被替换吗 06. 内置命令 —— cd07. 内置命令 —— exportshell 执行的命令通常有两种shell的环境变量从哪里来的(了解) 08. 类似ll这种别名命令无法识别后记 前言
前面我们讲述了进程的相关知识包括进程创建、进程等待、进程替换等这些我们都在Linux上进行了测试并且通常使用的shell来执行命令那么我们能不能自己来实现一个简单的shell呢
我们知道在shell上执行命令时其原理不过也只是调用和执行文件罢了也就是创建进程来执行程序而shell一般是不退出的那么我们现在开始玩一下
01. 框架搭建
命令行解释器一定是一个常驻内存的进程不退出所以我们使用while包起来
#include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include sys/wait.h
#include sys/types.h#define NUM 1024
#define SIZE 32
#define SEP //保存完整的命令行字符串
char cmd_line[NUM];//保存打散之后的命令行字符串
char *g_argv[SIZE];// 写一个环境变量的buffer用来测试
char g_myval[64];// shell 运行原理 通过让子进程执行命令父进程等待解析命令
int main()
{extern char**environ;//0. 命令行解释器一定是一个常驻内存的进程不退出while(1){}
}
02. 打印提示信息
参考shell的提示信息 它们都有各自的含义和获取方式但是这里为了简化不考虑这些细枝末节由大家自己改进 我这里就写死了哦~
我们可以直接使用printf函数打印但是会有一个问题如果设置了\n它会换行但是我们使用shell时并不会换行那么我们就需要用到fflush函数来冲刷缓存区。 //1. 打印出提示信息 [venuslocalhost myshell]# printf([rootlocalhost myshell]# );fflush(stdout);03. 获取用户键盘输入
如何获取用户在命令行的输入呢
我们用一个数组来存储命令使用fgets函数来获取输入 步骤
先初始化数组获取存储命令将最后的回车符号设置为\0
//2. 获取用户的键盘输入[输入的是各种指令和选项: ls -a -l -i]if(fgets(cmd_line, sizeof cmd_line, stdin) NULL){continue;}cmd_line[strlen(cmd_line)-1] \0;04. 命令行字符串解析
到这一步我们已经将命令行的字符串存储到数组中了接下来就是解析它 步骤
这里要使用strtok函数来裁剪字符串将存储的命令和系统内部命令做比对如果有就执行
g_argv[0] strtok(cmd_line, SEP); //第一次调用要传入原始字符串
int index 1;
if(strcmp(g_argv[0], ls) 0)
{g_argv[index] --colorauto;//加入配色
}
while(g_argv[index] strtok(NULL, SEP)); //第二次如果还要解析原始字符串,传入NULL
05. 创建子进程执行命令
怎么知道要调用的程序在哪里呢
直接使用进程替换使用execvp函数它可以直接使用环境变量不用自己写了也就是直接掉用系统中的指令的程序来使用即可。
为什么要替换
一切和应用场景有关我们有时候必须要让子进程执行新的程序
环境变量相关的数据会被替换吗
没有它不会被替换它会把父进程的环境变量拷贝继承过来它具有全局属性
pid_t id fork();
if(id 0) //child
{printf(下面功能让子进程进行的\n);execvp(g_argv[0], g_argv); // ls -a -l -iexit(1);
}
//father
int status 0;
pid_t ret waitpid(id, status, 0);
if(ret 0) printf(exit code: %d\n, WEXITSTATUS(status));此时程序基本功能就已经实现了
但是我们发现一个问题使用cd命令时他的路径不会改变这是个bug
原因是 在cd的时候自己写的shell都会执行execvp它只会影响子进程的路径 但是我们需要改变父进程的路径所以像cd这种命令我们不想让子进程去执行它而让父进程去执行它 这种让父进程自己执行的命令叫做内置命令、内建命令它的本质是shell中的一个函数调用
我们来修改下功能
06. 内置命令 —— cd
这里可以使用chdir函数来实现
chdir函数可以改变文件路径
我们可以使用下面代码使得cd命令的使用合理但是可能其他类似的命令也会出现相似的bug需要一一比对实现这里仅针对cd命令
if(strcmp(g_argv[0], cd) 0) //not child execute, father execute
{if(g_argv[1] ! NULL) chdir(g_argv[1]); //cd path, cd ..continue;
}07. 内置命令 —— export
上面我们讲了cd的bug而export也和cd一样也需要进行处理export的作用是导入环境变量我们既不想覆盖父进程的环境变量又想导入自己的环境变量该怎么做呢
代码如下
//导入环境变量
//比较第一个是不是export
if(strcmp(g_argv[0], export) 0 g_argv[1] ! NULL)
{strcpy(g_myval, g_argv[1]);//是的就取出后面的值int ret putenv(g_myval);//将它导入环境变量中if(ret 0) printf(%s export success\n, g_argv[1]);//如果导入成功就打印出来continue;
}shell 执行的命令通常有两种 第三方提供的对应的在磁盘中具有二进制文件的可执行程序(由子进程执行) shell内部自己实现的方法由自己(父进程)来执行有些命令就是要影响shell本身如改变路径的(cd、export)shell代表的是用户。
shell的环境变量从哪里来的(了解)
环境变量是写在配置文件中的shell启动的时候通过读取配置文件获得的起始环境变量
08. 类似ll这种别名命令无法识别
ll是ls -l的别名
想要支持就要当识别到ll时执行ls命令即可
if(strcmp(g_argv[0], ll) 0)
{g_argv[0] ls;g_argv[index] -l;g_argv[index] --colorauto;
}系统中肯定不是这样实现的但是大致原理相同
后记
最后我们就实现了一个简易的shell解释器
全部代码如下
#include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include sys/wait.h
#include sys/types.h#define NUM 1024
#define SIZE 32
#define SEP //保存完整的命令行字符串
char cmd_line[NUM];//保存打散之后的命令行字符串
char *g_argv[SIZE];// 写一个环境变量的buffer用来测试
char g_myval[64];// shell 运行原理 通过让子进程执行命令父进程等待解析命令
int main()
{extern char**environ;//0. 命令行解释器一定是一个常驻内存的进程不退出while(1){//1. 打印出提示信息 [whblocalhost myshell]# printf([rootlocalhost myshell]# );fflush(stdout);memset(cmd_line, \0, sizeof cmd_line);//2. 获取用户的键盘输入[输入的是各种指令和选项: ls -a -l -i]if(fgets(cmd_line, sizeof cmd_line, stdin) NULL){continue;}cmd_line[strlen(cmd_line)-1] \0;//3. 命令行字符串解析ls -a -l -i - ls -a -i// export myval105g_argv[0] strtok(cmd_line, SEP); //第一次调用要传入原始字符串int index 1;if(strcmp(g_argv[0], ls) 0){g_argv[index] --colorauto;}if(strcmp(g_argv[0], ll) 0){g_argv[0] ls;g_argv[index] -l;g_argv[index] --colorauto;}while(g_argv[index] strtok(NULL, SEP)); //第二次如果还要解析原始字符串,传入NULLif(strcmp(g_argv[0], export) 0 g_argv[1] ! NULL){strcpy(g_myval, g_argv[1]);int ret putenv(g_myval);if(ret 0) printf(%s export success\n, g_argv[1]);continue;}//4.内置命令, 让父进程shell自己执行的命令我们叫做内置命令内建命令//内建命令本质其实就是shell中的一个函数调用if(strcmp(g_argv[0], cd) 0) //not child execute, father execute{if(g_argv[1] ! NULL) chdir(g_argv[1]); //cd path, cd ..continue;}//5. fork()pid_t id fork();if(id 0) //child{printf(下面功能让子进程进行的\n);printf(child, MYVAL: %s\n, getenv(MYVAL));//测试环境变量printf(child, PATH: %s\n, getenv(PATH));//测试环境变量//环境变量相关的数据会被替换吗没有execvp(g_argv[0], g_argv); // ls -a -l -iexit(1);}//fatherint status 0;pid_t ret waitpid(id, status, 0);if(ret 0) printf(exit code: %d\n, WEXITSTATUS(status));}
}