代码之家  ›  专栏  ›  技术社区  ›  StoneThrow

从popen()ed文件*读取的输出是否在pclose()之前完成?

  •  0
  • StoneThrow  · 技术社区  · 6 年前

    pclose()

    函数的作用是:等待相关进程终止,并返回wait4(2)返回的命令的退出状态。

    我觉得这意味着如果 FILE* 创建人 popen() 已用类型打开 "r" 为了阅读 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;
    }
    

    我的问题是:如果我们只确定输出生成子进程在 关闭() fread() 完成了吗?但如果我们等到 , fp

    这感觉像是一个鸡和蛋的问题,但我已经看到类似的代码上面到处都是,所以我可能误解了什么。我很感激你对此的解释。

    4 回复  |  直到 6 年前
        1
  •  3
  •   n. m. could be an AI    6 年前

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

    当子进程关闭其管道末端时,将获得EOF。当它调用 close 明确地 出口。从那以后你的烟斗里再也出不来了。在获得EOF之后,您不知道进程是否已经终止,但是您确实知道它永远不会向管道写入任何内容。

    pclose 你闭上你的烟斗 等待孩子的终止。什么时候? P关闭 返回,你知道孩子已经终止了。

    如果你打电话 如果得不到EOF,并且孩子试图把东西写到管道的尽头,它就会失败(事实上它会得到一个EOF) SIGPIPE

    这里绝对没有鸡毛蒜皮的地方。

        2
  •  0
  •   tadman    6 年前

    阅读 the documentation for popen 更仔细地说:

    这个 pclose() 函数应关闭由打开的流 popen() 等待命令终止

    它阻塞并等待。

        3
  •  0
  •   StoneThrow    6 年前

    在进一步研究这个问题的过程中,我学到了一些东西,我想这可以回答我的问题:

    fread FILE* 退回人 popen pclose . 假设缓冲区 如果足够大,您就不会“错过”输出所产生的 command 给予 波本 .

    回去仔细考虑什么 size * nmemb )已读取字节 或文件结尾

    感谢 C - pipe without using popen ,我更明白什么 引擎盖下有:它有一个 dup2 重定向其 stdout 到它使用的管道的写入端。重要的是:它执行某种形式的 exec 执行指定的 命令 在这个子进程终止之后,它打开的文件描述符,包括 1 ( 标准 )已关闭 命令 标准 已关闭。

    接下来,我回去更仔细地思考 EOF 真的是在这种情况下。一开始,我有一种松散的、愚蠢的、错误的印象 试着读一本书 文件* ". 事实并非如此:如上所述: 弗雷德 EOF公司 或遇到错误。这个 退回人 波本 来自一个 fdopen 使用的管道的读取端 波本 ,所以 EOF公司 标准 -那是什么 重复2 与管道的写入端绑定-已关闭

    所以,最后我们得到的是: 命令 FD打开 他被解雇了 传递给 弗雷德 弗雷德 的缓冲区足够大), 弗雷德 将阻止,直到 EOF公司 的管道因执行 命令 . 也就是说,因为 弗雷德 EOF公司 遇到,以及 发生在 命令 -磨合 的子进程-终止时,可以安全地使用fread(具有足够大的缓冲区)来捕获 给予

    如果有人能证实我的推论和结论,我将不胜感激。

        4
  •  0
  •   tunglt    6 年前

    popen()只是fork、dup2、execv、fdopen等系列的一个快捷方式,它可以让我们通过文件流操作轻松访问子STDOUT、STDIN。

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

    为了避免数据丢失,我们将只在知道子进程已经终止时调用pclose():fgets()调用将返回NULL或fread()从阻塞返回,共享流到达末尾,EOF()将返回true。

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

    注意,返回流上的所有文件操作都在阻塞模式下工作,流是打开的,没有非阻塞标志。当子进程挂起或终止时,fread()会被永远阻止,因此只能对受信任的程序使用popen()。

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

    定期对WNOHANG使用waitpid(),以查看子进程是否已终止。