popen和pclose
常见的操作是创建一个管道连接到另一个进程,然后读其输出或向其输入端发送数据,为此,标准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相关推荐
- linux c之通过popen和pclose函数创建管道执行shell 运行命令使用总结
1.函数介绍 popen 和 pclose 函数 操作是创建一个管道链接到另一个进程,然后读其输出或向其输入端发送数据.标准 I/O 库提供了两个函数 popen 和 pclose 函数,这两个函数实 ...
- 基于管道的popen和pclose函数
基于管道的popen和pclose函数 标准I/O函数库提供了popen函数,它启动另外一个进程去执行一个shell命令行. 这里我们称调用popen的进程为父进程,由popen启动的进程称为子进程. ...
- linux c语言中如何通过进程名获取进程PID(awk命令行指令)popen、pclose
文章目录 不唯一匹配(包含字符的全都匹配上)(而且进程名最多为15个字符?)(前15个字符?) 方法1:嵌到程序里,通过命令行附带要查找的进程名称(交叉编译在arm摄像头里运行) 方法2:通过搜素/p ...
- Linux popen和pclose启动shell命令的问题思考
很多时候,我们会使用popen的方式来启动shell命令来代替系统自带的system函数启动的方式.这本身并没有什么问题,但是在使用过程中遇到了一个奇怪的问题. 这里遇到问题主要是启动python程序 ...
- 管道popen和pclose的实例使用
单次输出 /*取得当前目录下的文件个数*/ #include <stdio.h> #include <stdlib.h> #include <errno.h> #i ...
- Linux的system和popen的差异
1.system()和popen()简介 在linux中我们可以通过system()来执行一个shell命令,popen()也是执行shell命令并且通过管道和shell命令进行通信. system( ...
- Linux的system()和popen()差异
Linux的system()和popen()差异 1. system()和popen()简介 在Linux中我们可以通过system()来执行一个shell命令,popen()也是执行shell命令并 ...
- popen和system问题
popen和system问题 1. 问题描述 C的代码里面去调用命令启动一个shell脚本,分别使用了下面两个途径. 其中一个是: func1(cmd) { popen(cmd,type); pclo ...
- pclose与fclose的区别
先写结论:pclose会调用waitpid为popen时fork的子进程收尸,而fclose不会.所以如果调用popen后用fclose关闭,那么将可能产生僵尸进程. 一段丑陋的测试代码: #incl ...
- php多线程编程之popen方法示例
为了代码的执行效率,开发中不免用到多线程编程.需要程序并行运行,php的多线程也非常简单实用,demo如下: function background_run($func,$contr,$iuu = ' ...
最新文章
- 腾讯2016春季实习生(技术运营岗)招聘电话面试题汇集。
- (转)关于Linux核心转储文件 core dump
- 互联网思维之求职信,百战百胜
- leetcode 461. 汉明距离(Java版)
- IntelliJ IDEA 修改单行注释的格式
- 151205 财务管理原理作业(笔试题型)
- strip and linux lib compile
- 让S3c2410里拥有HIVE注册表的 全部步骤
- 联发科有没有高端处理器_2021年华为将成为联发科最大客户?麒麟或将“灭亡?”...
- 4-算法 校门外的树
- sql把字符数组转换成表
- go lang go get There is no tracking information for the current branch.Please specify which
- HTML+CSS登陆界面实例
- shiro.crypto.CryptoException: Unable to correctly extract the Initialization Vector or ciphertext
- 计算机启动相机代码,如何在win7系统中启动相机
- word 2013 新建批注 显示/隐藏批注 删除批注
- java swing 简单计算器_java用swing写了一个简单的计算器
- 【EV 录屏】电脑音视频录制软件:EV 录屏——下载和安装
- 推荐算法架构2:粗排
- ESD镜像文件转换成ISO镜像文件解决方案
热门文章
- Julia : 小s与关于绝对路径的转义方式
- 【水果识别】基于matlab GUI形态学水果识别(含识别率)【含Matlab源码 907期】
- 【语音隐写】基于matlab GUI DWT音频数字水印(带语音播报)【含Matlab源码 711期】
- 【车间调度】基于matlab灰狼优化算法求解柔性作业车间问题【含Matlab源码 661期】
- 机器学习指南_管理机器学习实验的快速指南
- SecondNamenode作用
- 图像像素点赋值_OpenCV学习笔记(二)之图像阈值化
- mysql1232_Mysql执行流程
- python类中的特殊方法_Python中类的初始化特殊方法
- php打印当前页面隐藏页眉,javascript实现window.print()去除页眉页脚_javascript技巧