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

#include <stdio.h>FILE *popen(const char *cmdstring, const char *type);
返回值:若成功则返回文件指针,若出错则返回NULLint 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/

popen和pclose相关推荐

  1. linux c之通过popen和pclose函数创建管道执行shell 运行命令使用总结

    1.函数介绍 popen 和 pclose 函数 操作是创建一个管道链接到另一个进程,然后读其输出或向其输入端发送数据.标准 I/O 库提供了两个函数 popen 和 pclose 函数,这两个函数实 ...

  2. 基于管道的popen和pclose函数

    基于管道的popen和pclose函数 标准I/O函数库提供了popen函数,它启动另外一个进程去执行一个shell命令行. 这里我们称调用popen的进程为父进程,由popen启动的进程称为子进程. ...

  3. linux c语言中如何通过进程名获取进程PID(awk命令行指令)popen、pclose

    文章目录 不唯一匹配(包含字符的全都匹配上)(而且进程名最多为15个字符?)(前15个字符?) 方法1:嵌到程序里,通过命令行附带要查找的进程名称(交叉编译在arm摄像头里运行) 方法2:通过搜素/p ...

  4. Linux popen和pclose启动shell命令的问题思考

    很多时候,我们会使用popen的方式来启动shell命令来代替系统自带的system函数启动的方式.这本身并没有什么问题,但是在使用过程中遇到了一个奇怪的问题. 这里遇到问题主要是启动python程序 ...

  5. 管道popen和pclose的实例使用

    单次输出 /*取得当前目录下的文件个数*/ #include <stdio.h> #include <stdlib.h> #include <errno.h> #i ...

  6. Linux的system和popen的差异

    1.system()和popen()简介 在linux中我们可以通过system()来执行一个shell命令,popen()也是执行shell命令并且通过管道和shell命令进行通信. system( ...

  7. Linux的system()和popen()差异

    Linux的system()和popen()差异 1. system()和popen()简介 在Linux中我们可以通过system()来执行一个shell命令,popen()也是执行shell命令并 ...

  8. popen和system问题

    popen和system问题 1. 问题描述 C的代码里面去调用命令启动一个shell脚本,分别使用了下面两个途径. 其中一个是: func1(cmd) { popen(cmd,type); pclo ...

  9. pclose与fclose的区别

    先写结论:pclose会调用waitpid为popen时fork的子进程收尸,而fclose不会.所以如果调用popen后用fclose关闭,那么将可能产生僵尸进程. 一段丑陋的测试代码: #incl ...

  10. php多线程编程之popen方法示例

    为了代码的执行效率,开发中不免用到多线程编程.需要程序并行运行,php的多线程也非常简单实用,demo如下: function background_run($func,$contr,$iuu = ' ...

最新文章

  1. 腾讯2016春季实习生(技术运营岗)招聘电话面试题汇集。
  2. (转)关于Linux核心转储文件 core dump
  3. 互联网思维之求职信,百战百胜
  4. leetcode 461. 汉明距离(Java版)
  5. IntelliJ IDEA 修改单行注释的格式
  6. 151205 财务管理原理作业(笔试题型)
  7. strip and linux lib compile
  8. 让S3c2410里拥有HIVE注册表的 全部步骤
  9. 联发科有没有高端处理器_2021年华为将成为联发科最大客户?麒麟或将“灭亡?”...
  10. 4-算法 校门外的树
  11. sql把字符数组转换成表
  12. go lang go get There is no tracking information for the current branch.Please specify which
  13. HTML+CSS登陆界面实例
  14. shiro.crypto.CryptoException: Unable to correctly extract the Initialization Vector or ciphertext
  15. 计算机启动相机代码,如何在win7系统中启动相机
  16. word 2013 新建批注 显示/隐藏批注 删除批注
  17. java swing 简单计算器_java用swing写了一个简单的计算器
  18. 【EV 录屏】电脑音视频录制软件:EV 录屏——下载和安装
  19. 推荐算法架构2:粗排
  20. ESD镜像文件转换成ISO镜像文件解决方案

热门文章

  1. Julia : 小s与关于绝对路径的转义方式
  2. 【水果识别】基于matlab GUI形态学水果识别(含识别率)【含Matlab源码 907期】
  3. 【语音隐写】基于matlab GUI DWT音频数字水印(带语音播报)【含Matlab源码 711期】
  4. 【车间调度】基于matlab灰狼优化算法求解柔性作业车间问题【含Matlab源码 661期】
  5. 机器学习指南_管理机器学习实验的快速指南
  6. SecondNamenode作用
  7. 图像像素点赋值_OpenCV学习笔记(二)之图像阈值化
  8. mysql1232_Mysql执行流程
  9. python类中的特殊方法_Python中类的初始化特殊方法
  10. php打印当前页面隐藏页眉,javascript实现window.print()去除页眉页脚_javascript技巧