上节中已经对后台作业进行了简单处理,基本上要实现的功能已经完了,下面回过头来,对代码进行一个调整,把写得不好的地方梳理一下,给代码加入适当的注释,这种习惯其实是比较好了,由于在开发的时候时间都比较紧,都只是想办法去尽快实现,而肯定会有一些代码是写得不太好的,所以有时间的话最好是从头至尾将整个代码进行梳理,也许在梳理的过程中会发现许多不足的地方,好了,下面开始:
而这个信号安装函数是在init.c中实现的:
接下来进行shell循环:
它的实现是在parse.c中:
如注释所示,可以挪至init.c中:
接下来,获取命令:
然后解析命令:
接下来的这句,是为了测试,在发布时可以注释掉了:
最后执行命令:
这个方法里面的代码有点乱,下面将其实现抽取到另外一个文件中,使得该函数要看起来清爽一些:
其实现execute.c:
#include "execute.h"
#include "def.h"
#include "externs.h"
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <linux/limits.h>
#include <fcntl.h> void forkexec(int i){
pid_t pid;
pid = fork();
if(pid == -) {
/* 创建进程失败了 */
ERR_EXIT("fork");
} if(pid > ) {
/* 父进程 */
if (backgnd == )
printf("%d\n", pid);
lastpid = pid;
} else if(pid == ) {
/* 子进程 */ /* 表示将第一条简单命令的infd重定向至/dev/null,其中cmd[i].infd == 0只有可能是第一条简单命令 */
/* 当第一条命令试图从标准输入获取数据的时候立既返回EOF */
if(cmd[i].infd == && backgnd == ){
//屏蔽后台作业,因为没有实现作业控制
cmd[i].infd = open("/dev/null", O_RDONLY);
} /* 将第一个简单命令进程作为进程组组长 */
if(i == ){
setpgid(, );
}
if(cmd[i].infd != ){
//说明该命令的输入是指向管道的读端
close();
dup(cmd[i].infd);
}
if(cmd[i].outfd != ){
//说明该命令的输出指向的是管道的写端
close();
dup(cmd[i].outfd);
}
/* 关闭3以上的所有文件描述符 */
/*int i;
for(i=3; i<OPEN_MAX; ++i){
close(i);
}*/ /*前台作业能够接收SIGINT,SIGQUIT信号,这两个信号就要恢复成默认操作*/
if(backgnd == ){//非后台作业
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
} /* 开始替换进程 */
execvp(cmd[i].args[], cmd[i].args);
/* 如果执行到这句,则证明替换失败了 */
exit(EXIT_FAILURE);
}
} int execute_disk_command(void){
/* ls | grep init | wc -w */
if(cmd_count == ) {
return ;
}
if(infile[] != '\0'){
cmd[].infd = open(infile, O_RDONLY);
} if(outfile[] != '\0'){
if(append)//说明是以追加的方式
cmd[cmd_count-].outfd = open(outfile, O_WRONLY | O_CREAT | O_APPEND, );
else
cmd[cmd_count-].outfd = open(outfile, O_WRONLY | O_CREAT | O_TRUNC, );
} /* 因为后台作业不会调用wait等待子进程退出,为避免僵尸进程,可以忽略SIGCHLD信号 */
if(backgnd == ){
signal(SIGCHLD, SIG_IGN);
}else{
signal(SIGCHLD, SIG_DFL);
}
int i;
/* 管道描述符 */
int fds[];
int fd;
for(i=; i<cmd_count; ++i){
/* 如果不是最后一条命令,则需要创建管道 */
if(i < cmd_count-){
pipe(fds);
/* 第一条命令的输出不再是标准输出,而是管道的写端 */
cmd[i].outfd = fds[];
/* 第二条命令的输入不再是标准输入,而是管道的读端 */
cmd[i+].infd = fds[];
} /* 创建一个进程,并且替换成系统命令 */
forkexec(i); if((fd = cmd[i].infd) != )
close(fd);
if((fd = cmd[i].outfd) != )
close(fd);
} if(backgnd == ){//如果是非后台作业
while(wait(NULL) != lastpid)
;
}
}

将其forkexec函数也抽取到execute.c文件中,下面来进行编译一下:

