写一个简单的 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/ 内核启动相 ... 
随机推荐
- 国人开源了一款超好用的 Redis 客户端,真香!!
			大家都知道,Redis Desktop Manager 是一款非常好用的 Redis 可视化客户端工具,但可惜的是 v0.9.4 版本之后需要收费了: 这个工具不再免费提供安装包了,要对所有安装包收费 ... 
- 微信小程序内置组件web-view的缓存问题探讨
			前言:博客或者论坛上面,还有自习亲身经历,发现微信小程序的webview组件的页面缓存问题相当严重,对开发H5的小童鞋来说应该困扰了不少.很多小童鞋硬是抓破脑袋也没有办法解决这个问题,那我们今天就来探 ... 
- 【故障公告】阿里云 RDS 数据库突发 CPU 近 100% 引发全站故障
			今天晚上9点我们收到阿里云的告警通知: [阿里云监控]华东1(杭州)-云数据库RDS版<cnblogsdb> [instanceId=xxx] 于21:00 发生告警, 前往诊断 CPU使 ... 
- Go | Go 语言打包静态文件以及如何与Gin一起使用Go-bindata
			系列文章目录 第一章 Go 语言打包静态文件以及如何与Gin一起使用Go-bindata 目录 系列文章目录 前言 一.go-bindata是什么? 二.使用步骤 1. 安装 2. 使用 3. 读取文 ... 
- HM16.0之帧间预测——xCheckRDCostInter()函数
			参考:https://blog.csdn.net/nb_vol_1/article/category/6179825/1? 1.源代码: #if AMP_MRG Void TEncCu::xCheck ... 
- python numpy数组操作2
			数组的四则运算 在numpy模块中,实现四则运算的计算既可以使用运算符号,也可以使用函数,具体如下例所示: #加法运算 import numpy as npmath = np.array([98,83 ... 
- .sync 修饰符
			vue 修饰符sync的功能是:当一个子组件改变了一个 prop 的值时,这个变化也会同步到父组件中所绑定 //写一个(子)组件Child.vue <template> <div c ... 
- Java多线程_同步工具CyclicBarrier
			CyclicBarrier概念:CyclicBarrier是多线程中的一个同步工具,它允许一组线程互相等待,直到到达某个公共屏障点.形象点儿说,CyclicBarrier就是一个屏障,要求这一组线程中 ... 
- 借助FRP反向代理实现内网穿透
			一.frp 是什么? frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP.UDP.HTTP.HTTPS 等多种协议.可以将内网服务以安全.便捷的方式通过具有公网 IP 节点的中转暴露到公 ... 
- go语言字符串的处理与json转换
			1 字符串的处理 可以通过Go标准库中的strings和strconv两个包中的函数进行相应的操作 1 字符串的操作 func Contains(s, substr string) bool 字符串s ... 
