刚接触Linux时,对shell总有种神秘感;在对shell的工作原理有所了解之后,便尝试着动手写一个shell。下面是一个从最简单的情况开始,一步步完成一个模拟的shell(我命名之为wshell)的过程。这个所谓的shell和主流的shell还是有不少区别的,最大的区别是它本身不能执行shell脚本、也不能对一些复杂的命令行进行分析——原因很简单,我没有写相应的解释器。如果想自己实现一个简化的shell脚本解释器,如果有编译原理的知识准备,本身不是难事,但是工作量比较大,这里就不完成了,有兴趣的读者可以进行尝试。
本文是边写代码边记录的,更接近于实现过程的思考过程,因此前面的章节可能和最新版的代码有不小的差别,较大的改动会在后文提出,请注意。不过读者不用担心,这些改动都是在原有基础上的完善和提升,并非推倒重来。可以算作上一篇博文《现代操作系统》精读与思考笔记 第一章 引论的副产品。
全部的代码开源,已托管至github:https://github.com/vvy/wshell,因此不再往文中大段大段地粘源代码了。第一次用github托管代码,如果有哪里没设置好请告诉我。
文中所指的和所模仿的shell均指bash。
一.基本功能
1.1 程序框架
首先,shell的基本框架可以用下面的代码概括,这部分代码出自于《现代操作系统(英文第三版)》(Modern Operating Systems)原书P54图1-19,这在上一篇博文《现代操作系统》精读与思考笔记 第一章 引论中已经提到过一次了:
#define TRUE 1 while(TRUE) { /* repeat forever */ type_prompt(); /* display prompt on the screen */ read_command(command,parameters); /* read input from terminal */ if(fork()!=0) { /* fork off child process */ /* Parent code */ waitpid(-1,&status,0); /* wait for child to exit */ } else { /* Child code */ execve(command,parameters,0); /* execute command */ } }
不怕写不出来代码,就怕没思路。想想这么搞确实能够模拟出shell最基本的行为:接受用户输入<=>执行相应程序,甚至借助execv族函数可以直接给程序传参数。有了这个框架就好办了,把那几个函数给实现不就成了呗?
1.2 type_prompt()的实现
来思考下type_prompt()该如何实现。顾名思义,这个要提供一个终端上的提示符,比如
再如
这里的实现需要注意的是,如果当前路径在用户路径下,那么用户路径就用~代替,否则会显示完整路径。分析这两个例子,可以看到输出是这样的形式:“用户名@主机名:路径$”(root权限的#提示符马上提到),对应地:
- 用户名使用getpwuid(getuid())获得,同时可以获得该用户home目录的路径;
- 主机名使用gethostname()获得;
- 路径使用getcwd()获得,如果这个路径包含了该用户home路径,那么使用~把home路径缩略。
- 对于提示符,模仿bash的风格,对于普通用户使用"$",root用户使用"#",需要检测执行这个wshell的用