方法6的popen,pclose函数:
参考: https://www.cnblogs.com/nufangrensheng/p/3561190.html
#include <stdio.h>
FILE *popen(const char *cmdstring,const char *type);
int pclose(FILE *fp);
返回值:cmdstring(shell)的终止状态,若出错则返回-1
popen函数:
pipe---->fork---->exec :创建一个管道,调用fork产生一个子进程,关闭管道的不使用端,执行一个shell以运行命令.
执行fp = popen(cmdstring, “r”)函数的结果
type为’r’连接到cmdstring的标准输出;此时fp相当于管道的fd[0], stdout相当于管道的fd[1].
返回一个指向fd[1] 端的FILE文件指针,然后从fd读取该数据
type为’w’连接到cmdstring的标准输入,此时fp相当于管道的fd[1], stdin相当于管道的fd[0].
返回fp,往fp写,也就写到了子进程的fd[0],把写入的数据(命令)给/bin/sh执行.
pclose函数: 关闭标准i/o流,然后等待命令终止。
用popen向分页程序传送文件:
#include"apue.h"
#include <sys/wait.h>
#define PAGER "${PAGER:-more}" /*PAGER若定义使用其值 , or default使用more */
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,pclose函数的实现:
参考: http://blog.csdn.net/litingli/article/details/5891726
1. #include <errno.h>
1. #include <fcntl.h>
2. #include <stdio.h>
3. #include <unistd.h>
4. static pid_t *childpid = NULL;
5. static int maxfd;
6.
7. FILE * popen(const char *cmdstring, const char *type)
8. {
9. int i;
10. int pfd[2];
11. pid_t pid;
12. FILE *fp;
13.
14. /* only allow "r" or "w" */
15. if ((type[0] != 'r' && type[0] != 'w') || type[1] != 0)
16. {
17. errno = EINVAL; /* required by POSIX */
18. return(NULL);
19. }
20.
21. if (childpid == NULL)/* first time through */
22. { /* allocate zeroed out array for child pids */
23. maxfd = open_max();//返回最大文件描述符个数,其里面实现用sysconf(_SC_OPEN_MAX)
24. if ((childpid = calloc(maxfd, sizeof(pid_t))) == NULL)
25. return(NULL);
26. }
27.
28. if (pipe(pfd) < 0) return(NULL); /* errno set by pipe() */
29. if (pfd[0] >= maxfd || pfd[1] >= maxfd )
30. { //maxfd <管道文件描述符,则认为很多文件描述符是打开的.
31. close(pfd[0]); close(pfd[1]); errnp = EMFILE; return (NULL);
32. }
33. if ((pid = fork()) < 0)
34. {
35. return(NULL); /* errno set by fork() */
36. }
37. else if (pid == 0) /* child */
38. {
39. if (*type == 'r')
40. {
41. close(pfd[0]);
42. if (pfd[1] != STDOUT_FILENO)
43. {
44. dup2(pfd[1], STDOUT_FILENO);
45. close(pfd[1]);
46. }
47. }
48. else
49. {
50. close(pfd[1]);
51. if (pfd[0] != STDIN_FILENO)
52. {
53. dup2(pfd[0], STDIN_FILENO);
54. close(pfd[0]);
55. }
56. }
57.
58. for (i = 0; i < maxfd; i++) /* close all descriptors in childpid[] */
59. if (childpid[i] > 0) close(i); //关闭调用open依旧打开着的的文件描述符.
60.
61. execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
62. _exit(127);
63. }
64.
65. if (*type == 'r') /* parent continues... */
66. {
67. close(pfd[1]);
if ((fp = fdopen(pfd[0], type)) == NULL) return(NULL);//将文件描述符转化为FILE*指针.
68. }
69. else
70. {
71. close(pfd[0]);
72. if ((fp = fdopen(pfd[1], type)) == NULL) return(NULL);
73. }
74.
75. childpid[fileno(fp)] = pid; /* remember child pid for this fd */
76. return(fp);
77. }
78.
79. int pclose(FILE *fp)
80. {
81. int fd, stat;
82. pid_t pid;
83.
84. if (childpid == NULL)
85. {
86. errno = EINVAL;
87. return(-1); /* popen() has never been called */
88. }
89.
90. fd = fileno(fp);
91. if ((pid = childpid[fd]) == 0) //通过文件描述符得到进程id.
92. {
93. errno = EINVAL;
94. return(-1); /* fp wasn't opened by popen() */
95. }
96.
97. childpid[fd] = 0;
98. if (fclose(fp) == EOF) return(-1);
99.
100. while (waitpid(pid, &stat, 0) < 0)
101. if (errno != EINTR) return(-1); /* error other than EINTR from waitpid() */
102. /*内核不对SIGCHLD信息排队,如果有b个子进程同时结束,父进程会收到1个信号,信号处理函数中while就会循环n次,
103. 把所有结束的子进程处理掉,直到没有已经结束的进程。*/
104. return(stat); /* return child's termination status */
105. }
Waitpid和SIGCHLD信号:
说明参考:
http://blog.csdn.net/roland_sun/article/details/32084825
http://blog.csdn.net/yinghuashihun/article/details/6387405
http://blog.csdn.net/liuxingen/article/details/38350347
https://www.cnblogs.com/wanpengcoder/p/5310983.html
返回EINTR:
1、pclose的调用者已近注册SIGCHLD信号处理函数,调用pclose时,其内部的waitpid返回EINTR.
返回ECHILD:
1、popen的调用进程调用watpid获取到popen子进程的退出状态,调用pclose时,其内部的waitpid返回ECHILD.
2、signal设置了忽略SIGCHLD信号的情况下,内核会处理子进程的退出状态并释放子进程资源将,等父进程调用waitpid时,发现对应的pid进
程已不存在,因而返回-1错误,errno为10(No child processes).
Popen实现的过滤器:
参考: https://www.cnblogs.com/nufangrensheng/p/3561190.html
注意,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,然后在程序清单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。