简体   繁体   English

如何控制popen stdin,stdout,stderr重定向?

[英]how to control popen stdin, stdout, stderr redirection?

I am confused about how popen() redirects stdin, stdout and stderr of the child process in unix. 我很困惑popen()如何在unix中重定向子进程的stdin,stdout和stderr。 The man page on popen() is not very clear in this regard. 关于popen()的手册页在这方面不是很清楚。 The call 电话

FILE *p = popen("/usr/bin/foo", "w");

forks a child process and executes a shell with arguments "-c", "/usr/bin/foo", and redirects stdin of this shell (which is redirected stdin of foo), stdout to p. 分叉子进程并执行带参数“-c”,“/ usr / bin / foo”的shell,并重定向此shell的stdin(重定向foo的stdin),stdout到p。 But what happens with stderr? 但是stderr会发生什么? What is the general principle behind it? 它背后的一般原则是什么?

I noticed that, if I open a file in foo (using fopen, socket, accept etc.), and the parent process has no stdout, it gets assigned the next available file number, which is 1 and so on. 我注意到,如果我在foo中打开一个文件(使用fopen,socket,accept等),并且父进程没有stdout,它会被分配下一个可用的文件号,即1,依此类推。 This delivers unexpected results from calls like fprintf(stderr, ...). 这会从fprintf(stderr,...)等调用中产生意外结果。

It can be avoided by writing 写作可以避免

FILE *p = popen("/usr/bin/foo 2>/dev/null", "w");

in the parent program, but are their better ways? 在父计划中,但他们是更好的方法吗?

popen(3) is just a library function, which relies on fork(2) and pipe(2) to do the real work. popen(3)只是一个库函数,它依赖fork(2)pipe(2)来完成实际的工作。

However pipe(2) can only create unidirectional pipes. 但是pipe(2)只能创建单向管道。 To send the child process input, and also capture the output, you need to open two pipes. 要发送子进程输入,并捕获输出,您需要打开两个管道。

If you want to capture the stderr too, that's possible, but then you'll need three pipes, and a select loop to arbitrate reads between the stdout and stderr streams. 如果你想捕获stderr ,那是可能的,但是你需要三个管道,并select一个循环来仲裁stdoutstderr流之间的读取。

There's an example here for the two-pipe version. 这里有一个例子在这里为双管版。

简单的想法:为什么不在命令字符串中添加“2>&1”以强制bash将stderr重定向到stdout(好吧,写入stdin仍然是不可能的,但至少我们得到stderr和stdout进入我们的C程序)。

The return value from popen() is a normal standard I/O stream in all respects save that it must be closed with pclose() rather than fclose(3). popen()的返回值在所有方面都是普通的标准I / O流,除非必须用pclose()而不是fclose(3)来关闭它。 Writing to such a stream writes to the standard input of the command; 写入这样的流写入命令的标准输入; the command's standard output is the same as that of the process that called popen(), unless this is altered by the command itself. 命令的标准输出与调用popen()的进程相同,除非命令本身改变了它。 Conversely, reading from a "popened" stream reads the command's standard output, and the command's standard input is the same as that of the process that called popen(). 相反,从“popened”流读取读取命令的标准输出,命令的标准输入与调用popen()的进程相同。

From its manpage, so it allows you to read the commands standard output or write into its standard input. 从其手册页,它允许您读取命令标准输出或写入其标准输入。 It doesn't say anything about stderr. 它没有说明stderr。 Thus that is not redirected. 因此,不会重定向。

If you provide "w", you will send your stuff to the stdin of the shell that is executed. 如果你提供“w”,你将把你的东西发送到执行的shell的stdin。 Thus, doing 这样做

FILE * file = popen("/bin/cat", "w");
fwrite("hello", 5, file);
pclose(file);

Will make the shell execute /bin/cat, and pass it the string "hello" as its standard input stream. 将使shell执行/ bin / cat,并将字符串"hello"作为其标准输入流传递给它。 If you want to redirect, for example stderr to the file "foo" do this first, before you execute the code above: 如果要将stderr重定向到文件"foo" ,请先执行此操作,然后再执行上述代码:

FILE * error_file = fopen("foo", "w+");
if(error_file) {
    dup2(fileno(error_file), 2);
    fclose(error_file);
}

It will open the file, and duplicate its file-descriptor to 2, closing the original file descriptor afterwards. 它将打开文件,并将其文件描述符复制到2,然后关闭原始文件描述符。

Now, if you have your stdout closed in your parent, then if the child calls open it will get 1, since that's (if stdin is already opened) the next free file-descriptor. 现在,如果您的父节点中关闭了stdout,那么如果子调用open ,它将获得1,因为那是(如果stdin已经打开)下一个空闲文件描述符。 Only solution i see is to just use dup2 and duplicate something into that in the parent, like the above code. 我看到的唯一解决方案就是使用dup2并将其复制到父级中,就像上面的代码一样。 Note that if the child opens stdout , it will not make stdout open in the parent too. 请注意,如果孩子打开stdout ,它不会使stdout父太开放。 It stays closed there. 它在那里保持关闭。

if you just want to get STDERR, try this: 如果您只想获得STDERR,请尝试以下方法:

#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <malloc.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>

/*
 * Pointer to array allocated at run-time.
 */
static pid_t    *childpid = NULL;

/*
 * From our open_max(), {Prog openmax}.
 */
static int      maxfd;

FILE *
mypopen(const char *cmdstring, const char *type)
{
    int     i;
    int     pfd[2];
    pid_t   pid;
    FILE    *fp;

    /* only allow "r" "e" or "w" */
    if ((type[0] != 'r' && type[0] != 'w' && type[0] != 'e') || type[1] != 0) {
        errno = EINVAL;     /* required by POSIX */
        return(NULL);
    }

    if (childpid == NULL) {     /* first time through */
        /* allocate zeroed out array for child pids */
        maxfd = 256;
        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);   /* errno set by fork() */
    } else if (pid == 0) {                          /* child */
        if (*type == 'e') {
            close(pfd[0]);
            if (pfd[1] != STDERR_FILENO) {
                dup2(pfd[1], STDERR_FILENO);
                close(pfd[1]);
            }
        } else 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 == 'e') {
        close(pfd[1]);
        if ((fp = fdopen(pfd[0], "r")) == NULL)
            return(NULL);
    } else 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; /* remember child pid for this fd */
    return(fp);
}

int
mypclose(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 */
}

int shellcmd(char *cmd){
    FILE *fp;
    char buf[1024];
    fp = mypopen(cmd,"e");
    if (fp==NULL) return -1;

    while(fgets(buf,1024,fp)!=NULL)
    {
        printf("shellcmd:%s", buf);
    }

    pclose(fp);
    return 0;
}

int main()
{
    shellcmd("ls kangear");
}

and you will get this: 你会得到这个:

shellcmd:ls: cannot access kangear: No such file or directory

Check out popenRWE by Bart Trojanowski. 查看Bart Trojanowski的popenRWE Clean way to do all 3 pipes. 干净的方式做所有3管道。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM