PHP 读取 shell_exec 实时输出

问题描述 投票:0回答:4

我只是在我的 Linux 服务器上试验 PHP 和

shell_exec
。这是一个非常酷的功能,到目前为止我真的很喜欢它。有没有办法查看命令运行时正在发生的实时输出?

例如,如果运行

ping stackoverflow.com
,当它ping目标地址时,每次ping时,都用PHP显示结果?这可能吗?

我很想看到缓冲区运行时的实时更新。也许这是不可能的,但肯定会很好。

这是我正在尝试的代码,我尝试过的每一种方法总是在命令完成后显示结果。

<?php

  $cmd = 'ping -c 10 127.0.0.1';

  $output = shell_exec($cmd);

  echo "<pre>$output</pre>";

?>

我尝试将

echo
部分放入循环中,但仍然没有成功。有人有什么建议让它在屏幕上显示实时输出而不是等到命令完成吗?

我尝试过

exec
shell_exec
system
passthru
。他们每个人都在完成后展示内容。除非我使用了错误的语法或者我没有正确设置循环。

php
4个回答
122
投票

要读取进程的输出,

popen()
是最佳选择。您的脚本将与程序并行运行,您可以通过读写它的输出/输入来与它交互,就好像它是一个文件一样。

但是,如果您只想将结果直接转储给用户,您可以切入正题并使用

passthru()
:

echo '<pre>';
passthru($cmd);
echo '</pre>';

如果你想在程序运行时显示输出,你可以这样做:

while (@ ob_end_flush()); // end all output buffers if any

$proc = popen($cmd, 'r');
echo '<pre>';
while (!feof($proc))
{
    echo fread($proc, 4096);
    @ flush();
}
echo '</pre>';

此代码应运行命令并在运行时将输出直接推送给最终用户。

更多有用信息

请注意,如果您正在使用会话,那么运行其中一个会话将阻止用户加载其他页面,因为会话强制不能发生并发请求。为了防止出现问题,请在循环之前调用

session_write_close()

如果您的服务器位于 nginx 网关后面,则 nginx 缓冲可能会破坏所需的行为。设置标头

header('X-Accel-Buffering: no');
来提示 nginx 它不应该这样做。由于首先发送标头,因此必须在发送任何数据之前在脚本开头调用此函数。


26
投票

首先,感谢 Havenard 的片段 - 它很有帮助!

Havenard 代码的稍微修改版本,我发现很有用。

<?php
/**
 * Execute the given command by displaying console output live to the user.
 *  @param  string  cmd          :  command to be executed
 *  @return array   exit_status  :  exit status of the executed command
 *                  output       :  console output of the executed command
 */
function liveExecuteCommand($cmd)
{

    while (@ ob_end_flush()); // end all output buffers if any

    $proc = popen("$cmd 2>&1 ; echo Exit status : $?", 'r');

    $live_output     = "";
    $complete_output = "";

    while (!feof($proc))
    {
        $live_output     = fread($proc, 4096);
        $complete_output = $complete_output . $live_output;
        echo "$live_output";
        @ flush();
    }

    pclose($proc);

    // get exit status
    preg_match('/[0-9]+$/', $complete_output, $matches);

    // return exit status and intended output
    return array (
                    'exit_status'  => intval($matches[0]),
                    'output'       => str_replace("Exit status : " . $matches[0], '', $complete_output)
                 );
}
?>

用法示例:

$result = liveExecuteCommand('ls -la');

if($result['exit_status'] === 0){
   // do something if command execution succeeds
} else {
    // do something on failure
}

7
投票

如果您愿意下载依赖项,Symfony 的处理器组件 可以执行此操作。我发现使用这个界面比自己用

popen()
passthru()
重新发明任何东西更干净。

这是由 Symfony 文档提供的:

您还可以将 Process 类与 foreach 构造一起使用来获取 生成时的输出。默认情况下,循环等待新的 进入下一次迭代之前的输出:

$process = new Process('ls -lsa');
$process->start();

foreach ($process as $type => $data) {
    if ($process::OUT === $type) {
        echo "\nRead from stdout: ".$data;
    } else { // $process::ERR === $type
        echo "\nRead from stderr: ".$data;
    }
}

作为警告,我遇到了一些问题,PHP 和 Nginx 在将输出发送到浏览器之前尝试缓冲输出。您可以通过在 php.ini 中关闭输出缓冲来禁用 PHP 中的输出缓冲:

output_buffering = off
。显然有一种方法可以在 Nginx 中禁用它,但我最终使用内置的 PHP 服务器进行测试以避免麻烦。 我在 Gitlab 上提供了完整的示例:

https://gitlab.com/hpierce1102/web-shell-output-streaming


0
投票

function pipe_exec($cmd) { $proc = proc_open($cmd, [ ['pipe', 'r'], ['file', "/dev/stdout", 'w'], ['file', "/dev/stderr", 'w'] ], $pipes); return proc_close($proc); } // Example usage. echo pipe_exec('ls') . PHP_EOL;

将 stdout 和 stderr 视为文件,并 
proc_open

处理对它们的写入。

该示例回显了 

ls

的预期输出,然后是下一行的退出代码。

    

© www.soinside.com 2019 - 2024. All rights reserved.