问题描述
我使用的函数利用proc_open()
来调用Shell命令.看来我做STDIO的方式是错误的,有时会导致PHP或目标命令锁定.这是原始代码:
I was using a function that made use of proc_open()
to invoke shell commands. It seems the way I was doing STDIO was wrong and sometimes caused PHP or the target command to lock up. This is the original code:
function execute($cmd, $stdin=null){
$proc=proc_open($cmd,array(0=>array('pipe','r'),1=>array('pipe','w'),2=>array('pipe','w')),$pipes);
fwrite($pipes[0],$stdin); fclose($pipes[0]);
$stdout=stream_get_contents($pipes[1]); fclose($pipes[1]);
$stderr=stream_get_contents($pipes[2]); fclose($pipes[2]);
return array( 'stdout'=>$stdout, 'stderr'=>$stderr, 'return'=>proc_close($proc) );
}
大多数时间它都可以工作,但这还不够,我想使其始终工作.
It works most of the time, but that is not enough, I want to make it work always.
问题出在stream_get_contents()
,如果STDIO缓冲区的数据超过4k,则锁定.
The issue lies in stream_get_contents()
locking up if the STDIO buffers exceed 4k of data.
function out($data){
file_put_contents('php://stdout',$data);
}
function err($data){
file_put_contents('php://stderr',$data);
}
if(isset($argc)){
// RUN CLI TESTCASE
out(str_repeat('o',1030);
err(str_repeat('e',1030);
out(str_repeat('O',1030);
err(str_repeat('E',1030);
die(128); // to test return error code
}else{
// RUN EXECUTION TEST CASE
$res=execute('php -f '.escapeshellarg(__FILE__));
}
我们向STDERR和STDOUT输出两次字符串,其总长度为4120字节(超过4k).这会导致PHP两侧都锁住.
We output a string twice to STDERR and STDOUT with the combined length of 4120 bytes (exceeding 4k). This causes PHP to lock up on both sides.
显然,stream_select()
是可行的方式.我有以下代码:
Apparently, stream_select()
is the way to go. I have the following code:
function execute($cmd,$stdin=null,$timeout=20000){
$proc=proc_open($cmd,array(0=>array('pipe','r'),1=>array('pipe','w'),2=>array('pipe','w')),$pipes);
$write = array($pipes[0]);
$read = array($pipes[1], $pipes[2]);
$except = null;
$stdout = '';
$stderr = '';
while($r = stream_select($read, $write, $except, null, $timeout)){
foreach($read as $stream){
// handle STDOUT
if($stream===$pipes[1])
/*...*/ $stdout.=stream_get_contents($stream);
// handle STDERR
if($stream===$pipes[2])
/*...*/ $stderr.=stream_get_contents($stream);
}
// Handle STDIN (???)
if(isset($write[0])) ;
// the following code is temporary
$n=isset($n) ? $n+1 : 0; if($n>10)break; // break while loop after 10 iterations
}
}
唯一剩下的难题就是处理STDIN(请参见标记为(???)
的行).我发现STDIN必须由调用我的函数execute()
的任何对象提供.但是,如果我根本不想使用STDIN怎么办?在上面的测试用例中,我没有要求输入,但是我应该对STDIN做一些事情.
The only remaining piece of the puzzle is handling STDIN (see the line marked (???)
).I figured out STDIN must be supplied by whatever is calling my function, execute()
. But what if I don't want to use STDIN at all? In my testcase, above, I didn't ask for input, yet I'm supposed to do something to STDIN.
也就是说,上述方法仍然在冻结.我不太确定下一步该怎么做.
That said, the above approach still freezes at stream_get_contents()
. I'm quite unsure what to do/try next.
Jakob Truelsen提出了解决方案,并发现了原始问题. 4k技巧也是他的想法.在此之前,我对功能为何运行良好感到困惑(不知道这一切都取决于缓冲区的大小).
The solution was suggested by Jakob Truelsen, as well as discovering the original issue. The 4k tip was also his idea. Prior to this I was puzzled as to why the function was working fine (didn't know it all depended on buffer size).
推荐答案
好吧,似乎已经过去了一年,却忘记了这件事仍在等待中!
Well, seems a year passed and forgot this thing is still pending!
但是,我将这个混乱包裹在一个不错的PHP类中,您可以在Github上找到 .
However, I wrapped up this mess in a nice PHP class which you can find on Github.
剩下的主要问题是读取STDERR会导致PHP脚本被阻止,因此已被禁用.
The main remaining problem is that reading STDERR causes the PHP script to block, so it has been disabled.
从好的方面来说,多亏了事件和一些不错的编码(我希望!),一个人实际上可以与正在执行的进程进行交互(因此,类名称为InterExec
).因此,您可以在PHP中拥有机器人式的行为.
On the bright side, thanks to events and some nice coding (I hope!), one can actually interact with the process being executed (hence the class name, InterExec
). So you can have bot-style behavior in PHP.
这篇关于在PHP中正确执行Shell的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!