|
常見的操作是創(chuàng)建一個管道連接到另一個進程,然后讀其輸出或向其輸入端發(fā)送數據,為此,標準I/O庫提供了兩個函數popen和pclose。這兩個函數實現(xiàn)的操作是:創(chuàng)建一個管道,調用fork產生一個子進程,關閉管道的不使用端,執(zhí)行一個shell以運行命令,然后等待命令終止。 #include <stdio.h> FILE *popen(const char *cmdstring, const char *type); 返回值:若成功則返回文件指針,若出錯則返回NULL int pclose(FILE *fp); 返回值:cmdstring的終止狀態(tài),若出錯則返回-1 函數popen先執(zhí)行fork,然后調用exec以執(zhí)行cmdstring,并且返回一個標準I/O文件指針。如果type是“r”,則文件指針連接到cmdstring的標準輸出(見圖15-5)。 fp相當于管道的fd[0], stdout相當于管道的fd[1]. 圖15-5 執(zhí)行fp = popen(cmdstring, “r”)函數的結果 如果type是“w”,則文件指針連接到cmdstring的標準輸入(見圖15-6)。 fp相當于管道的fd[1], stdin相當于管道的fd[0]. 圖15-6 執(zhí)行fp = popen(cmdstring, “w”)函數的結果 pclose函數關閉標準I/O流,等待命令執(zhí)行結束,然后返回shell的終止狀態(tài)。(我們曾在http://www.cnblogs.com/nufangrensheng/p/3510101.html對終止狀態(tài)進行過說明,system函數(http://www.cnblogs.com/nufangrensheng/p/3512291.html)也返回終止狀態(tài)。)如果shell不能被執(zhí)行,則pclose返回的終止狀態(tài)與shell已執(zhí)行exit(127)一樣。 cmdstring由Bourne shell以下列方式執(zhí)行: 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 */ } 這里有許多需要考慮的細節(jié):首先,每次調用popen時,應當記住所創(chuàng)建的子進程的進程ID,以及其文件描述符或FILE指針。我們選擇在數組childpid中保存子進程ID,并用文件描述符作為其下標。于是,當以FILE指針作為參數調用pclose時,我們調用標準I/O函數fileno得到文件描述符,然后取得子進程ID,并用其作為參數調用waitpid。因為一個進程可能調用popen多次,所以在動態(tài)分配childpid數組時(第一次調用popen時),其數組長度應當是最大文件描述符數,于是該數組中可以存放與最大文件描述符數相同的子進程。 POSIX.1要求子進程 關閉在之前調用popen時打開且當前仍舊打開的所有I/O流。為此,在子進程中從頭逐個檢查childpid數組的各元素,關閉仍舊打開的任何描述符。 若pclose的調用者已經為信號SIGCHLD設置了一個信號處理程序,則pclose中的waitpid調用將返回一個EINTR。因為允許調用者捕捉此信號(或者任何其他可能中斷waitpid調用的信號),所以當waitpid被一個捕捉到的信號中斷時,我們只是再次調用waitpid。 注意,如果應用程序調用waitpid,并且獲得popen所創(chuàng)建的子進程的終止狀態(tài),則在應用程序調用pclose時,其中將調用waitpid,它發(fā)現(xiàn)子進程已不再存在,此時返回-1,errno被設置為ECHILD。 注意,popen絕不應由設置用戶ID或設置用戶組ID程序調用。當它執(zhí)行命令時,popen等同于: execl("/bin/sh", "sh", "-c", command, NULL); 它在從調用者繼承的環(huán)境中執(zhí)行shell,并由shell解釋執(zhí)行command。一個心懷不軌的用戶可以操縱這種環(huán)境,使得shell能以設置ID文件模式所授予的提升了的權限以及非預期的方式執(zhí)行命令。 popen特別適用于構造簡單的過濾器程序,它變換運行命令的輸入或輸出。當命令希望構造它自己的管道線時,就是這種情形。 實例 考慮一個應用程序,它向標準輸出寫一個提示,然后從標準輸入讀1行。使用popen,可以在應用程序和輸入之間插入一個程序以便對輸入進行變換處理。圖15-7顯示了為此做的進程安排。 圖15-7 用popen對輸入進行變換處理 對輸入進行的變化可能是路徑名擴充,或者是提供一種歷史機制(記住以前輸入的命令)。 程序清單15-6是一個簡單的過濾程序,它只是將標準輸入復制到標準輸出,在復制時,將所有大寫字符變換為小寫字符。在寫了一行以后,對標準輸出進行了沖洗(用fflush),其理由可參考進程間通信之協(xié)同進程。 程序清單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); } 對該過濾程序進行編譯,其可執(zhí)行目標代碼放在文件myuclc中(也就是編譯后的可執(zhí)行文件名為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環(huán)境高級編程》(第二版),僅作個人學習記錄所用。關于本書可參考:http://www./。 |
|
|
來自: just_person > 《待分類》