另外在编译成,需要修改一下Makefile:

修改这么多后下面编译一下:
好了,对于上面的实现都解释的外部命令,那对于系统的内部命令还需要兼容,下面主要是来实现内部命令的解析:
首先要判断是否是内部命令,如果是,则执行内部命令:
另外需要将builtin.h包含在parse.c文件中:
下面来编译一下:
说明忘了将builtin.o文件加入到Makefile中了,修改并再编译:
一大堆错,不用着急,一个个解决,首先builtin.c文件中也需要用到check函数,而之前是定义在parse.c中,这时应该将其定义在parse.h中,让builtin.c来包含它既可,另外还需包含一些系统头文件:
在builtin.c中去包含parse.h文件:
下面再来make并执行:
思考一个问题:系统有大量的内部命令,那是不是每解析一个内部命令,我们都要在builtin.c中加一个判断语句,这样会造成builtin函数会越来越庞大,所以这种实现方式还是不太灵活,下面改用数组来避免这种情况的发生:
最后builtin.c的代码如下:
#include "builtin.h"
#include "parse.h"
#include "externs.h"
#include <stdlib.h>
#include <stdio.h> typedef void (*CMD_HANDLER)(void); typedef struct builtin_cmd
{
char *name;
CMD_HANDLER handler; } BUILTIN_CMD; void do_exit(void);
void do_cd(void);
void do_type(void); BUILTIN_CMD builtins[] =
{
{"exit", do_exit},
{"cd", do_cd},
{"type", do_type},
{NULL, NULL}
}; /*
* 内部命令解析
* 返回1表示为内部命令,0表示不是内部命令
*/
int builtin(void)
{
/*
if (check("exit"))
do_exit();
else if (check("cd"))
do_cd();
else
return 0; return 1;
*/ int i = ;
int found = ;
while (builtins[i].name != NULL)
{
if (check(builtins[i].name))
{
builtins[i].handler();
found = ;
break;
}
i++;
} return found;
} void do_exit(void)
{
printf("exit\n");
exit(EXIT_SUCCESS);
} void do_cd(void)
{
printf("do_cd ... \n");
} void do_type(void)
{
printf("do_type ... \n");
}

编译运行:

好了,关于内部命令的具体实现这里就不多说了,主要是还是实现其原理,这样自己的一个小型的shell程序就已经完成了,跟系统的shell程序还相差很多,但是通过这个程序足以可以将之前学的知识给串接起来, 达到一个很好的练习的目的,关于小型shell程序最终就实现到这,东西还是比较多,需好好消化。
【说明】:由于linux系统命令太多太多,下面只是简单实现几个,做一个范例,重点是知道其实现原理。
 
 
下面列一下该程序中使用到的各个文件的作用:
main.c----主调程序
def:h----定义常量,结构体
externs.h----定义extern的变量
init.h/init.c----做一些初始化操作
parse.h/parse.c----做命令的解析
execute.h/execute.c----做外部命令的执行
builtin.h/builtin.c----做内部命令的执行【只实现其原理】
 

