在PHP中正确执行shell

Chr*_*ian 9 php proc-open k2f

问题

我正在使用一个proc_open()用来调用shell命令的函数.看起来我在做STDIO的方式是错误的,有时导致PHP或目标命令锁定.这是原始代码:

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) );
}
Run Code Online (Sandbox Code Playgroud)

大部分时间都有效,但这还不够,我想让它始终有效.

stream_get_contents()如果STDIO缓冲区超过4k的数据,则问题在于锁定.

测试用例

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__));
}
Run Code Online (Sandbox Code Playgroud)

我们输出一个字符串两次到STDERR和STDOUT,总长度为4120字节(超过4k).这会导致PHP双方都锁定.

显然,stream_select()是要走的路.我有以下代码:

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

    }
}
Run Code Online (Sandbox Code Playgroud)

唯一剩下的难题是处理STDIN(参见标记的行(???)). 我想出STDIN必须由调用我的函数提供execute().但是如果我根本不想使用STDIN呢?在上面的测试用例中,我没有要求输入,但我应该对STDIN做一些事情.

也就是说,上述方法仍然冻结stream_get_contents().我很不确定该做什么/下一步尝试.

积分

Jakob Truelsen提出了解决方案,并发现了原始问题.4k技巧也是他的想法.在此之前,我很困惑为什么函数工作正常(不知道这一切都取决于缓冲区大小).

Chr*_*ian 5

好吧,似乎一年过去了,忘了这件事还在等待!

但是,我把这个混乱包裹在一个很好的PHP类中,你可以在Github上找到.

剩下的主要问题是读取STDERR会导致PHP脚本被阻塞,因此它已被禁用.

从好的方面来说,感谢事件和一些不错的编码(我希望!),实际上可以与正在执行的进程(因此是类名InterExec)进行交互.所以你可以在PHP中使用bot风格的行为.