小型命令解析器|minShell|多进程|重定向|进程控制【超详细的代码注释和解释】
前言
那么这里博主先安利一下一些干货满满的专栏啦!
这里包含了博主很多的数据结构学习上的总结,每一篇都是超级用心编写的,有兴趣的伙伴们都支持一下吧!手撕数据结构https://blog.csdn.net/yu_cblog/category_11490888.html?spm=1001.2014.3001.5482
https://blog.csdn.net/yu_cblog/category_11490888.html?spm=1001.2014.3001.5482
这里是STL源码剖析专栏,这个专栏将会持续更新STL各种容器的模拟实现。算法专栏https://blog.csdn.net/yu_cblog/category_11464817.html
https://blog.csdn.net/yu_cblog/category_11464817.html
什么是Shell
用百度百科的话讲:
在计算机科学中,Shell俗称壳(用来区别于核),是指“为使用者提供操作界面”的软件(command interpreter,命令解析器)。它类似于DOS下的COMMAND.COM和后来的cmd.exe。它接收用户命令,然后调用相应的应用程序(摘自百度百科)。
用通俗的话讲:
shell就是我们写命令的地方,其实它的本质也是一个进程,名为bash。
那么我们可以来模拟实现一下这个东西。
模拟实现代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
// shell运行原理: 通过让子进程执行命令,父进程等待和解析命令
// 为什么要子进程呢 -- 这样就算命令执行在子进程里面出错 -- 也不会影响父进程
// 因此我们不怕命令在命令行中出错
//
#define NUM 1024 // 定义一个大小为1024的缓冲区
char cmd_line[NUM]; //保存完整的命令行字符串
#define SIZE 32
char *g_argv[SIZE]; //保存拆分之后的命令行字符串
#define SEP " " //定义命令分隔符
#define INPUT_REDIR 1 //定义输入重定向
#define OUTPUT_REDIR 2 //定义输出重定向
#define APPEND_REDIR 3 //定义追加重定向
#define NONE_REDIR 0 //定义没有重定向
int redir_status = NONE_REDIR; //重定向类型
char* CheckRedir(char* start)
{
assert(start);
redir_status = NONE_REDIR;
char* end = start + strlen(start)-1;//abcd\0
//此时end是最后一个有效字符 -- 是\0的前一个
while(end>=start)
{
if(*end == '>')
{
//ls -a -l>myfile.txt
//ls -a -l>>myfile.txt
//所以我们在前面要定义一下
if(*(end-1) == '>')
{
//连续两个> ---- 追加重定向
redir_status = APPEND_REDIR;
*(end - 1) = '\0';
end++;
break;
}
//此时就是输出重定向
*end = '\0';
end++;
redir_status = OUTPUT_REDIR;
break;
}
else if(*end == '<')
{
//cat<myfile.txt 输入
redir_status = INPUT_REDIR;
*end = '\0';
end++;
break;
}
else
{
end--;
}
}
if(end >= start)
{
//提前break的
return end;//要打开的文件的地址
}
else
{
return NULL;
}
}
int main()
{
//获取全局的环境变量
extern char** environ;
//0. 命令行解释器,一定是一个常驻内存的进程 -- 不退出
while(1)
{
//1. 打印出提示信息 [yufc@localhost myshell]#
// 其实这一串是可以用系统接口来获取的 -- 不过我们不关心这些
printf("[yufc@localhost myshell]# ");
fflush(stdout);//手动刷新
//2. 获取用户的键盘输入[输入指的是各种指令和选项]
// 要输入 -- 我们就要提供一个输入的缓冲区
memset(cmd_line,'\0',sizeof cmd_line); //sizeof不是函数 -- 是运算符, 所以可以不用()
if(fgets(cmd_line,sizeof cmd_line, stdin) == NULL)
{
//表示没有在stdin里面获取命令时出错
continue;
}
//此时要把cmd_line最后面的回车去掉
cmd_line[strlen(cmd_line)-1] = '\0';
//printf("echo: %s\n",cmd_line);
//3. 拆分命令
//"ls -a -l" ---> "ls" "-a" "-l"
//strtok
//第一次调用 -- 要传入原始字符串
//如果还要继续解析原字符串 -- 传入NULL
//把重定向也添加进来
//ls -a -l>log.txt 今天我们先不去处理空格 -- 空格是特殊处理
//ls -a -l>>log.txt
//ls -a -l<log.txt
//分析是否有重定向
//ls -a -l>log.txt ---> ls -a -l\0log.txt
char* sep = CheckRedir(cmd_line);
// printf("%p\n",sep);
g_argv[0] = strtok(cmd_line,SEP);
int idx = 1;
//可以把ls的颜色加一下
if(strcmp(g_argv[0],"ls") == 0)
{
g_argv[idx++] = "--color=auto";
}
while(g_argv[idx++] = strtok(NULL,SEP)); //这种写法 -- 如果返回NULL,子串提取完成
//测试一下看看提取的对不对
//for(idx = 0;g_argv[idx];idx++)
//{
// printf("g_argv[%d]: %s\n",idx,g_argv[idx]);
//}
//4. TODO
if(strcmp(g_argv[0],"cd")==0)
{
//让父进程执行 -- 不要创建子进程
//内置命令(内建命令) -- 本质就是shell中的一个函数调用
//我们用一个系统调用 -- chdir
if(g_argv[1]!=NULL)chdir(g_argv[1]);
continue;
}
//导入环境变量
// if(strcmp(g_argv[0],"export") == 0 && g_argv[1]!=NULL)
// {
// int ret = putenv(g_argv[1]);
// if(ret == 0) printf("%s export success\n",g_argv[1]);
// continue;
// }
//5. fork()
pid_t id = fork();
printf("开始重定向2 %d\n",id);
if(id == 0) //child
{
if(sep != NULL)
{
int fd = -1;
//说明命令曾经有重定向
switch(redir_status)
{
case INPUT_REDIR:
fd = open(sep, O_RDONLY);
dup2(fd, 0);
break;
case OUTPUT_REDIR:
fd = open(sep, O_WRONLY | O_TRUNC | O_CREAT, 0666);
dup2(fd, 1);
break;
case APPEND_REDIR:
//TODO
fd = open(sep, O_WRONLY | O_APPEND | O_CREAT, 0666);
dup2(fd, 1);
break;
default:
printf("bug?\n");
break;
}
}
// printf("下面功能让子进程进行的\n");
// printf("child, MYVAL: %s\n", getenv("MYVAL"));
// printf("child, PATH: %s\n", getenv("PATH"));
//cd cmd , current child path
//execvpe(g_argv[0], g_argv, environ); // ls -a -l -i
//不是说好的程序替换会替换代码和数据吗??
//环境变量相关的数据,会被替换吗??没有!
execvp(g_argv[0], g_argv); // ls -a -l -i
exit(1);
}
//父进程 -- 这里我们不用else了 -- 子进程执行完直接退出, 后面的肯定是父进程了
int status = 0;
pid_t ret = waitpid(id,&status,0);
if(ret > 0)
{
printf("exit code: %d\n",WEXITSTATUS(status));
}
}
return 0;
}
执行效果
这份代码只是在原理层面简单实现了shell,至于其他个性化的颜色等设置,我们可以继续添加配置。
在代码的实现过程,还添加了一些重定向信息,大家在用的时候可以屏蔽这部分代码。
小型命令解析器|minShell|多进程|重定向|进程控制【超详细的代码注释和解释】的更多相关文章
- 【Linux】一步一步学Linux——初识Linux命令解析器(10)
目录 00. 目录 01. Shell简介 02. Shell分类 03. 交互式shell和非交互式shell 04. 登录shell和非登录shell 05. Shell类型 06. 参考 00. ...
- 邵国际: C 语言对象化设计实例 —— 命令解析器
本文系转载,著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 作者: 邵国际 来源: 微信公众号linux阅码场(id: linuxdev) 内容简介 单片机工程师常常疑惑为什么 ...
- Boost学习之语法解析器--Spirit
Boost.Spirit能使我们轻松地编写出一个简单脚本的语法解析器,它巧妙利用了元编程并重载了大量的C++操作符使得我们能够在C++里直接使用类似EBNF的语法构造出一个完整的语法解析器(同时也把C ...
- Appium Android Bootstrap源码分析之命令解析执行
通过上一篇文章<Appium Android Bootstrap源码分析之控件AndroidElement>我们知道了Appium从pc端发送过来的命令如果是控件相关的话,最终目标控件在b ...
- shell---命令解析器---简单操作快捷键
命令解析器---解析输入命令并执行操作 命令或目录补齐快捷键:TAB键 遍历史 输入: 小键盘:上 或 CTRL+P 小键盘:下 或 CTRL+N 光标移动: 小键盘:左 或 CTRL+B 小键盘:右 ...
- 10.AutoMapper 之自定义值解析器(Custom Value Resolvers)
https://www.jianshu.com/p/3e7cf1d1f17d 自定义值解析器(Custom Value Resolvers) 虽然AutoMapper涵盖了相当多的目标成员映射方案,但 ...
- 【vuejs深入三】vue源码解析之二 htmlParse解析器的实现
写在前面 一个好的架构需要经过血与火的历练,一个好的工程师需要经过无数项目的摧残. 昨天博主分析了一下在vue中,最为基础核心的api,parse函数,它的作用是将vue的模板字符串转换成ast,从而 ...
- 深入解析QML引擎, 第4部分: 自定义解析器
原文 QML Engine Internals, Part 4: Custom Parsers ——————————————————————————————————————————— 上一篇 绑定类型 ...
- Mybatis源码解析,一步一步从浅入深(三):实例化xml配置解析器(XMLConfigBuilder)
在上一篇文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码 ,中我们看到 代码:XMLConfigBuilder parser = new XMLConfigBuilder(read ...
- linux与Windows进程控制
进程管理控制 这里实现的是一个自定义timer用于统计子进程运行的时间.使用方式主要是 timer [-t seconds] command arguments 例如要统计ls的运行时间可以直接输入t ...
随机推荐
- d3条形图案例
- zzuli 1908
***做的时候判断当前位置为.的上下左右是否为*,如果全是改位置就改为*,如果四周中有为.,再DFS一下,其实就相当于把判断化为更小的子问题*** #include<iostream> # ...
- vim处理冲突文件
一.文件冲突前: 二.文件冲突后(默认为): 此时编辑文档,将before改为after但是异常退出了,此时编辑文档提示冲突: 回车进入展示编辑前的文档: ll-a可查看到隐藏文件的信息: 三.文件冲 ...
- MySQL 覆盖索引详解
本文转载自:MySQL 覆盖索引详解,作者 Sevn 1. 什么是索引? 索引(在 MySQL 中也叫"键key")是存储引擎快速找到记录的一种数据结构,通俗来说类似书本的目录. ...
- idea导入maven项目结构不全
本文为博主原创,转载请注明出处 将本地的项目导入idea中,其操作第一步为: File->open->选中导入maven项目的pom文件,正常情况通过该步骤项目就会导入到idea中. 通过 ...
- 2023年江苏“领航杯”MISC一个很有意思的题目(别把鸡蛋放在同一个篮子里面)
别把鸡蛋放在同一个篮子里面 题目附件:https://wwzl.lanzoue.com/i6HmX16finnc 1.题目信息 解压压缩包打开附件,获得5141个txt文档,每个文档都有内容,发现是b ...
- [转帖]oracle查询表变化量
根据变化量,可确定表的繁忙度,以及作为判断可能数据增长的对象. select obj.owner, obj.object_name, to_char(sn.BEGIN_INTERVAL_TIME,'y ...
- ZHS16GBK字符集下面Oracle数据库varchar与nvarchar的验证
ZHS16GBK字符集下面Oracle数据库varchar与nvarchar的验证 背景 周末分析了 SQLServer mysql等数据库 想着继续分析一下oracle数据库 这边oracle使用的 ...
- [转帖]docker(一):docker pull指定运行平台架构
https://zhuanlan.zhihu.com/p/539888862 1.概述 大家好,我是欧阳方超.某日要在服务器上部署docker服务,其中要用到nginx,nginx经过pull.sav ...
- [转帖]Intel AVX 系列指令基础介绍
https://zhuanlan.zhihu.com/p/437657452 一.发展背景 1993年,Intel公司推出了奔腾处理器,该类型处理器拥有两条执行流水线,和当时的处理器相比,可以同时执行 ...