進程間通信之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 == '\n')
            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('\n');
    exit(0);
}

因為標准輸出通常是行緩沖的,而提示符並不包括換行符,所以在寫了提示之后,需要調用fflush。

本篇博文內容摘自《UNIX環境高級編程》(第二版),僅作個人學習記錄所用。關於本書可參考:http://www.apuebook.com/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM