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

转载于:https://www.cnblogs.com/webor2006/p/3888463.html

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

  1. Linux系统编程30:进程信号之产生信号的四种方式(Core Dump,kill,raise)

    文章目录 (1)通过按键产生信号-Core Dump (2)调用系统函数向进程发送信号 A:kill B:raise C:abort (3)由软件条件产生信号 (4)硬件异常产生信号 总结: 为了方便 ...

  2. linux系统发送信号的系统调用是,linux系统编程之信号:信号发送函数sigqueue和信号安装函数sigaction...

    信号发送函数sigqueue和信号安装函数sigaction sigaction函数用于改变进程接收到特定信号后的行为. sigqueue()是比较新的发送信号系统调用,主要是针对实时信号提出的(当然 ...

  3. Linux系统编程(一)

    Linux系统编程(一) 一.进程和程序 二.内存布局 内核空间 用户空间 三.进程状态 四.环境变量 五.进程共享 一.进程和程序 程序:是指编译好的二进制文件,存储在磁盘中,不占用系统资源. 进程 ...

  4. linux原子过程,linux系统编程:IO读写过程的原子性操作实验

    所谓原子性操作指的是:内核保证某系统调用中的所有步骤(操作)作为独立操作而一次性加以执行,其间不会被其他进程或线程所中断. 举个通俗点的例子:你和女朋友OOXX的时候,突然来了个电话,势必会打断你们高 ...

  5. 资深程序员带你攻克 Linux 系统编程

    作者简介:宇文拓,近十年 Linux C/C++ 开发经验,现就职于某创业公司,负责服务器架构与系统设计.曾就职于某通信业知名美企,负责核心网和防火墙产品研发.在 GitHub 上发布了开源项目 An ...

  6. 【README】Linux系统编程必读:本专栏内容提要以及系统调用接口总结

    文章目录 前言 第一部分:博客知识点 (1)基础篇 Linux系统编程1:Linux中使用率最高的一些命令 Linux系统编程2:详解Linux中的权限问题 Linux系统编程3:基础篇之详解Linu ...

  7. Linux系统编程25:基础IO之亲自实现一个动静态库

    本文接:Linux系统编程24:基础IO之在Linux下深刻理解C语言中的动静态库以及头文件和库的关系 文章目录 A:说明 B:实现静态库 C:实现动态库 A:说明 前面说过,库其实就是头文件和和.a ...

  8. 【Linux】Linux系统编程(入门与系统编程)(一)(环境搭建、常见指令以及权限理解)

    目录 linux系统编程 : 1.推动技术进步的基本模式 2.理解操作系统的发展 Linux 背景介绍 UNIX发展的历史: Linux发展历史 开源 Linux的发行版本: a.技术角度 b.商业化 ...

  9. linux 可定义信号数,Linux系统编程(20)——信号基本概念

    信号及信号来源 信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的.信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知 ...

最新文章

  1. 【百度地图API】——如何用label制作简易的房产标签
  2. 滑动门和翻转门实现的横竖双tab标签测试页面(附源码)
  3. 网站社区类产品管理经验
  4. python学习_数据处理编程实例(一)
  5. 语义分割和实例分割_一文读懂语义分割与实例分割
  6. 嵌入式无法使用QAudioDeviceInfo类
  7. 接口测试---mock变量自定义变量的使用
  8. DOM对象转化成jQuery对象 $(参数) (能不能查到jQuery对象的所有方法)
  9. 2021 年 338 道架构技术面试大厂高频题汇总(附答案详解)
  10. Java的GUI学习三(frame)
  11. C++ 读取txt文件方法读取速度比较
  12. 2021-05-22 Android 网络性能测试工具iperf详细使用方法
  13. go模板引擎生成html,goweb-模板引擎
  14. 【gp数据库】十条实用数据库SQL优化建议
  15. linux新硬盘格式化,linux添加新硬盘并格式化
  16. js在一个指定元素前添加内容_Day036-JS
  17. Android程序中重启系统,Android调用系统关机与重启功能
  18. ajax、promise、react、缓存笔记记录
  19. 解决IE11兼容HTML5 设置:设置兼容性视图网站正常显示网页
  20. 少儿Python编程6-计算机数据结构和算法

热门文章

  1. 低姿态生活,高境界做人
  2. 标准化工作及相关组织
  3. 频繁使用花呗、借呗、微粒贷、京东白条会影响在银行的信用吗?
  4. 基金一般拿多长时间合适?
  5. 有没有用逆向算法恢复马赛克的可能性?
  6. 本人24岁,女,现在是一所双非大学的大四本科生,被保研到了华中师范大学,应该去读吗?
  7. 男朋友让我纹他的名字,但我不想纹怎么办?
  8. 如何保持婚姻的新鲜感?
  9. 地板之间出现缝隙如何处理?
  10. 实体与电商,有啥区别?