zoukankan      html  css  js  c++  java
  • 进程间通信之popen和pclose函数

    常见的操作是创建一个管道连接到另一个进程,然后读其输出或向其输入端发送数据,为此,标准I/O库提供了两个函数popen和pclose。这两个函数实现的操作是:创建一个管道,调用fork产生一个子进程,关闭管道的不使用端,执行一个shell以运行命令,然后等待命令终止

    #include <stdio.h>
    
    FILE *popen(const char *cmdstring, const char *type);
    返回值:若成功则返回文件指针,若出错则返回NULL
    
    int pclose(FILE *fp);
    返回值:cmdstring的终止状态,若出错则返回-1

    函数popen先执行fork,然后调用exec以执行cmdstring,并且返回一个标准I/O文件指针。如果type是“r”,则文件指针连接到cmdstring的标准输出(见图15-5)。

    未命名

        fp相当于管道的fd[0], stdout相当于管道的fd[1].

        图15-5 执行fp = popen(cmdstring, “r”)函数的结果

    如果type是“w”,则文件指针连接到cmdstring的标准输入(见图15-6)。

    未命名

          fp相当于管道的fd[1], stdin相当于管道的fd[0].

          图15-6 执行fp = popen(cmdstring, “w”)函数的结果

    pclose函数关闭标准I/O流,等待命令执行结束,然后返回shell的终止状态。(我们曾在http://www.cnblogs.com/nufangrensheng/p/3510101.html对终止状态进行过说明,system函数(http://www.cnblogs.com/nufangrensheng/p/3512291.html)也返回终止状态。)如果shell不能被执行,则pclose返回的终止状态与shell已执行exit(127)一样。

    cmdstring由Bourne shell以下列方式执行:

    sh -c cmdstring

    这表示shell将扩展cmdstring中的任何特殊字符。 例如,可以使用:

    fp = popen("ls *.c", "r");
    或者
    fp = popen("cmd 2>&1", "r");

    实例

      程序清单15-4 用popen向分页程序传送文件

    #include "apue.h"
    #include <sys/wait.h>
    
    #define PAGER    "${PAGER:-more}"    /* environment variable, or default */
    
    int
    main(int argc, char *argv[])
    {
        char    line[MAXLINE];
        FILE    *fpin, *fpout;
    
        if(argc != 2)
            err_quit("usage: a.out <pathname>");
        if((fpin = fopen(argv[1], "r")) == NULL)
            err_sys("can't open %s", argv[1]);
    
        if((fpout = popen(PAGER, "w")) == NULL)
            err_sys("popen error");
    
        /* copy argv[1] to pager */
        while(fgets(line, MAXLINE, fpin) != NULL)
        {
            if(fputs(line, fpout) == EOF)
                err_sys("fputs error to pipe");
        }
        if(ferror(fpin))
            err_sys("fgets error");
        if(pclose(fpout) == -1)
            err_sys("pclose error");
    
        exit(0);
    }

    使用popen减少了需要编写的代码量。

    shell命令${PAGER:-more}的意思是:如果shell变量PAGER已经定义,且其值非空,则使用其值,否则使用字符串more。

    实例:popen和pclose函数

    程序清单15-5是我们编写的popen和pclose版本。

    程序清单15-5 popen和pclose函数

    #include "apue.h"
    #include <errno.h>
    #include <fcntl.h>
    #include <sys/wait.h>
    
    /*
    * Pointer to array allocated at run-time.
    */
    static pid_t    *childpid = NULL;
    
    /*
    * From our open_max(), open_max()函数见http://www.cnblogs.com/nufangrensheng/p/3496323.html中的程序清单2-4。
    */
    static int maxfd;
    
    FILE *
    popen(const char *cmdstring, const char *type)
    {
        int      i;
        int      pfd[2];
        pid_t    pid;
        FILE    *fp;
        
        /* only allow "r" or "w" */
        if((type[0] != 'r' &&  type[0] != 'w') || type[1] != 0)
        {
            errno = EINVAL;    /* required by POSIX */
            return(NULL);
        }
        
        if(childpid == NULL)    /* first time through */
        {
            /* allocate zerod out array for child pids */
            maxfd = open_max();
            if((childpid = calloc(maxfd, sizeof(pid_t))) == NULL)
                return(NULL);
        }
        
        if(pipe(pfd) < 0)
            return(NULL);    /* errno set by pipe() */
    
    
        if((pid = fork()) < 0)
        {
            return(NULL);    /* error set by fork() */
        }
        else if(pid == 0)
        {
            if(*type == 'r')
            {
                close(pfd[0]);
                if(pfd[1] != STDOUT_FILENO)
                {
                    dup2(pfd[1], STDOUT_FILENO);
                    close(pfd[1]);    
                }
            }
            else
            {
                close(pfd[1]);
                if(pfd[0] != STDIN_FILENO)
                {
                    dup2(pfd[0], STDIN_FILENO);
                    close(pfd[0]);
                }
            }
            
            /* close all descriptors in childpid[] */
            for(i=0; i < maxfd; i++)
                if(childpid[i] > 0)
                    close(i);
    
            execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
            _exit(127);
        }
    
        /* parent continues... */
        if(*type == 'r')
        {
            close(pfd[1]);
            if((fp = fdopen(pfd[0], type)) == NULL)
                return(NULL);
        }
        else
        {
            close(pfd[0]);
            if((fp = fdopen(pfd[1], type)) == NULL)
                return(NULL);
        }
        
        childpid[fileno(fp)] = pid;    /* remeber child pid for this fd */
        return(fp);
    }
    
    int
    pclose(FILE *fp)
    {
        int      fd, stat;
        pid_t    pid;
    
        if(childpid == NULL)
        {
            errno = EINVAL;
            return(-1);    /* popen() has never been called */
        }
        
        fd = fileno(fp);
        if((pid = childpid[fd]) = 0)
        {
            errno = EINVAL;
            return(-1);    /*  fp wasn't opened by popen() */
        }
    
        childpid[fd] = 0;
        if(fclose(fp) == EOF)
            return(-1);
    
        while(waitpid(pid, &stat, 0) < 0)
            if(errno != EINTR)
                return(-1);    /* error other than EINTR from waitpid() */
    
        return(stat);        /* return child's termination status */
    }

    这里有许多需要考虑的细节:首先,每次调用popen时,应当记住所创建的子进程的进程ID,以及其文件描述符或FILE指针。我们选择在数组childpid中保存子进程ID,并用文件描述符作为其下标。于是,当以FILE指针作为参数调用pclose时,我们调用标准I/O函数fileno得到文件描述符,然后取得子进程ID,并用其作为参数调用waitpid。因为一个进程可能调用popen多次,所以在动态分配childpid数组时(第一次调用popen时),其数组长度应当是最大文件描述符数,于是该数组中可以存放与最大文件描述符数相同的子进程。

    POSIX.1要求子进程 关闭在之前调用popen时打开且当前仍旧打开的所有I/O流。为此,在子进程中从头逐个检查childpid数组的各元素,关闭仍旧打开的任何描述符。

    若pclose的调用者已经为信号SIGCHLD设置了一个信号处理程序,则pclose中的waitpid调用将返回一个EINTR。因为允许调用者捕捉此信号(或者任何其他可能中断waitpid调用的信号),所以当waitpid被一个捕捉到的信号中断时,我们只是再次调用waitpid。

    注意,如果应用程序调用waitpid,并且获得popen所创建的子进程的终止状态,则在应用程序调用pclose时,其中将调用waitpid,它发现子进程已不再存在,此时返回-1,errno被设置为ECHILD。

    注意,popen绝不应由设置用户ID或设置用户组ID程序调用。当它执行命令时,popen等同于:

    execl("/bin/sh", "sh", "-c", command, NULL);

    它在从调用者继承的环境中执行shell,并由shell解释执行command。一个心怀不轨的用户可以操纵这种环境,使得shell能以设置ID文件模式所授予的提升了的权限以及非预期的方式执行命令。

    popen特别适用于构造简单的过滤器程序,它变换运行命令的输入或输出。当命令希望构造它自己的管道线时,就是这种情形。

    实例

    考虑一个应用程序,它向标准输出写一个提示,然后从标准输入读1行。使用popen,可以在应用程序和输入之间插入一个程序以便对输入进行变换处理。图15-7显示了为此做的进程安排。

    未命名

                                图15-7 用popen对输入进行变换处理

    对输入进行的变化可能是路径名扩充,或者是提供一种历史机制(记住以前输入的命令)。

    程序清单15-6是一个简单的过滤程序,它只是将标准输入复制到标准输出,在复制时,将所有大写字符变换为小写字符。在写了一行以后,对标准输出进行了冲洗(用fflush),其理由可参考进程间通信之协同进程。

    程序清单15-6 将大写字符转换成小写字符的过滤程序

    #include "apue.h"
    #include <ctype.h>
    
    int
    main(void)
    {
        int c;
        
        while((c = getchar()) != EOF)
        {
            if(isupper(c))
                c = tolower(c);
            if(putchar(c) == EOF)
                err_sys("output error");
            if(c == '
    ')
                fflush(stdout);
        }
        exit(0);
    }

    对该过滤程序进行编译,其可执行目标代码放在文件myuclc中(也就是编译后的可执行文件名为myuclc),然后在程序清单15-7中用popen调用它们。

    程序清单15-7 调用大写/小写过滤程序以读取命令

    #include "apue.h"
    #include <sys/wait.h>
    
    int
    main(void)
    {
        char    line[MAXLINE];
        FILE    *fpin;
    
        if((fpin = popen("/home/zhu/apue/myuclc", "r")) == NULL)
            err_sys("popen error");
        for(;;)
        {
            fputs("prompt> ", stdout);
            fflush(stdout);
            if(fgets(line, MAXLINE, fpin) == NULL)    /* read from pipe */
                break;
            if(fputs(line, stdout) == EOF)
                err_sys("fputs error to pipe");
        }
        if(pclose(fpin) == -1)
            err_sys("pclose error");
        putchar('
    ');
        exit(0);
    }

    因为标准输出通常是行缓冲的,而提示符并不包括换行符,所以在写了提示之后,需要调用fflush。

    本篇博文内容摘自《UNIX环境高级编程》(第二版),仅作个人学习记录所用。关于本书可参考:http://www.apuebook.com/

  • 相关阅读:
    X-Plosives (并查集)
    HDU1272小希的迷宫 (并查集)
    React 初学
    js 插件 issue
    js常用方法
    常用网址
    js 零零散散的总结。
    git 常用命令
    es6 babel编译
    屏幕适配
  • 原文地址:https://www.cnblogs.com/nufangrensheng/p/3561190.html
Copyright © 2011-2022 走看看