这里可以找到代码

github.com/z0gSh1u/expshell

支持的特性

  • 单条指令的执行
  • 引号引起的参数(如 $ 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,后续要用到
  • 获取主机名

    gethostname(char_buf, CHAR_BUF_SIZE);
    string hostname(char_buf);
    • 有时 hostname 会是形如 localhost.locald.xxx 的形式,也 split 处理一下
  • 输出之即可

    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 即可

执行命令

主要见 run_cmd 函数。该函数接收一个 cmd*,递归地完成其链上所有 cmd 的执行。

  • 对于 exec_cmd

    • 检查别名,替换别名,例如 ll → ls -l
    • 使用 execvp 函数执行命令
      • 这篇博文 了解 exec 族函数,可见 execvp 在当前场景最为合适
      • 第二个参数是一个末元素为 NULL 的 char**(char*[]),内容为 argv
  • 对于 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++)的更多相关文章

  1. 写了一个简单的Linux Shell用来下载文件

    #!/bin/sh ; i<; i=i+ )); do # 利用spider来探测请求的资源是否存在,并把请求的结果写入到一个文件 wget --spider --http-user=usern ...

  2. 实现一个简单的 Linux Shell(C++)

    Implement a simple command interpreter in Linux. The interpreter should: support both internal and e ...

  3. 如何写一个简单的shell

    如何写一个简单的shell 看完<UNIX环境高级编程>后我就一直想写一个简单的shell来作为练习,因为有事断断续续的写了好几个月,如今写了差不多来总结一下. 源代码放在了Github: ...

  4. 一个简单的linux下设置定时执行shell脚本的示例

    很多时候我们有希望服务器定时去运行一个脚本来触发一个操作,比如说定时去备份服务器数据.数据库数据等 不适合人工经常做的一些操作这里简单说下 shell Shell俗称壳,类似于DOS下的command ...

  5. linux设备驱动第三篇:写一个简单的字符设备驱动

          在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分 ...

  6. linux设备驱动第三篇:如何写一个简单的字符设备驱动?

    在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存 ...

  7. 如何写一个简单的http服务器

    最近几天用C++写了一个简单的HTTP服务器,作为学习网络编程和Linux环境编程的练手项目,这篇文章记录我在写一个HTTP服务器过程中遇到的问题和学习到的知识. 服务器的源代码放在Github. H ...

  8. 《Linux内核分析》第三周 构建一个简单的Linux系统MenuOS

    [刘蔚然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK THREE ...

  9. 第三节 构造一个简单的Linux系统MenuOS——20135203齐岳

    第三节 构造一个简单的Linux系统MenuOS By 20135203齐岳 Linux内核源代码 arch/ 支持不同cpu的源代码 Documentations/ 文档存储 init/ 内核启动相 ...

随机推荐

  1. 第三篇 Scrum冲刺博客

    一.会议图片 二.项目进展 成员 完成情况 今日任务 冯荣新 商品列表,商品详情轮播图 商品底部工具栏,购物车列表 陈泽佳 历史足迹,静态页面 渲染搜索结果,防抖的实现 徐伟浩 未完成 商品信息录入 ...

  2. 如何为指定python解释器安装pip

    有时候我们通常会有很多python解释器,例如python2.python3.python(Anaconda). 参考链接:https://www.cnblogs.com/michaelcjl/p/1 ...

  3. akka-grpc - 基于akka-http和akka-streams的scala gRPC开发工具

    关于grpc,在前面的scalaPB讨论里已经做了详细的介绍:google gRPC是一种全新的RPC框架,在开源前一直是google内部使用的集成工具.gRPC支持通过http/2实现protobu ...

  4. 区块链入门到实战(23)之以太坊(Ethereum) – 虚拟机架构

    以太坊(Ethereum)网络中,定义了一组通用协议用于支持智能合约的运行,其核心便是以太坊(Ethereum)虚拟机. 下图解释了该架构: 开发人员使用Solidity等开发语言开发智能合约 源程序 ...

  5. PL/SQL语言基础

    PL/SQL语言基础 进行PL/SQL编程前,要打开输出set serveroutput on 1.创建一个匿名PL/SQL块,将下列字符输出到屏幕:"My PL/SQL Block Wor ...

  6. Python趣味入门5:循环语句while

    跟着小牛叔,找准正确编程入门姿势,每天只要阅读10分钟. 任何语言都有循环语句,在Python里循环更是变化无穷,有基本的循环,有循环else语句,引伸出来的还有迭代器.推导式,咱们先学习最简单的一种 ...

  7. OGG复制同步,提示字段长度不够ORA-01704

    日常运维OGG的环境中,如果遇到复制进程报错,提示字段长度不足如何处理??? 正常情况下,字段长度不足,但是未达到Oracle的限制时,可以对字段进行扩大限制满足目的. 实际环境中,遇到源端GBK,目 ...

  8. Windows 远程桌面鼠标光标不可见

    一.问题描述 通过在云端的主机上部署 frp 服务,实现「使用Windows 远程桌面(RDP)从互联网侧访问内网的主机」.但是,使用 Windows 自带的远程桌面工具 RDP 连接到另一台计算机时 ...

  9. Alink漫谈(二十) :卡方检验源码解析

    Alink漫谈(二十) :卡方检验源码解析 目录 Alink漫谈(二十) :卡方检验源码解析 0x00 摘要 0x01 背景概念 1.1 假设检验 1.2 H0和H1是什么? 1.3 P值 (P-va ...

  10. 初学WebGL引擎-BabylonJS:第4篇-灯光动画与丛林场景

    前几章接触的案例都是接近静态的,由这张开始开始接触大量动态的内容,包括 球体灯光,变动的形体,以及一个虚拟的丛林场景 下章我会试着结合1-9案例的内容做出一个demo出来 [playground]-l ...