写一个简单的 Linux Shell (C++)
这里可以找到代码
支持的特性
- 单条指令的执行
- 引号引起的参数(如
$ some_program "hello, world") - 重定向(>、< )
- 管道(|)
- 内建指令(如 cd、history、quit)
- 指令别名(如 ll → ls -l)
- 家目录(~)
运行截图

如何写一个简单的 Shell
这里简单介绍写 Shell 时比较关键的一些部分,具体请查看源代码。
展示提示符
见 show_command_prompt 函数。
command_prompt 是在每行最开始显示的一段与用户名、路径等相关的提示信息。ExpShell 显示的 prompt 形如 [root@localhost tmp]>。用 > 而非 #、$ 作为提示符,以区分原生 Shell。
获取用户名
passwd *pwd = getpwuid(getuid());
string username(pwd->pw_name);
获取当前目录
getcwd(char_buf, CHAR_BUF_SIZE);
string cwd(char_buf);
- prompt 中目录只显示最近一级,此处用
/来 split 后取最后一个即可 - 家目录需要折叠为
~,这里顺便把家目录地址存到全局变量home_dir,后续要用到
- prompt 中目录只显示最近一级,此处用
获取主机名
gethostname(char_buf, CHAR_BUF_SIZE);
string hostname(char_buf);
- 有时 hostname 会是形如
localhost.locald.xxx的形式,也 split 处理一下
- 有时 hostname 会是形如
输出之即可
cout << "[" << username << "@" << hostname << " " << cwd << "]> ";
解析命令
为存储解析结果,定义如下四个类:
- cmd:各种 cmd 的基类
- exec_cmd:形如
argv[0] argv[1] ...的普通命令 - pipe_cmd:管道命令,形如
left: cmd* | right: cmd* - redirect_cmd:重定向命令,形如
cmd_: cmd* > (or <) file
(最基础的)解析 exec_cmd
见
parse_exec_cmd函数。注意这里使用string_split_protect函数来 split 出 argv,这样可以保持被引号引起的带空格的 argument 不被拆分。解析一条命令
见
parse函数。采用分治法递归地解析命令。- 从左到右扫描字符串
- 如果是普通字符,则读入缓存
- 如果是重定向符号,将当前缓存解析为 exec_cmd,作为左手边 cmd;继续不断读入直到再次遇到符号或字符串结束,作为右手边 file,构建 redirect_cmd
- 如果是管道符号,递归地调用
parse解析右侧剩余,解析结果作为本层递归的右手边,构建 pipe_cmd
解析内建命令
主要支持 cd 、history 和 quit 命令。
- 调用
exit(0)即可实现 quit - history 命令根据记录打印即可
- 对于 cd,考虑如下情况
- 无参 cd 等价于
cd ~ - 对于形如
cd ~/some_path的命令,使用home_dir替换~ - 其他情况调用
chdir即可
- 无参 cd 等价于
- 调用
执行命令
主要见 run_cmd 函数。该函数接收一个 cmd*,递归地完成其链上所有 cmd 的执行。
对于 exec_cmd
- 检查别名,替换别名,例如 ll → ls -l
- 使用
execvp函数执行命令- 看 这篇博文 了解 exec 族函数,可见
execvp在当前场景最为合适 - 第二个参数是一个末元素为 NULL 的 char**(char*[]),内容为 argv
- 看 这篇博文 了解 exec 族函数,可见
对于 pipe_cmd
为 pipe_cmd 的 left 和 right 分别 fork 子进程执行,并使用管道让这两个兄弟进程通信
这张图很好地说明了父子进程使用管道通信的方法

