C++ 以最佳方式从 system() 命令中捕获标准输出

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/125828/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 13:03:27  来源:igfitidea点击:

Capturing stdout from a system() command optimally

c++csystemstdout

提问by SinisterDex

I'm trying to start an external application through system()- for example, system("ls"). I would like to capture its output as it happens so I can send it to another function for further processing. What's the best way to do that in C/C++?

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

回答by jkramer

From the popen manual:

从 popen 手册:

#include <stdio.h>

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

int pclose(FILE *stream);

回答by jkramer

Try the popen() function. It executes a command, like system(), but directs the output into a new file. A pointer to the stream is returned.

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

回答by wnoise

EDIT: misread question as wanting to pass output to another program, not another function. popen() is almost certainly what you want.

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

System gives you full access to the shell. If you want to continue using it, you can redirect it's output to a temporary file, by system("ls > tempfile.txt"), but choosing a secure temporary file is a pain. Or, you can even redirect it through another program: system("ls | otherprogram");

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

Some may recommend the popen() command. This is what you want if you can process the output yourself:

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

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

which will give you a FILE pointer you can read from with the command's output on it.

这将为您提供一个 FILE 指针,您可以从中读取命令的输出。

You can also use the pipe() call to create a connection in combination with fork() to create new processes, dup2() to change the standard input and output of them, exec() to run the new programs, and wait() in the main program to wait for them. This is just setting up the pipeline much like the shell would. See the pipe() man page for details and an example.

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

回答by Will

The functions popen()and such don't redirect stderr and such; I wrote popen3()for that purpose.

函数popen()等不会重定向 stderr 等;我popen3()就是为此而写的。

Here's a bowdlerised version of my popen3():

这是我的 popen3() 的 bowdlerised 版本:

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

回答by GreenScape

The most efficient way is to use stdoutfile descriptor directly, bypassing FILEstream:

最有效的方法是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;
}

To read output from childuse popen2()like this:

要从孩子使用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));

To both write and read:

写和读:

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

回答by mousomer

Actually, I just checked, and:

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

  1. popen is problematic, because the process is forked. So if you need to wait for the shell command to execute, then you're in danger of missing it. In my case, my program closed even before the pipe got to do it's work.

  2. I ended up using systemcall with tar command on linux. The return value from systemwas the result of tar.

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

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

So: if you need the return value, then not no only is there no need to use popen, it probably won't do what you want.

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

回答by nephewtom

In this page: capture_the_output_of_a_child_process_in_cdescribes the limitations of using popen vs. using fork/exec/dup2/STDOUT_FILENO approach.

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

I'm having problems capturing tsharkoutput with popen.

我在使用 popen捕获tshark输出时遇到问题。

And I'm guessing that this limitation might be my problem:

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

It returns a stdio stream as opposed to a raw file descriptor, which is unsuitable for handling the output asynchronously.

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

I'll come back to this answer if I have a solution with the other approach.

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

回答by Andrew Edgecombe

The functions popen()and pclose()could be what you're looking for.

功能popen()pclose()可能是您正在寻找的。

Take a look at the glibc manualfor an example.

glibc 手册为例。

回答by shoosh

In Windows, instead of using system(), use CreateProcess, redirect the output to a pipe and connect to the pipe.

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

I'm guessing this is also possible in some POSIX way?

我猜这在某些 POSIX 方式中也是可能的?

回答by Nicholas Flynt

I'm not entirely certain that its possible in standard C, as two different processes don't typically share memory space. The simplest way I can think of to do it would be to have the second program redirect its output to a text file (programname > textfile.txt) and then read that text file back in for processing. However, that may not be the best way.

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