github.com/z0gSh1u/expshellgit
$ some_program "hello, world"
)这里简单介绍写 Shell 时比较关键的一些部分,具体请查看源代码。github
见 show_command_prompt
函数。shell
command_prompt 是在每行最开始显示的一段与用户名、路径等相关的提示信息。ExpShell 显示的 prompt 形如 [root@localhost tmp]>
。用 > 而非 #、$ 做为提示符,以区分原生 Shell。缓存
获取用户名函数
passwd *pwd = getpwuid(getuid()); string username(pwd->pw_name);
获取当前目录ui
getcwd(char_buf, CHAR_BUF_SIZE); string cwd(char_buf);
/
来 split 后取最后一个便可~
,这里顺便把家目录地址存到全局变量 home_dir
,后续要用到获取主机名.net
gethostname(char_buf, CHAR_BUF_SIZE); string hostname(char_buf);
localhost.locald.xxx
的形式,也 split 处理一下输出之便可code
cout << "[" << username << "@" << hostname << " " << cwd << "]> ";
为存储解析结果,定义以下四个类:blog
argv[0] argv[1] ...
的普通命令left: cmd* | right: cmd*
cmd_: cmd* > (or <) file
(最基础的)解析 exec_cmd递归
见 parse_exec_cmd
函数。注意这里使用 string_split_protect
函数来 split 出 argv,这样能够保持被引号引发的带空格的 argument 不被拆分。
解析一条命令
见 parse
函数。采用分治法递归地解析命令。
parse
解析右侧剩余,解析结果做为本层递归的右手边,构建 pipe_cmd解析内建命令
主要支持 cd 、history 和 quit 命令。
exit(0)
便可实现 quitcd ~
cd ~/some_path
的命令,使用 home_dir
替换 ~
chdir
便可主要见 run_cmd
函数。该函数接收一个 cmd*
,递归地完成其链上全部 cmd 的执行。
对于 exec_cmd
execvp
函数执行命令
execvp
在当前场景最为合适对于 pipe_cmd
为 pipe_cmd 的 left 和 right 分别 fork 子进程执行,并使用管道让这两个兄弟进程通讯
这张图很好地说明了父子进程使用管道通讯的方法
依据上图,不难类比出兄弟进程进行 IPC 的方法以下
对于 redirect_cmd
在一个死循环中读入当前命令,若是不是 builtin_command,则 fork 子进程进行解析和执行(避免阻塞 ExpShell 自身),执行完成后子进程 exit。
WIFEXITED(status)
为 0 时表示子进程异常退出,使用 WEXITSTATUS(status)
能够进一步得到子进程的 exit code