Shell就是命令行的解释器,因为在Linux下,是没有图形界面的,我们需要通过命令行输入我们的命令,然后Shell将命令解析后反馈给Linux内核,内核运算处理后再讲结果通过Shell解析给用户。

我们输入的ls其实就是字符串,shell通过解析这个字符串,通过程序替换的方法将字符串ls替换为系统函数ls,完成操作,所以我们需要做的有一下几步。

  1. 获取输入的命令
  2. 解析输入的命令
  3. 创建子进程,通过子进程进行程序替换
  4. 父进程等待子进程退出

这里用到的知识都是前面几篇Linux进程概念和进程控制中写过的。

我们还可以再这个基础上,再添加例如重定向,管道,信号处理,后台作业,内置命令等功能。但是因为这里是实现一个简易的shell,所以就只添加了重定向的功能。

获取输入的命令

头文件

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<fcntl.h>
#define  MAX_CMD  1024
char buff[MAX_CMD];
//命令行的缓冲区大小1024个字节

首先我们要从终端获取数据,同时需要对终端进行模拟

int get_cmd()
{memset(buff, 0x00, MAX_CMD);printf("[lee@localhost ~]$ ");fflush(stdout);//模拟终端显示fgets(buff, MAX_CMD - 1, stdin);//输入buff[strlen(buff) - 1] = '\0';//最后一位是\0return 0;
}

解析输入的命令

我们需要将输入的字符串中的所有命令都保存在字符串数组argv中,以NULL结尾

char **do_parse(char *buff)
{char *ptr = buff;int argc = 0;static char *argv[32];//将所有提取出来的命令保存进argv中,因为在程序整个生命域中都要使用,       声明为静态while('\0' != *ptr){if(' ' != *ptr){argv[argc++] = ptr;while('\0' != *ptr && ' ' != *ptr){ptr++;}*ptr = '\0';}ptr++;}//去掉空格,提取命令argv[argc] = NULL;//最后一位必须是NULLreturn argv;
}

输出重定向

这里实现的主要是输出重定向中的>和>>

> 重定向输出符号,覆盖原来的内容并写入
>>重定向输出符号,追加写入

void do_redirect(char *buff)
{int redirect_flag = 0;//记录>出现的次数char *redirect_file = NULL;char *ptr = buff;while(*ptr != '\0'){if(*ptr == '>'){*ptr++ = '\0';++redirect_flag;//如果这一位是>,则计数加一,并将这一位改写为\0,防止被当成命令解析if(*ptr == '>'){++redirect_flag;ptr++;}while(*ptr == ' ' && *ptr != '\0'){ptr++;}redirect_file = ptr;//检测完>>之后,后面的就是重定向的文件名,将其解析出来while(*ptr != ' ' && *ptr != '\0'){ptr++;}             *ptr = '\0';}ptr++;}if(redirect_flag == 1){int fd = open(redirect_file, O_WRONLY|O_CREAT|O_TRUNC, 0664);dup2(fd, 1);//将标准输出重定向到文件//如果只出现了一个>,则说明是覆盖重定向,以O_TRUNC覆盖模式打开}else if(redirect_flag == 2){int fd = open(redirect_file, O_WRONLY|O_CREAT|O_APPEND, 0664);dup2(fd, 1);//>出现了两次,说明是追加重定向,以O_APPEND追加模式打开}
}

程序替换

int do_exec(char *buff)
{char **argv ={ NULL };int pid = fork();//创建子进程, 在子进程中进行程序替换if(0 == pid){    do_redirect(buff);//重定向argv = do_parse(buff);//解析命令if(NULL != argv[0]){execvp(argv[0], argv);//替换进程}else{exit(-1);//输入命令错误则退出进程}          }   else{waitpid(pid, NULL, 0);//等待子进程退出,防止僵尸进程}    return 0;
}

接下来调用就可以

