popen 的使用方法及场景

Popen的使用方法

1.Popen的应用场景

popen应用于执行shell命令,并读取此命令的返值,或者与执行的命令进行交互。

2.Popen的实现

Popen()函数通过创建一个管道,调用fork()产生一个子进程,然后调用execl("/bin/sh", "sh", "-c”,command,0);执行shell命令。可以通过这个管道进行标准输入输出操作,下面会附上源码。

3.Pclose操作

Pclose()函数会闭标准i/0流,等待子进程结束,然后返回shell终止状态。如果不执行,则pclose()返回终止状态就是shellexit状态。

 源码附上:

/* Copyright (C) 2004       Manuel Novoa III    <mjn3@codepoet.org>
 * Copyright (C) 2000-2006 Erik Andersen <andersen@uclibc.org>
 *
 * Licensed under the LGPL v2.1, see the file COPYING.LIB in this tarball.
 *
 * Dedicated to Toni.  See uClibc/DEDICATION.mjn3 for details.
 */

/* Jan 1, 2004
 *
 * Rewrite popen for SUSv3 compliance.
 *   Added a list of popen()'d to store pids and use waitpid() in pclose().
 *   Loop on waitpid() failure due to EINTR as required.
 *   Close parent's popen()'d FILEs in the {v}fork()'d child.
 *   Fix failure exit code for failed execve().
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
#include <bits/uClibc_mutex.h>

#ifdef __UCLIBC_MJN3_ONLY__
#warning "hmm... susv3 says Pipe streams are byte-oriented."
#endif /* __UCLIBC_MJN3_ONLY__ */


/* uClinux-2.0 has vfork, but Linux 2.0 doesn't */
#include <sys/syscall.h>
#if ! defined __NR_vfork
# define vfork fork
# define VFORK_LOCK		((void) 0)
# define VFORK_UNLOCK		((void) 0)
#endif

#ifndef VFORK_LOCK
__UCLIBC_MUTEX_STATIC(mylock, PTHREAD_MUTEX_INITIALIZER);
# define VFORK_LOCK		__UCLIBC_MUTEX_LOCK(mylock)
# define VFORK_UNLOCK		__UCLIBC_MUTEX_UNLOCK(mylock)
#endif

struct popen_list_item {
	struct popen_list_item *next;
	FILE *f;
	pid_t pid;
};

static struct popen_list_item *popen_list /* = NULL (bss initialized) */;

FILE *popen(const char *command, const char *modes)
{
	FILE *fp;
	struct popen_list_item *pi;
	struct popen_list_item *po;
	int pipe_fd[2];
	int parent_fd;
	int child_fd;
	int child_writing;			/* Doubles as the desired child fildes. */
	pid_t pid;

	child_writing = 0;			/* Assume child is writing. */
	if (modes[0] != 'w') {		/* Parent not writing... */
		++child_writing;		/* so child must be writing. */
		if (modes[0] != 'r') {	/* Oops!  Parent not reading either! */
			__set_errno(EINVAL);
			goto RET_NULL;
		}
	}

	if (!(pi = malloc(sizeof(struct popen_list_item)))) {
		goto RET_NULL;
	}
	// 打开一个pipe,管道是单向。故数据流只能单向流动。
	if (pipe(pipe_fd)) {
		goto FREE_PI;
	}

	//下面两个描述符就是管道的两端的描述,一个为读一个为写。
	child_fd = pipe_fd[child_writing]; 
	parent_fd = pipe_fd[1-child_writing]; 
	//fdopen就是打开一个描述,fd相同模式或者字集的方式打开。意思就是把一个已找打开的流与一个文件描述符相关联,且
	//这个文件描述是唯一的,这样也就可以保证这个函数接口的可重入性。如果设计的不可重入性,也就没必要再做一次fdopen了。
	if (!(fp = fdopen(parent_fd, modes))) {
		close(parent_fd);
		close(child_fd);
		goto FREE_PI;
	}

	VFORK_LOCK;
	//再这里创建一个子进程,然后执行 shell命令。这里最重的两步就是用pipe的两个描述替换标准输入或者输出。
	if ((pid = vfork()) == 0) {	/* Child of vfork... */
		close(parent_fd);
		if (child_fd != child_writing) {
			dup2(child_fd, child_writing); //用child_fd来代替标准输入或输出。
			close(child_fd);
		}

		/* SUSv3 requires that any previously popen()'d streams in the
		 * parent shall be closed in the child. */
		//关闭不必要的资源。
		for (po = popen_list ; po ; po = po->next) {
			close(po->f->__filedes);
		}

		//执行exec shell,这个时候标准输入/输出就变为pipe管道的一端了。
		//这里只能实现单向的功能。要么读要么写。
		execl("/bin/sh", "sh", "-c", command, (char *)0);

		/* SUSv3 mandates an exit code of 127 for the child if the
		 * command interpreter can not be invoked. */
		_exit(127);
	}
	VFORK_UNLOCK;

	/* We need to close the child filedes whether vfork failed or
	 * it succeeded and we're in the parent. */
	close(child_fd);
	//将当前的信息保存到全局链表。为了是pclose可以找到对的子进程与通信文件描述。
	if (pid > 0) {				/* Parent of vfork... */
		pi->pid = pid;
		pi->f = fp;
		VFORK_LOCK;
		pi->next = popen_list;
		popen_list = pi;
		VFORK_UNLOCK;

		return fp;
	}

	/* If we get here, vfork failed. */
	fclose(fp);					/* Will close parent_fd. */

 FREE_PI:
	free(pi);

 RET_NULL:
	return NULL;
}

#warning is pclose correct wrt the new mutex semantics?

int pclose(FILE *stream)
{
	struct popen_list_item *p;
	int stat;
	pid_t pid;

	/* First, find the list entry corresponding to stream and remove it
	 * from the list.  Set p to the list item (NULL if not found). */
	VFORK_LOCK;
	if ((p = popen_list) != NULL) {
		if (p->f == stream) {// 找到stream对应的popen结点。
			popen_list = p->next;
		} else {
			struct popen_list_item *t;
			do {
				t = p;
				if (!(p = t->next)) {
					__set_errno(EINVAL); /* Not required by SUSv3. */
					break;
				}
				if (p->f == stream) {
					t->next = p->next;
					break;
				}
			} while (1);
		}
	}
	VFORK_UNLOCK;

	if (p) {
		pid = p->pid;			/* Save the pid we need */
		free(p);				/* and free the list item. */

		fclose(stream);	/* The SUSv3 example code ignores the return. */

		/* SUSv3 specificly requires that pclose not return before the child
		 * terminates, in order to disallow pclose from returning on EINTR. */
		do {
			if (waitpid(pid, &stat, 0) >= 0) { //等待子进程返回。获取返回值。
				return stat;
			}
			if (errno != EINTR) {
				break;
			}
		} while (1);
	}

	return -1;
}

举例附上:

#include <stdio.h>
#include <string.h>

int main()
{
	FILE *fp = NULL;
	char buf[1024] = "";

	fp = popen("ls -al", "r");
	if(fp == NULL)
	{
		perror("popen error\n");
		return -1;
	}
	while(fgets(buf, sizeof(buf), fp) != 0)
	{
		printf("%s\n", buf);
		memset(buf, 0x0, sizeof(buf));
	}
	pclose(fp);
	return 0;
}


 

 

阅读更多
文章标签: popen popen源码
上一篇进程间通信之管道
下一篇进程间通信之消息队列
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