以最佳方式从 system() 命令捕获标准输出 [重复]

Posted

技术标签:

【中文标题】以最佳方式从 system() 命令捕获标准输出 [重复]【英文标题】:Capturing stdout from a system() command optimally [duplicate] 【发布时间】:2010-09-12 15:42:11 【问题描述】:

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

【问题讨论】:

最优是什么意思?根据我的回答,我会说最佳可能取决于每种情况。 fork/exec/dup2/STDOUT_FILENO 方法可能适合特殊情况。 【参考方案1】:

来自 popen 手册:

#include <stdio.h>

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

int pclose(FILE *stream);

【讨论】:

FILE *stream = popen(const char *command, const char *type); 我想补充一点,这只适用于 Posix 系统。在我的 Windows 7 版本上,没有 popen 功能,但是有一个 _popen 可以工作。【参考方案2】:

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

【讨论】:

【参考方案3】:

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

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

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

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

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

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

【讨论】:

【参考方案4】:

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;

【讨论】:

【参考方案5】:

最有效的方法是直接使用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));

【讨论】:

dup2(::open("/dev/null", O_RDONLY), 2); 是否正确?似乎更有逻辑 dup2(::open("/dev/null", O_WRONLY), 2); @MelVisoMartinez,是的,会更有效。【参考方案6】:

popen()pclose() 函数可能正是您想要的。

以glibc manual 为例。

【讨论】:

【参考方案7】:

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

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

【讨论】:

【参考方案8】:

其实我刚查了一下,然后:

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

    我最终在 Linux 上使用带有 tar 命令的 system 调用。 system 的返回值是 tar 的结果。

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

【讨论】:

问题要求捕获输出,而不是退出代码。所以我认为你提到的问题不会出现。 这就是 wait(...) 系列系统调用的用途。他们等待您的子进程完成,因此您可以获得它的输出。 “人 -s2 等待”【参考方案9】:

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

我遇到了问题capturing tshark output with popen。

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

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

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

【讨论】:

【参考方案10】:

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

【讨论】:

以上是关于以最佳方式从 system() 命令捕获标准输出 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

是否可以从管道中的 sh DSL 命令捕获标准输出

如何使用 Python 在日志文件中复制/捕获标准输出

如何在 elisp 中捕获 shell 命令的标准输出?

SharpPcap - 从标准输出捕获

捕获和处理来自 R 的外部命令的输出

在单元测试中捕获调试窗口输出?