int main(int argc, char*argv[])
{while(1){//如果命令输入正确则启用程序替换。if(!get_cmd())do_exec(buff);}return 0;
}

为了能够以后更好的管理和修改,写一个makefile

minishell:minishell.cgcc $^ -o $@

下面来验证一下

基本的命令都可以解析

再试一下输出重定向

可以看到,两种重定向都可以正常运作。

管道,信号处理,后台作业,内置命令等功能的话可以再这个shell的基础上再进行修改,但这里只是实现一个简单的命令行解释器,所以就先实现这些简易的功能。

代码非常简单,总共也就100多行
完整代码

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<fcntl.h>
#define  MAX_CMD  1024char buff[MAX_CMD];//1.获取命令
int get_cmd()
{memset(buff, 0x00, MAX_CMD);printf("[lee@localhost ~]$ ");fflush(stdout);//模拟终端显示fgets(buff, MAX_CMD - 1, stdin);buff[strlen(buff) - 1] = '\0';return 0;}//2.解析命令
char **do_parse(char *buff)
{char *ptr = buff;int argc = 0;static char *argv[32];while('\0' != *ptr){if(' ' != *ptr){argv[argc++] = ptr;while('\0' != *ptr && ' ' != *ptr){ptr++;}*ptr = '\0';}ptr++;}//去掉空格,提取命令argv[argc] = NULL;return argv;
}void do_redirect(char *buff)
{int redirect_flag = 0;char *redirect_file = NULL;char *ptr = buff;while(*ptr != '\0'){if(*ptr == '>'){*ptr++ = '\0';++redirect_flag;if(*ptr == '>'){++redirect_flag;ptr++;}while(*ptr == ' ' && *ptr != '\0'){ptr++;}redirect_file = ptr;while(*ptr != ' ' && *ptr != '\0'){ptr++;}*ptr = '\0';}ptr++;}if(redirect_flag == 1){int fd = open(redirect_file, O_WRONLY|O_CREAT|O_TRUNC, 0664);dup2(fd, 1);}else if(redirect_flag == 2){int fd = open(redirect_file, O_WRONLY|O_CREAT|O_APPEND, 0664);dup2(fd, 1);}
}//4.程序替换
int do_exec(char *buff)
{char **argv ={ NULL };int pid = fork();//创建子进程, 在子进程中进行程序替换if(0 == pid){    do_redirect(buff);argv = do_parse(buff);if(NULL != argv[0]){execvp(argv[0], argv);//替换进程}else{exit(-1);//输入命令错误则退出进程}}    else{waitpid(pid, NULL, 0);//等待子进程退出}return 0;
}int main(int argc, char*argv[])
{while(1){if(!get_cmd())do_exec(buff);}return 0;
}

C语言实现miniShell相关推荐

  1. C语言缓冲区与重定向

    目录 什么是缓冲区? 刷新策略 模拟实现重定向 标准输出和标准错误有什么区别? 上文提到关闭1号文件(标准输出文件),根据文件描述符分配规则,再打开的文件的描述符就是1,看以下代码: 运行代码后,前1 ...

  2. Go语言的错误异常处理机制及其应用

    一.背景 在日常编写golang程序或阅读别人的golang代码时,我们总会看到如下的一堆代码块: xx, err = func(xx) if err != nil {//do sth. to tac ...

  3. Go 知识点(19)— Go 语言中的野指针

    野指针是一种指向内存位置是不可知的指针,一般是由于指针变量在声明时没有初始化所导致的.在 Go语言中,布尔类型的零值为 false,数值类型的零值为 0,字符串类型的零值为 "", ...

  4. gcc 自动识别的文件扩展名,gcc/g++ -x 选项指定语言,不同 gcc 版本 -std 编译选项支持列表

    对于执行 C 或者 C++ 程序,需要借助 gcc(g++)指令来调用 GCC 编译器. 对于以 .c 为扩展名的文件,GCC 会自动将其视为 C 源代码文件 对于以 .cpp 为扩展名的文件,GCC ...

  5. OpenCV 笔记(07)— Mat 对象输出格式设置(Python 格式、CSV 格式、NumPy 格式、C 语言格式)

    首先是下面代码中将要使用的 r 矩阵的定义.需要注意,我们可以通过用 randu 函数产生的随机值来填充矩阵, 需要给定一个上限和下限来确保随机值在期望的范围内. Mat r = Mat(2, 3, ...

  6. 利用牛顿法求平方根-Go语言实现

    牛顿法解释 百度的解释如下: 通俗的解释就是:多数方程不存在求根公式,牛顿提出了一种用迭代来求方程近似根的方法.思路就是不断取切线,用线性方程的根逼近非线性方程f(x)=0f(x)=0f(x)=0的根 ...

  7. 翻转二叉树 c语言实现 递归 栈 队列

    前言 题目比较好理解,就是翻转二叉树 代码 c语言实现 #include<stdio.h> #include<stdlib.h> #include<string.h> ...

  8. 字符串全排列的问题 python和c语言实现

    前言 这是一个的经典的问题 设计一个算法,输出一个字符串字符的全排列. 比如,String = "abc" 输出是"abc","bac",& ...

  9. 快速排序的递归和非递归实现 c语言版本

    代码 挖坑法 解释 选取一个关键字(key)作为枢轴,一般取整组记录的第一个数/最后一个,这里采用选取序列第一个数为枢轴,也是初始的坑位. 设置两个变量i = l;j = r;其中l = 0, r = ...

最新文章

  1. MySQL单表数据量过千万,采坑优化记录,完美解决方案
  2. 标记化结构初始化语法(C语言)
  3. java 打印一棵树_java编程题之从上往下打印出二叉树
  4. 敏捷冲刺每日报告一(Java-Team)
  5. .NET MVC访问某方法后会跳转页面
  6. Avg_row_length是怎么计算的?
  7. 把字符串变为变量_python学习第10课--列表和字符串的可变性
  8. 首批共享单车死于 2019
  9. Intel的AVX2指令集解读
  10. Java对MySql数据库进行备份与还原
  11. 洛谷P2038 无线网络发射器选址 水题 枚举
  12. dispimg函数怎么用_excel中的lookup函数究竟该怎么用?如何才能准确理解它的用法?...
  13. 企业微信H5开发使用微信开发者工具
  14. 全球IP地址分配对应表
  15. UOJ220 [NOI2016] 网格 【割顶】【并查集】
  16. C盘扩容-Win10
  17. mysql 父子排序_mysql 父子结构排序
  18. 公众号如何向用户发送重要的服务通知?
  19. MIPI_DSI协议简要介绍
  20. 挖矿木马应急响应指南

热门文章

  1. oracle解除死锁
  2. linux ctime 时间戳,关于LINUX三种时间戳的详细说明(带实验)
  3. mysql 交集_MYSQL交集函数
  4. php数组格式化显示,php 打印数组格式化显示
  5. Spring注解编程基石(三)
  6. koa2 mysql增删改查_react+koa2+mysql零门槛的全栈体验,附上完整项目分享
  7. 分析工厂模式中的问题并改造
  8. 构建增强现实移动应用程序的六款顶级工具
  9. vector的简单实现
  10. 数据结构-线性表(栈与队列的特殊性)