上节中已经对后台作业进行了简单处理,基本上要实现的功能已经完了,下面回过头来,对代码进行一个调整,把写得不好的地方梳理一下,给代码加入适当的注释,这种习惯其实是比较好了,由于在开发的时候时间都比较紧,都只是想办法去尽快实现,而肯定会有一些代码是写得不太好的,所以有时间的话最好是从头至尾将整个代码进行梳理,也许在梳理的过程中会发现许多不足的地方,好了,下面开始:
而这个信号安装函数是在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. [LeetCode] 502. IPO 上市

    Suppose LeetCode will start its IPO soon. In order to sell a good price of its shares to Venture Cap ...

  2. MAX10 ADC的一些基知识

    MAX10 ADC 的一些知识 1.        MAX 10 内部集成的12bit SAR ADC的特点为: a.        采样速率高达1Mhz. b.        模拟通道多达18个,单 ...

  3. 学习数据结构Day3

    栈和队列 栈是一种线性结构,相比数组 他对应的操作是数组的子集 只能从一端进入,也只能从一端取出 这一端成为栈顶 栈是一种先进后出的数据结构,Last In First Out(LIFO) 程序调用的 ...

  4. java lambda怎么表达式判断被调用接口名称和接口中方法

    1.首先能够用于lambda表达式的只能是interface,并且interface 中只有一个方法. 这就说明,只要找到接口类型就能确定用的是哪个方法.(如下:intTypeInterface.St ...

  5. pyhton数据类型:字典、集合、列表、元组

    基本常识 元组 列表 字典 集合 初始化 tuple=(1,2,3,4) list=[1,2,3,4] dic={'a':12,'b':34} set={1,2,3,4} 元素索引 tuple[0] ...

  6. lambda表达式笔记

    前几天一位好友分享了一篇文章,其中讲到了lambda表达式,正好最近看了一些内容,就做做笔记吧... lambda表达式服务于函数式接口,如果需要一个函数式接口的对象时,就可以用lambda表达式代替 ...

  7. Java开发笔记(一百三十二)Swing的表格

    前面介绍了程序界面上一些简单控件的组合排列,它们用来表达相互之间联系较弱的信息倒还凑合,要是用来表达关联性较强的聚合信息就力不从心了.倘若只是简单信息的罗列,例如商品名称列表.新闻标题列表.学生姓名列 ...

  8. CSS3 @font-face 规则

    指定名为"myFirstFont"的字体,并指定在哪里可以找到它的URL: @font-face { font-family: myFirstFont; src: url('San ...

  9. 微服务与SpringCloud简介

    A.官网 https://spring.io/projects/spring-cloud B.简介 Spring Cloud是一个基于Spring Boot实现的云应用开发工具,它为基于JVM的云应用 ...

  10. 学习笔记之MongoDB

    MongoDB - Wikipedia MongoDB is a cross-platform document-oriented database program. Classified as a  ...