linux系统编程综合练习-实现一个小型的shell程序(四)的更多相关文章

  1. linux系统编程综合练习-实现一个小型的shell程序(一)

    之前已经花了不少篇幅学习了linux系统编程的很多知识点:文件与io.进程.信号.管道,而零散的知识点,怎么能够综合的串接起来是学习的一个很重要的目的,当然最好的方式就是用所学的知识点做一个项目了,所 ...

  2. linux系统编程综合练习-实现一个小型的shell程序(三)

    上节中已经实现了对普通命令的解析,包括输入重定向,输出重定向,管道,后台作业,这次就来执行已经解析好的命令,对应的函数为:execute_command(),首先对带有管道的命令进行执行: 比如:&q ...

  3. linux系统编程综合练习-实现一个小型的shell程序(二)

    上节minishell当中,已经初步实现了一个简单命令的解析,这节来继续对更加复杂命令进行解析,包含:输入重定向的解析.管道行的解析.输出重定向的解析以及是否有后台作业的解析,如下: 下面对其进行实现 ...

  4. Linux系统学习笔记之 1 一个简单的shell程序

    不看笔记,长时间不用自己都忘了,还是得经常看看笔记啊. 一个简单的shell程序 shell结构 1.#!指定执行脚本的shell 2.#注释行 3.命令和控制结构 创建shell程序的步骤 第一步: ...

  5. Linux系统编程(33)—— socket编程之TCP程序的错误处理

    上一篇的例子不仅功能简单,而且简单到几乎没有什么错误处理,我们知道,系统调用不能保证每次都成功,必须进行出错处理,这样一方面可以保证程序逻辑正常,另一方面可以迅速得到故障信息. 为使错误处理的代码不影 ...

  6. linux系统编程之进程(一)

    今天起,开始学习linux系统编程中的另一个新的知识点----进程,在学习进程之前,有很多关于进程的概念需要了解,但是,概念是很枯燥的,也是让人很容易迷糊的,所以,先抛开这些抽象的概念,以实际编码来熟 ...

  7. linux系统编程之管道(三)

    今天继续研究管道的内容,这次主要是研究一下命名管道,以及与之前学过的匿名管道的区别,话不多说,进入正题: 所以说,我们要知道命名管道的作用,可以进行毫无关系的两个进程间进行通讯,这是匿名管道所无法实现 ...

  8. linux系统编程之信号(七)

    今天继续学习信号,主要是学习关于时间和定时器相关的函数的使用,关于这个实际上有很多内容,这里先简要进行说明,等之后再慢慢进行相关深入,也主要是为接下来要做的一个综合linux系统编程的例子做准备,好了 ...

  9. Linux系统编程【2】——编写who命令

    学到的知识点 通过实现who命令,学到了: 1.使用man命令寻找相关信息 2.基于文件编程 3.体会到c库函数与系统调用的不同 4.加深对缓冲技术的理解 who命令的作用 who命令的使用 在控制终 ...

随机推荐

  1. consul ACL 配置范例

    service "dashboard" { policy = "write" } service "dashboard-sidecar-proxy&q ...

  2. Apollo环境配置

    一.背景 Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境.不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限.流程治理等特性,适用于微服务配置管理 ...

  3. python json模块(15)

    JSON 的全称是 JavaScript Object Notation,即 JavaScript 对象符号,它是一种轻量级.跨平台.跨语言的数据交换格式,其设计意图是把所有事情都用设计的字符串来表示 ...

  4. LeetCode 238. 除自身以外数组的乘积(Product of Array Except Self)

    238. 除自身以外数组的乘积 238. Product of Array Except Self 题目描述 LeetCode LeetCode238. Product of Array Except ...

  5. [hdu 1062] Text Reverse | STL-stack

    原题 题目大意: t组数据,每组为一行,遇到空格时讲前面的单词反转输出. 题解: 显然的栈题,遇到空格时将当前栈输出清空即可 #include<cstdio> #include<st ...

  6. 前端 html篇

    web开发本质: html是一个标准,规定了大家怎么写网页 1.浏览器输入网址回车发生了什么事 1. 浏览器 给服务端 发送了一个消息2. 服务端拿到消息3. 服务端返回消息4. 浏览器展示页面 se ...

  7. Django连接多个数据库并实现读写分离

    当一个项目当中有大量的数据的时候,你所有的IO操作都在一个数据库中操作,会造成项目的性能的降低.如果你能对项目中的数据进行读写分离的话,那么将大大提高你项目的性能.而Django自带的机制也对此提供了 ...

  8. 【LEETCODE】51、数组分类,简单级别,题目:581,830,1010,665

    package y2019.Algorithm.array; /** * @ClassName FindUnsortedSubarray * @Description TODO 581. Shorte ...

  9. Keyboard Purchase CodeForces - 1238E (状压)

    大意: 给定串$s$, 字符集为字母表前$m$个字符, 求一个$m$排列$pos$, 使得$\sum\limits_{i=2}^n|{pos}_{s_{i-1}}-{pos}_{s_{i}}|$最小. ...

  10. C语言开发中常用英文缩写

    BIOS(Basic Input Output System): 基本输入输出系统 reference: https://baike.baidu.com/item/bios/91424?fr=alad ...