违法网站建设国外服务器,网站建设 启象科技,做企业网站步骤,开发一款游戏软件需要多少钱目的基本结构提取输入命令fgets的使用命令初步处理命令的本质创建子进程重要知识补充进程替换命令处理简单 bash 完成及演示优化bashls颜色输出颜色实现cd命令ecport 命令envecho $echo $#xff1f;目的
主要目的在于进一步了解 Linux 系统下使用进程相关的系统调用 及 shel…目的基本结构提取输入命令fgets的使用命令初步处理命令的本质创建子进程重要知识补充进程替换命令处理简单 bash 完成及演示优化bashls颜色输出颜色实现cd命令ecport 命令envecho $echo $目的
主要目的在于进一步了解 Linux 系统下使用进程相关的系统调用 及 shell 工作的基本原理
本篇文章适合有一定C语言基础及基本了解 Linux操作 和 Linux进程同学编写
为减少废话我基本不会解释简单语句以及所有函数用法我相信大家既然要写这个命令行解释器 bash对语法等相关知识肯定是有了一定了解
最终目的实现一个基本能用的bash
主要内容
获取命令行解析命令行建立一个子进程fork替换子进程execvpcd / ecport / env / echo 等特殊命令单独处理使其运行在父进程中 父进程等待子进程退出waitpid
基本结构
首先在Linux命令行上我们是可以不断输入的所以其一定是一个循环在这我们就设置一个死循环吧最后程序退出使用 CTRL C 退出即可 如下 运行效果 如下输出了我想要的命令行提示字符串想要不一样的自己可以改printf中的内容 由于是死循环所以最后输入CTRL C 退出程序 注意在做下一步时上述的的sleep(1)及命令行提示字符串后面的 \n 可以删掉了这些只是测试用的
提取输入命令
在命令行输入的命令需先存入数组供后续处理可打印测试自己是否将命令存入了数组但后续需删掉打印代码 运行如下
fgets的使用
通过man手册查询得知上述用到的 fgets资料如下 从流中读取最多一个小于size的字符并将它们存储到s指向的内存中,读取到EoF或换行符后停止。且换行符也会被存到s指向的内存中最后会在最后一个数据后一位填充字符串终止符\0
fgets()读取成返回s,失败返回NULL
所以读取命令行之后需要检查一下fgets()是否读取失败
命令初步处理
上面说到fgets()会把\n也存到字符串中。刚好在命令行输入时就会用回车表示输入结束 所以我加了第19行处理了一下 没处理之前printf中并未加换行符但打印出的代码却自动换了说明存命令输入的数组末尾确实存有\n不然不会自动换行 测试如下 处理之后打印出的代码不会自动换行了。
命令的本质
Linux中执行命令的本质就是进程进程就是执行某个程序所以执行命令就是执行程序
但是命令基本都是以子进程的形式进行官方bash 在执行命令时也是多以子进程形式进行的这样能保证bash的稳定性
子进程的好处子进程执行命令时出错不会影响父进程并且在发生错误后还会将错误返回给父进程父进程只需要报错即可
创建子进程
上面讲述了为保证bash的稳定性可用子进程来执行命令我也采用这一方法 这次代码增加了两个头文件 怎加代码如下
重要知识补充
说明一下其实我们平时在命令行执行的 ls -l 其实 ls 就是程序文件名 -l 是程序参数表示我们要怎么执行。
如 ls 等命令程序文件的路径都是在默认环境中的在默认环境路径下的程序直接输入程序文件名即可运行但不在默认环境路径下的程序需 ./ 执行
进程替换
上面讲述了执行命令就是执行特定的程序 但是要执行其他程序我们需要进行进程替换这里我用的进程替换为execvp()介绍如下
参数中字符串 file是程序文件名如 ls argv 是以NULL结尾的指针数组argv 里存的是 程序文件名程序参数等 execvp()只有错误时才会返回-1
命令处理
上述讲述要执行进程替换execvp需传程序名文件名 及以NULL结尾的指针数组 我们想要的
char* argv[] {ls,-l,NULL};
execvp(argv[0],argv);但是咱读取命令行得到的是这样的如在命令行输入ls -l 实际得到的
char* command {ls -l}; 所以咱得处理一下所获得的字符串并用指针数组储存起来 处理思路 1 将command 数组中数据以空格为分割将数据存入指针数组argv中 2 最后在 argv 数组有效元素末尾添加NULL
字符串分割我用的是这个函数
strtok()函数的作用是:将一个字符串分解为0个或多个非空标记的序列。在第一次调用strtok()时要解析的字符串应该在str中指定。在后续的每个应该解析相同字符串的调用中str必须为NULL。 delim参数指定了一组字节用于分隔parsedl字符串中的标记。
如果找到了指定分割符则返回其分割后的字符串注意每次只能找一个分割符并返回一个字符串失败返回NULL
则代码如下编写
处理字符串的分割函数 主函数增加了五句
编辑好后直接退出vim编译一下运行即可执行ls pwd ps 命令 还能创建文件 等
简单 bash 完成及演示 优化bash
ls颜色输出颜色
测试发现自己写的bashls输出的文件列表没有配色
查询得知 ls 只是别名其调用时实际时 ls - -colorauto,所以实际是系统bash在调用ls时同时也调用了配色方案 在自己的bash中查询 ls 没有带 --colorauto 经测试在命令行结尾 添加–colorauto即可调用配色方案 但每次执行 ls 都要手动加上 --colorauto那太麻烦了吧所以咱直接优化代码 添加代码如下 再执行就已经有了颜色搭配
实现cd命令
在自己编写的bash中执行 cd 发现并不作用这是因为 cd 也在子进程执行了执行完cd后子进程又推出了改变的是子进程当前目录但咱们的bash作为父进程并没有改变 改进代码使 cd 命令在 bash 进程中运行
改进前咱先了解一下一个函数 chdir()将调用进程的当前工作目录更改为path中指定的目录如果成功返回0。出错时返回-1
所以咱要改变当前进程工作目录直接调用chdir即可但咱需要先筛选出 cd 命令 查找函数 编辑好后退出vim,编译运行即可正常使用cd命令
ecport 命令
ecport是定义环境变量env 是查询环境变量
和上述cd一样如果不做特殊处理ecport和env命令也是被子进程运行了细想我们自己写的bash每次执行命令都会创建子进程执行假设先执行 ecport 程序开辟子进程定义 ecport命令 后进程就退出了进程被销毁ecport定义的环境变量也没了
之后又想执行 env 命令程序开辟子进程执行了env命令肯定是查询不到刚刚定义的环境变量因为他们在执行命令时都不是一个进程不可能被查询到
但是只要ecport是在父进程运行的子进程就会继承父进程的环境变量也就能查询到了
这里就不演示了直接修改代码吧 思路ecport 和cd命令一样需要特殊处理执行在父进程下 添加环境变量的函数如下 putenv()函数的作用是:添加或更改环境变量的值。参数字符串的形式为namevalue。如果name在环境中不存在则将string添加到环境中。如果name存在则将环境中name的值更改为value。string所指向的字符串成为环境的一部分因此改变字符串也改变了环境。
putenv()函数成功时返回零如果发生错误则返回非零。如果发生错误则设置errno来指示错误的原因 添加代码如下
但是经测试env还是查询不到刚刚定义的变量环境列表很长我就不截全了 这是因为在bash中定义的环境变量需要自己去维护存储环境变量的内存保证其不被覆盖且一直存在
所以咱在putenv()之前得把要声明的环境变量存储到一个在当前进程结束前不会被覆盖的位置。
更改后代码如下 将之前的putenv命令等代码放到main函数中 这样就改完啦
env
env是用于查看环境变量的命令由于之前我没有对env命令进行处理所以现在执行env肯定是被执行在子进程中的
但是我们在执行env的时候是想看自己的环境变量还是子进程的环境变量呢 不用质疑肯定是自己的所以env命令也需要特殊处理
首先在int find_command(char* argv[])函数中加入红框中代码用于筛选env命令 主函数也有所改变两个 if 语句相较于之前被调换了上下位置 跟改完后编译运行即可。打印出来的每个环境变量之前有编号是因为我的打印命令自己加的
echo $
这个命令也需要特殊处理 要不然也会去提取子进程中的内容。使用该命令时用户肯定是想要查看当前进程内容的 在int find_command(char* argv[])函数中加入红框中的内容即可对echo$命令做处理
执行例如 echo $PATH命令 echo $USER命令
echo $
这个命令是提取进程退出码但目前我的My_bash并未实现这一功能。执行后居然是打印说明功能缺失了。 需要特殊处理一下
思路在筛选出echo命令后在筛选$后面紧跟的 于是在之前筛选echo命令的if中加入了一个判断是否是号为打印退出码函数也怎加了一个退出码的形参。 主函数也有改变怎加了一个存储退出码的变量 怎加括号中内容提取退出码 函数传参也多传了一个退出码 测试如下已经能正常输出退出码
小伙伴们到这我就演示完成了可能还有其他功能没有实现需要自己扩展哦 下面是本人源码
#includestdlib.h
#includestdio.h
#includeassert.h
#includestring.h
#includeunistd.h//sleep的头文件#includesys/types.h
#includesys/wait.h#define max 100int DisposeStr(char* _command ,char* _argv[])//字符串分割处理
{int i 0;_argv[i] strtok(_command, );if(_argv[i] NULL){ return -1; }//如果一个字符串都没有直接返回while(_argv[i])//当等于NULL退出循环{ _argv[i] strtok(NULL, );//连续调用用NULL}if(strcmp(_argv[0] , ls) 0)//如果输入的是ls命令{_argv[--i] (char*)--colorauto;//在NULL位置改为 --colorauto_argv[i] NULL;//在最后以NULL结尾}return 0;
}int find_command(char* argv[], int quit)
{if(strcmp(argv[0],echo)0)//匹配env命令{const char* pp NULL;if(argv[1][0] $)//保证其是 echo $.....{pp getenv(argv[1]1);//$之后的字符串if(argv[1][1] ?){printf(%d\n, quit );}//提取$后面是否是else if(pp ! NULL){ printf(%s%s\n,argv[1]1 , pp) ; }else{return 1;}//匹配失败}else{return 1;}//匹配失败return 0;//执行结束返回0}if(strcmp(argv[0],cd)0)//匹配cd命令{int i chdir(argv[1]);if(i -1){printf(%s\n,strerror(2));//cd执行出错则报错}return 0;//执行结束返回0} if(strcmp(argv[0],env)0)//匹配env命令{int i 0;extern char** environ;for(i 0 ; environ[i]; i ){printf(%d:%s\n,i ,environ[i]);//循环打印环境变量}return 0;//执行结束返回0}return 1;//返回1代表没匹配上对应指令需要执行else
}int main()
{char export_str[30][100] {{0}};int port 0;int _quit 0;//储存退出码while(1){char* argv[10] {NULL};//初始化为NULLchar command[100] {0};//将数组初始化\0printf([ZhuGeBin made the bash]$);//命令行提示符char* tmp fgets(command, 100 , stdin);//从输入流中输入到command数组中assert(tmp);//确保命令读取成功(void)tmp;//保证编译不报错command[strlen(command)-1] \0;//将字符串末尾的\n去掉int cur DisposeStr(command , argv);//字符串分割处理if(cur -1){continue ;}//如果输入空字符串重新输入if(strcmp(export,argv[0])0)//匹配exprot命令{strcpy(export_str[port] , argv[1]);//存储环境变量putenv(export_str[port-1]);//添加环境变量continue;}else if(find_command(argv , _quit)0) { }//查找命令else{pid_t it fork();//创建子进程if(it 0){int i execvp(argv[0],argv);printf(%s\n,strerror(i));exit(1);}int status 0;//需初始化为0int cur waitpid(it , status , 0);//阻塞式等待子进程if(cur 0 )//等待成功{_quit WEXITSTATUS(status);}}}return 0;
}