【问题标题】:PHP reading shell_exec live outputPHP 读取 shell_exec 实时输出
【发布时间】:2013-11-20 21:12:57
【问题描述】:

我只是在我的 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 部分置于循环中,但仍然没有成功。任何人对让它在屏幕上显示实时输出而不是等到命令完成有什么建议吗?

我试过execshell_execsystempassthru。他们每个人都在完成后显示内容。除非我使用了错误的语法或者我没有正确设置循环。

【问题讨论】:

  • 为什么不先在shell中执行文件?
  • 使用 passthru() 代替 shell_exec()
  • 我现在就研究 passthru。
  • popenfread
  • 没有运气弄清楚 passthru() 工作的语法。

标签: php


【解决方案1】:

要读取进程的输出,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>';

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

【讨论】:

  • 实际上我在研究时发现了一个与此类似的代码,但我一定是搞砸了。我尝试了大约一个小时才能让它工作。谢谢你,这正是我想要的完美。
  • 是的,诀窍是强制完全不发生缓冲。这段代码确保了这一点。通常 PHP 只有在累积至少 4KB 的数据时才会刷新输出。
  • 从 python 进程捕获输出时,您需要禁用输出缓冲:python -u /path/to/watch ... 或 PYTHONUNBUFFERED=1 watch ...
  • @ErikvandeVen 在这种情况下,您可能需要查看mkfifo()
  • 如果您在 Nginx 中,您还应该添加 header('X-Accel-Buffering: no'); 以确保 gzip 和 fastcgi_buffering 对单个响应关闭。我想知道为什么这段代码不起作用,这就是原因。
【解决方案2】:

首先,感谢 Havenard 的 sn-p - 它帮了很多忙!

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

【讨论】:

  • 这样的东西是否适用于整个 .sh 脚本?我想获得从 PHP 调用的 .sh 脚本的即时反馈?在这里查看我的问题:stackoverflow.com/questions/34999905/…
  • 它会起作用的。类似于:liveExecuteCommand("sh /path/to/script.sh");
  • 太棒了……正是我想要的。
  • 一个小建议:在 exit_status 上抛出一个 intval( ... ) 以将其作为整数而不是字符串返回。
  • 非常有用。请注意,您应该在您的 shell 命令中使用尾随分号,否则 exit_status 将始终为 0。
【解决方案3】:

如果您愿意下载依赖项,Symfony's processor component 会这样做。我找到了使用这个清洁器的界面,而不是用popen()passthru() 自己重新发明任何东西。

这是由 Symfony 文档提供的:

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

$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。显然有 a way to disable it in Nginx,但我最终使用 PHP 内置服务器进行测试以避免麻烦。

我在 Gitlab 上放了一个完整的例子:https://gitlab.com/hpierce1102/web-shell-output-streaming

【讨论】:

  • 是的,在 Nginx 中,您还应该执行 header('X-Accel-Buffering: no'); 以确保 gzip 和 fastcgi_buffering 对单个响应关闭,而不是对所有请求关闭它。我花了太长时间才弄清楚。
猜你喜欢
  • 2020-08-23
  • 1970-01-01
  • 2011-08-08
  • 1970-01-01
  • 2012-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多