最佳地从system()命令捕获stdout

时间:2008-09-24 07:27:35

标签: c++ c system stdout

我正在尝试通过system()启动外部应用程序 - 例如system("ls")。我希望捕获它的输出,因此我可以将它发送到另一个函数进行进一步处理。在C / C ++中最好的方法是什么?

10 个答案:

答案 0 :(得分:40)

来自popen手册:

#include <stdio.h>

FILE *popen(const char *command, const char *type);

int pclose(FILE *stream);

答案 1 :(得分:32)

尝试使用popen()函数。它执行一个命令,如system(),但将输出定向到一个新文件。返回指向流的指针。

  FILE *lsofFile_p = popen("lsof", "r");

  if (!lsofFile_p)
  {
    return -1;
  }

  char buffer[1024];
  char *line_p = fgets(buffer, sizeof(buffer), lsofFile_p);
  pclose(lsofFile_p);

答案 2 :(得分:6)

编辑:误读了想要将输出传递给另一个程序的问题,而不是另一个函数。 popen()几乎可以肯定你想要的。

系统为您提供对shell的完全访问权限。如果你想继续使用它,你可以  系统(“ls&gt; tempfile.txt”)将其输出重定向到临时文件,但选择安全的临时文件是一件痛苦的事。或者,您甚至可以通过另一个程序重定向:system(“ls | otherprogram”);

有些人可能会推荐使用popen()命令。如果您可以自己处理输出,那么这就是您想要的:

FILE *output = popen("ls", "r");

它将为您提供一个FILE指针,您可以使用命令的输出读取它。

您还可以使用pipe()调用与fork()一起创建连接以创建新进程,使用dup2()来更改它们的标准输入和输出,使用exec()来运行新程序,以及wait()在主程序中等待它们。这只是像shell一样设置管道。有关详细信息和示例,请参见pipe()手册页。

答案 3 :(得分:3)

函数popen()等不会重定向stderr等;我为此目的写了popen3()

这是我的popen3()的一个弓箭版本:

int popen3(int fd[3],const char **const cmd) {
        int i, e;
        int p[3][2];
        pid_t pid;
        // set all the FDs to invalid
        for(i=0; i<3; i++)
                p[i][0] = p[i][1] = -1;
        // create the pipes
        for(int i=0; i<3; i++)
                if(pipe(p[i]))
                        goto error;
        // and fork
        pid = fork();
        if(-1 == pid)
                goto error;
        // in the parent?
        if(pid) {
                // parent
                fd[STDIN_FILENO] = p[STDIN_FILENO][1];
                close(p[STDIN_FILENO][0]);
                fd[STDOUT_FILENO] = p[STDOUT_FILENO][0];
                close(p[STDOUT_FILENO][1]);
                fd[STDERR_FILENO] = p[STDERR_FILENO][0];
                close(p[STDERR_FILENO][1]);
                // success
                return 0;
        } else {
                // child
                dup2(p[STDIN_FILENO][0],STDIN_FILENO);
                close(p[STDIN_FILENO][1]);
                dup2(p[STDOUT_FILENO][1],STDOUT_FILENO);
                close(p[STDOUT_FILENO][0]);
                dup2(p[STDERR_FILENO][1],STDERR_FILENO);
                close(p[STDERR_FILENO][0]);
                // here we try and run it
                execv(*cmd,const_cast<char*const*>(cmd));
                // if we are there, then we failed to launch our program
                perror("Could not launch");
                fprintf(stderr," \"%s\"\n",*cmd);
                _exit(EXIT_FAILURE);
        }

        // preserve original error
        e = errno;
        for(i=0; i<3; i++) {
                close(p[i][0]);
                close(p[i][1]);
        }
        errno = e;
        return -1;
}

答案 4 :(得分:2)

最有效的方法是直接使用stdout文件描述符,绕过FILE流:

pid_t popen2(const char *command, int * infp, int * outfp)
{
    int p_stdin[2], p_stdout[2];
    pid_t pid;

    if (pipe(p_stdin) == -1)
        return -1;

    if (pipe(p_stdout) == -1) {
        close(p_stdin[0]);
        close(p_stdin[1]);
        return -1;
    }

    pid = fork();

    if (pid < 0) {
        close(p_stdin[0]);
        close(p_stdin[1]);
        close(p_stdout[0]);
        close(p_stdout[1]);
        return pid;
    } else if (pid == 0) {
        close(p_stdin[1]);
        dup2(p_stdin[0], 0);
        close(p_stdout[0]);
        dup2(p_stdout[1], 1);
        dup2(::open("/dev/null", O_WRONLY), 2);

        /// Close all other descriptors for the safety sake.
        for (int i = 3; i < 4096; ++i) {
            ::close(i);
        }

        setsid();
        execl("/bin/sh", "sh", "-c", command, NULL);
        _exit(1);
    }

    close(p_stdin[0]);
    close(p_stdout[1]);

    if (infp == NULL) {
        close(p_stdin[1]);
    } else {
        *infp = p_stdin[1];
    }

    if (outfp == NULL) {
        close(p_stdout[0]);
    } else {
        *outfp = p_stdout[0];
    }

    return pid;
}

要阅读 child 的输出,请使用popen2(),如下所示:

int child_stdout = -1;
pid_t child_pid = popen2("ls", 0, &child_stdout);

if (!child_pid) {
    handle_error();
}

char buff[128];
ssize_t bytes_read = read(child_stdout, buff, sizeof(buff));

同时写和读:

int child_stdin = -1;
int child_stdout = -1;
pid_t child_pid = popen2("grep 123", &child_stdin, &child_stdout);

if (!child_pid) {
    handle_error();
}

const char text = "1\n2\n123\n3";
ssize_t bytes_written = write(child_stdin, text, sizeof(text) - 1);

char buff[128];
ssize_t bytes_read = read(child_stdout, buff, sizeof(buff));

答案 5 :(得分:1)

在Windows中,不使用system(),而是使用CreateProcess,将输出重定向到管道并连接到管道。

我猜这种POSIX方式也有可能吗?

答案 6 :(得分:1)

函数popen()pclose()可能就是您要找的。

查看glibc manual的示例。

答案 7 :(得分:1)

实际上,我刚检查过,并且:

  1. popen是有问题的,因为这个过程是分叉的。因此,如果您需要等待shell命令执行,那么您就有可能错过它。就我而言,我的程序甚至在管道完成之前就关闭了。

  2. 我最终在linux上使用tar命令调用 system system 的返回值是 tar 的结果。

  3. 所以:如果你需要返回值,那么不仅不需要使用popen,它可能不会做你想要的。

答案 8 :(得分:1)

在此页面中:capture_the_output_of_a_child_process_in_c描述了使用popen与使用fork / exec / dup2 / STDOUT_FILENO方法的限制。

我遇到了问题capturing tshark output with popen

我猜这个限制可能是我的问题:

  

它返回一个stdio流而不是原始文件描述符   不适合异步处理输出。

如果我有其他方法的解决方案,我会回到这个答案。

答案 9 :(得分:0)

我不完全确定它在标准C中是可能的,因为两个不同的进程通常不共享内存空间。我能想到的最简单的方法是让第二个程序将其输出重定向到文本文件(programname&gt; textfile.txt),然后再读回该文本文件进行处理。但是,这可能不是最好的方式。