zoukankan      html  css  js  c++  java
  • linux系统编程综合练习-实现一个小型的shell程序(四)

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

    编译运行:

    好了,关于内部命令的具体实现这里就不多说了,主要是还是实现其原理,这样自己的一个小型的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----做内部命令的执行【只实现其原理】
     
  • 相关阅读:
    【林】Ubuntu下安装和设置 OpenSSH Server
    吐吐槽
    【不定时推荐】这些年读过的书第一本--《一个人的朝圣》
    source insight 和keil 编辑对齐
    WeifenLuo DockContent停靠窗口的大小设置
    指针无法保存值
    php 练习基础
    php 写日志
    数据库设计——字段类型设计
    .net用TreeView实现CheckBoxes全选 反选 子节选中 传值
  • 原文地址:https://www.cnblogs.com/webor2006/p/3888463.html
Copyright © 2011-2022 走看看