关于 dup2(fd[0],STDOUT_FILENO) 的一些波折

在看 unix 环境高级编程 的时候,在管道这一部分,还没有看到后面的代码时,一直被一个问题困扰着。先看代码

//15-6
#include "apue.h"
#include 

#define	DEF_PAGER	"/bin/more"		/* default pager program */

int
main(int argc, char *argv[])
{
	int		n;
	int		fd[2];
	pid_t	pid;
	char	*pager, *argv0;
	char	line[MAXLINE];
	FILE	*fp;

	if (argc != 2)
		err_quit("usage: a.out ");

	if ((fp = fopen(argv[1], "r")) == NULL)
		err_sys("can't open %s", argv[1]);
	if (pipe(fd) < 0)
		err_sys("pipe error");

	if ((pid = fork()) < 0) {
		err_sys("fork error");
	} else if (pid > 0) {								/* parent */
		close(fd[0]);		/* close read end */

		/* parent copies argv[1] to pipe */
		while (fgets(line, MAXLINE, fp) != NULL) {
			n = strlen(line);
			if (write(fd[1], line, n) != n)			//这里只是把 line 中的字符串写入到管道的写端。还是很正常的
				err_sys("write error to pipe");
		}
		if (ferror(fp))
			err_sys("fgets error");

		close(fd[1]);	/* close write end of pipe for reader */

		if (waitpid(pid, NULL, 0) < 0)
			err_sys("waitpid error");
		exit(0);
	} else {										/* child */
		close(fd[1]);	/* close write end */
		if (fd[0] != STDIN_FILENO) {
			if (dup2(fd[0], STDIN_FILENO) != STDIN_FILENO)		//在这里呢,则是把 标准输入 文件描述符重新指向了管道的读端,也是可以理解的
				err_sys("dup2 error to stdin");
			close(fd[0]);	/* don't need this after dup2 */
		}

		/* get arguments for execl() */
		//这里我在 lbuntu 中测试的时候,是返回 NULL 的,所以最后 pager = "/bin/more",就是说下面 execl 调用的是 /bin/more 程序
		//至于 argv0 就是 "more" 字符串了,没啥好说的
		if ((pager = getenv("PAGER")) == NULL)
			pager = DEF_PAGER;				
		if ((argv0 = strrchr(pager, '/')) != NULL)
			argv0++;		/* step past rightmost slash */
		else
			argv0 = pager;	/* no slash in pager */

		if (execl(pager, argv0, (char *)0) < 0)		//可是在这里,execl 一个程序后,为什么在父进程中写入到管道写端的字符串会被直接输出到了 标准输出 了呢?
			err_sys("execl error for %s", pager);
	}
	exit(0);
}

上面代码中最大的疑惑是:dup2的功能只是把 STDIN_FILENO 文件描述符重定向到 fd[0],可是在 excel 后,却可以在标准输出中输出了管道读端的内容。(难道标准输入也可以进行标准输出?或者 excel 后,程序会自动读取管道的内容?excel 后,管道会的内容会被自动读取出来?可是都不应该啊,从来就没有自动的东西)怎么也说不通。

继续看后面 15-12 ,15-12是作者实现的 popen() pclose() 函数。从中可以看到很多的细节。分析一下 popen() 的一部分代码:

//15-12
if ((pid = fork()) < 0) {
	return(NULL);	/* errno set by fork() */
} else if (pid == 0) {							/* child */
	if (*type == 'r') {					//如果标志是 'r',则把 标准输出 文件描述符重定向到管道的写端
		close(pfd[0]);
		if (pfd[1] != STDOUT_FILENO) {
			dup2(pfd[1], STDOUT_FILENO);
			close(pfd[1]);
		}
	} else {						//如果标志是 'w',则把 标准输入 文件描述符重定向到管道的读端
		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);
}

假如,调用 popen() 的标志是 "r" ,那么在调用进程中的管道就是把 写端 给关闭了,然后把 读端生成了流。子进程呢,则是把 读端 关闭了,把 标准输出文件描述符 重定向到了 管道 的写端。

先大概了解这么多,再来看 15-14 和 15-15 

//15-14
#include "apue.h"
#include 

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);
}

15-14 最后会生成一个 myuclc 程序,被15-15调用

//15-15
#include "apue.h"
#include 

int
main(void)
{
	char	line[MAXLINE];
	FILE	*fpin;

	if ((fpin = popen("myuclc", "r")) == NULL)		//使用 popen 调用 myuclc 程序
		err_sys("popen error");
	for ( ; ; ) {
		fputs("prompt> ", stdout);			//输出到标准输出
		fflush(stdout);
		if (fgets(line, MAXLINE, fpin) == NULL)		//从 fpin 流读入数据 需要讲解的就是这一部分了
			break;
		if (fputs(line, stdout) == EOF)			//在输出到标准输入
			err_sys("fputs error to pipe");
	}
	if (pclose(fpin) == -1)
		err_sys("pclose error");
	putchar('\n');
	exit(0);
}

从 fpin 流读入数据究竟是一个怎样的流程呢?

假设 15-15 的 main 为进程 A ,在调用 popen 时,就是 15-12 的代码,生成了子线程 B ,因为使用的是 “r” 标志,所以进程 A 就把自己管道的 写端 给关闭了,然后把 读端 生成了流。进程 B 则是把 读端 关闭了,把 标准输出 文件描述符 重定向到了 管道 的写端;然后进程 B 再调用 execl() ,进入到了程序 myuclc (15-14) 中。所以在程序 myuclc 中,它调用 标准输出 时,就会因为重定向的关系,数据就被写入到了管道的写端中;而 标准输入没有重定向,所以依旧会从控制终端上读取信息。

所以在进程 A 中,fputs() 仍旧使用标准输出,输出到控制终端

fgets(line,MAXLINE,fpin) 调用时,是从流 fpin 中读取数据,因为 fpin 流是由管道的读端生成的,所以就是从管道的读端读取数据。而管道的写端的数据哪里来呢?就是在程序 myuclc 中,调用标准输出的时候,重定向写入的了。

所以,完全没有上面疑惑的那些问题什么 excel 的自动化功能!!!!!!都是假的!!!!!全是手动的!!!!!至于为什么15-6中的程序,调用 /bin/more 能把文件的内容直接输出到了控制终端,那绝对是因为 more 程序通过标准输入(已经被重定向到管道的读端了)读取读端的内容,然后再偷偷的通过标准输出写出来的! 

你可能感兴趣的:(关于 dup2(fd[0],STDOUT_FILENO) 的一些波折)