依据上图,不难类比出兄弟进程进行 IPC 的方法如下
- 父进程 pipe
- 父进程 fork 两次
- child1 关读端,重定向写端,执行命令,关写端
- child2 关写端,重定向读端,执行命令,关读端
- 父进程关闭读、写端,并 wait
对于 redirect_cmd
- 打开 file,获得 fd
- 重定向 stdin 或 stdout 到 fd
- 执行命令
- 关闭 fd
主函数
在一个死循环中读入当前命令,如果不是 builtin_command,则 fork 子进程进行解析和执行(避免阻塞 ExpShell 自身),执行完成后子进程 exit。
其他细节
- pipe、open、dup2 等方法返回值小于 0 均表示出现错误,需要触发 panic
- 对于 wait 方法的状态字,当
WIFEXITED(status)为 0 时表示子进程异常退出,使用WEXITSTATUS(status)可以进一步获得子进程的 exit code
写一个简单的 Linux Shell (C++)的更多相关文章
- 写了一个简单的Linux Shell用来下载文件
#!/bin/sh ; i<; i=i+ )); do # 利用spider来探测请求的资源是否存在,并把请求的结果写入到一个文件 wget --spider --http-user=usern ...
- 实现一个简单的 Linux Shell(C++)
Implement a simple command interpreter in Linux. The interpreter should: support both internal and e ...
- 如何写一个简单的shell
如何写一个简单的shell 看完<UNIX环境高级编程>后我就一直想写一个简单的shell来作为练习,因为有事断断续续的写了好几个月,如今写了差不多来总结一下. 源代码放在了Github: ...
- 一个简单的linux下设置定时执行shell脚本的示例
很多时候我们有希望服务器定时去运行一个脚本来触发一个操作,比如说定时去备份服务器数据.数据库数据等 不适合人工经常做的一些操作这里简单说下 shell Shell俗称壳,类似于DOS下的command ...
- linux设备驱动第三篇:写一个简单的字符设备驱动
在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分 ...
- linux设备驱动第三篇:如何写一个简单的字符设备驱动?
在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存 ...
- 如何写一个简单的http服务器
最近几天用C++写了一个简单的HTTP服务器,作为学习网络编程和Linux环境编程的练手项目,这篇文章记录我在写一个HTTP服务器过程中遇到的问题和学习到的知识. 服务器的源代码放在Github. H ...
- 《Linux内核分析》第三周 构建一个简单的Linux系统MenuOS
[刘蔚然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK THREE ...
- 第三节 构造一个简单的Linux系统MenuOS——20135203齐岳
第三节 构造一个简单的Linux系统MenuOS By 20135203齐岳 Linux内核源代码 arch/ 支持不同cpu的源代码 Documentations/ 文档存储 init/ 内核启动相 ...
随机推荐
- CODING DevOps 微服务项目实战系列最后一课,周四开讲!
随着软件工程越来越复杂化,如何在 Kubernetes 集群进行灰度发布成为了生产部署的"必修课",而如何实现安全可控.自动化的灰度发布也成为了持续部署重点关注的问题.CODING ...
- 网络基础之IP地址
一.IP地址 1.IP地址就是给互联网上每一台主机 (或路由器)每一个接口分配一个在全世界范围内是唯一的32位二进制的地址标识符.现在由互联网名字和数字分配机构ICANN进行分配. 2.转换成十进制 ...
- adb命令将抓包工具证书从用户目录移动至系统目录,解决反爬对于本地证书认证
代码和注释 adb shell #连接手机进入shell模式 #su root #如果你不root权限可以试着这个一般都是root cd /data/misc/user/0/cacerts-added ...
- 计算机网络-链路层(2)多路访问控制协议(multiple access control protocol)
单一共享广播信道,如果两个或者两个以上结点同时传输,会互相干扰(interference) 冲突(collision):结点同时接收到两个或者多个信号→接收失败! MAC协议采用分布式算法决定结点如何 ...
- The Successor Representation: Its Computational Logic and Neural Substrates
郑重声明:原文参见标题,如有侵权,请联系作者,将会撤销发布! Received May 14, 2018; revised June 28, 2018; accepted July 5, 2018.T ...
- pytest的fixture怎么用?
文章总览图 fixture和unittest是冲突的.舍弃unittest只用pytest. 会遇到在很多用例当中,它的前置条件是长得一样的.用例写的越来越多的时候,肯定会遇到前置条件都差不多,大家差 ...
- Solr的原理及使用
1.Solr的简介Solr是一个独立的企业级搜索应用服务器,它对外提供类似于Web-service的API接口.用户可以通过http请求,向搜索引擎服务器提交一定格式的XML文件,生成索引:也可以通过 ...
- google protocol buffer——protobuf的使用特性及编码原理
这一系列文章主要是对protocol buffer这种编码格式的使用方式.特点.使用技巧进行说明,并在原生protobuf的基础上进行扩展和优化,使得它能更好地为我们服务. 在上一篇文章中,我们展示了 ...
- seo增加外链的方法
http://www.wocaoseo.com/thread-128-1-1.html 今天给大家介绍一下本人发外链的一点经验吧.好多新手都感觉,发个外链真的好难哦.其实之前我也是这样认为的,发外链好 ...
- Javascript数组与函数初识
1 - 数组 1.1 数组的概念 数组可以把一组相关的数据一起存放,并提供方便的访问(获取)方式. 数组是指一组数据的集合,其中的每个数据被称作元素,在数组中可以存放任意类型的元素.数组是一种将一组数 ...