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
就无效了,那么就没有地方可以再检索命令的输出了,对吧?
这感觉像是个鸡和蛋的问题,但是我看过的代码都与上面的类似,所以我可能误会了一些东西。感谢您对此的解释。
答案 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
,并且command
在fread
的子进程中运行的EOF
终止后发生,所以使用fread是安全的(具有足够大的缓冲区)以捕获分配给EOF
的{{1}}的完整输出。
感谢任何人都可以证实我的推论和结论。