从popen()的FILE *中读取的输出是否在pclose()之前完成?

时间:2018-09-06 20:31:20

标签: c++ posix popen pclose

pclose()的手册页中说:

  

pclose()函数等待关联的进程终止,并返回由wait4(2)返回的命令的退出状态。

我觉得这意味着如果用FILE*类型打开由popen()创建的关联"r"以读取command的输出,那么您在调用pclose()之后之前,还不确定输出是否已经完成。但是在pclose()之后,封闭的FILE*必须肯定是无效的,那么如何确定已阅读command的整个输出?

为举例说明我的问题,请考虑以下代码:

// main.cpp

#include <iostream>
#include <cstdio>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h>

int main( int argc, char* argv[] )
{
  FILE* fp = popen( "someExecutableThatTakesALongTime", "r" );
  if ( ! fp )
  {
    std::cout << "popen failed: " << errno << " " << strerror( errno )
              << std::endl;
    return 1;
  }

  char buf[512] = { 0 };
  fread( buf, sizeof buf, 1, fp );
  std::cout << buf << std::endl;

  // If we're only certain the output-producing process has terminated after the
  // following pclose(), how do we know the content retrieved above with fread()
  // is complete?
  int r = pclose( fp );

  // But if we wait until after the above pclose(), fp is invalid, so
  // there's nowhere from which we could retrieve the command's output anymore,
  // right?

  std::cout << "exit status: " << WEXITSTATUS( r ) << std::endl;

  return 0;
}

如上所述,我的问题是:如果仅确定在pclose()之后会产生输出的子进程,那么我们如何知道用fread()检索到的内容是完整的呢?但是,如果我们等到pclose()之后,fp就无效了,那么就没有地方可以再检索命令的输出了,对吧?

这感觉像是个鸡和蛋的问题,但是我看过的代码都与上面的类似,所以我可能误会了一些东西。感谢您对此的解释。

4 个答案:

答案 0 :(得分:3)

TL; DR执行摘要:我们怎么知道用fread()检索的内容是完整的? —我们有一个EOF。

当子进程关闭管道末端时,您将得到一个EOF。当它显式调用close退出时,可能会发生这种情况。在那之后,一切都不会从管道末端出来。获得EOF后,您不知道该进程是否已终止,但是您确实知道它永远不会向管道写入任何内容。

通过调用pclose,您可以关闭管道的末端 ,并等待子项的终止。 pclose返回时,您知道孩子已经终止。

如果在没有获得EOF的情况下调用pclose,并且孩子试图在管道的末端写入内容,则它将失败(实际上它将得到SIGPIPE并可能死亡)。

这里绝对没有空间容纳任何鸡肉和鸡蛋。

答案 1 :(得分:0)

更仔细地阅读the documentation for popen

  

AddEditEmojiTableViewController函数应关闭由pclose()打开的流,等待命令终止,并返回正在运行的进程的终止状态。命令语言解释器。

它阻止并等待。

答案 2 :(得分:0)

popen()只是fork,dup2,execv,fdopen等系列的快捷方式。它将使我们能够通过文件流操作轻松访问子STDOUT,STDIN。

popen()之后,父进程和子进程都独立执行。 pclose()不是“ kill”函数,它只是等待子进程终止。由于这是一个阻塞函数,执行pclose()时生成的输出数据可能会丢失。

为避免丢失此数据,仅当知道子进程已终止时才调用pclose():fgets()调用将返回NULL或fread()从阻塞返回,共享流到达末尾且EOF ()将返回true。

这里是将popen()与fread()结合使用的示例。如果执行过程失败,则此函数返回-1;如果确定,则返回0。子输出数据将在szResult中返回。

int exec_command( const char * szCmd, std::string & szResult ){

    printf("Execute commande : [%s]\n", szCmd );

    FILE * pFile = popen( szCmd, "r");
    if(!pFile){
            printf("Execute commande : [%s] FAILED !\n", szCmd );
            return -1;
    }

    char buf[256];

    //check if the output stream is ended.
    while( !feof(pFile) ){

        //try to read 255 bytes from the stream, this operation is BLOCKING ...
        int nRead = fread(buf, 1, 255, pFile);

        //there are something or nothing to read because the stream is closed or the program catch an error signal
        if( nRead > 0 ){
            buf[nRead] = '\0';
            szResult += buf;
        }
    }

    //the child process is already terminated. Clean it up or we have an other zoombie in the process table.
    pclose(pFile); 

    printf("Exec command [%s] return : \n[%s]\n",  szCmd, szResult.c_str() );
    return 0;
}

请注意,返回流上的所有文件操作均以BLOCKING模式工作,该流是打开的,没有O_NONBLOCK标志。当子进程挂起并终止神经时,fread()可能会永远被阻止,因此只能对受信任的程序使用popen()。

要对子进程进行更多控制并避免文件阻塞操作,我们应该自己使用fork / vfork / execlv等,用O_NONBLOCK标志修改打开的属性的管道,不时使用poll()或select()来确定是否有一些数据,然后使用read()函数从管道中读取。

定期与WNOHANG一起使用waitpid()来查看子进程是否已终止。

答案 3 :(得分:0)

在进一步研究此问题时,我学到了一些东西,我想回答了我的问题:

从本质上讲:是的,从fread返回的FILE*popen之前,pclose是安全的。假设分配给fread的缓冲区足够大,那么您不会“错过”分配给command的{​​{1}}生成的输出。

回头仔细考虑popen的作用:它有效地阻塞直到(fread * size)个字节被读取或文件结束 (或错误)。

由于C - pipe without using popen,我更了解nmemb的作用:它执行popen将其dup2重定向到它的管道的写端用途。重要的是:它执行某种形式的stdout,以在派生进程中执行指定的exec,并且在此子进程终止后,其开放文件描述符包括command({{ 1}})已关闭。即指定的1的终止是子进程的stdout关闭的条件。

接下来,我回过头来,更仔细地考虑了command在这种情况下的真正含义。刚开始,我的印象是松散的和错误的印象,即“ stdout试图尽可能快地从EOF进行读取,并在读取最后一个字节后返回/取消阻塞”。事实并非如此:如上所述:fread将读取/阻止,直到读取了目标字节数或遇到FILE*或错误为止。 fread返回的EOF来自FILE*使用的管道的读取端的popen,因此当fdopen 出现在子进程'popen-用管道的写端进行EOF-关闭

因此,最后我们拥有的是:stdout创建一个管道,其写入端获取运行指定的dup2的子进程的输出,并且如果popen则其读取结束ed传递给command的{​​{1}}。 (假设fdopen的缓冲区足够大),FILE*将阻塞直到发生fread,这对应于fread的管道的写端因终止而关闭正在执行的fread中。即因为EOF一直阻塞,直到遇到popen,并且commandfread的子进程中运行的EOF终止后发生,所以使用fread是安全的(具有足够大的缓冲区)以捕获分配给EOF的{​​{1}}的完整输出。

感谢任何人都可以证实我的推论和结论。