欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

简单汇总:C语言实现Shell命令执行的三种方法——popen技巧

最编程 2024-07-25 19:09:22
...

根据模式重定向子进程的标准输入或输出,提供控制子进程输入或输出的能力。

函数原型:

#include <stdio.h>
FILE *popen(const char *cmdstring, const char *type);
int pclose(FILE *fp);

简版实现

以下代码修改自《UNIX环境高级编程》和 glibc 中 popen 的实现。

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdlib.h>     /* for calloc*/

static pid_t *childpid = NULL;
static int maxfd = 1024;

FILE * popen(const char *cmd, const char * mode)
{
    int do_read = 0, do_write = 0;
    int parent_end, child_end;
    int fds[2];
    pid_t pid;
    FILE *fp;
    const char *type= mode;

    while (*type != '\0') {
        switch (*type++)
        {
        case 'r':
            do_read = 1;
            break;
        case 'w':
            do_write = 1;
            break;
        
        default:
            return NULL;
        }
    }

    if (do_read ^ do_write == 0)
        return NULL;

    if (pipe(fds) < 0)
        return NULL;

    if (do_read) {
        parent_end = fds[0];
        child_end = fds[1];
    } else {
        parent_end = fds[1];
        child_end = fds[0];
    }

    if (childpid == NULL) {
        if ((childpid = calloc(maxfd, sizeof(pid_t))) == NULL) {
            perror("calloc error");
            return NULL;
        }
    }

    if ((pid = fork()) < 0)
        return NULL;
    else if (pid == 0) {
        int child_std_end = do_read ? 1 : 0;
        
        if (child_end != child_std_end)
            dup2(child_end, child_std_end); /* clear close-on-exec flag of child_std_end */
        
        close(child_end);
        close(parent_end);
        /*
         * POSIX.1 要求 popen 关闭那些以前调用 popen 打开,
         * 现在仍在子进程中打开的 I/O 流。
         */
        for (int i = 0; i < maxfd; i++) {
            if (childpid[i] > 0)
                close(i);
        }
			
        execl("/bin/sh", "sh", "-c", cmd, (char *)0);
        _exit(127);	/* execl error */
    }

    close(child_end);
    if ((fp = fdopen(parent_end, mode)) == NULL) {
        perror("fail open fd");
        return NULL;
    }

    childpid[fileno(fp)] = pid;
    return fp;
}

int pclose(FILE* stream)
{
    int pid, stat;
    int fd;

    if (childpid == NULL) {
        errno = EINVAL;
        return -1;
    }

    fd = fileno(stream);
    if (fd >= maxfd) {
        errno = EINVAL;
        return -1;
    }
    if ((pid = childpid[fd]) == 0) {
        errno = EINVAL;
        return -1;
    }

    childpid[fd] = 0;
    if (fclose(stream) == EOF) {
        return -1;
    }

    while (waitpid(pid, &stat, 0) < 0) {
        if (errno != EINTR) {
            return -1;
        }
    }
    return stat;
}

使用示例:

void test_popen()
{
    FILE *fp;
    char *data = "hello world!";
    char buf[1024];

    /* write example */
    char *cmdstring = "wc -c";	/* 统计字符个数 */
    if ((fp = popen(cmdstring, "w")) == NULL) {
        perror("popen error");
        return;
    }
    fputs(data, fp);
    if (pclose(fp) < 0) {
        perror("pclose error");
        return;
    }

    /* read example */
    cmdstring = "ls -al";
    if ((fp = popen(cmdstring, "r")) == NULL) {
        perror("popen error");
        return;
    }
    for ( ; ; ) {
        fflush(stdout);
        if (fgets(buf, sizeof(buf), fp) == NULL)
            break;
        if (fputs(buf, stdout) == EOF) {
            perror("fputs error");
            return;
        }
    }
    if (pclose(fp) < 0) {
        perror("pclose error");
        return;
    }
}