【问题标题】:Shell output not being fully retrieved by PHP!PHP 未完全检索 Shell 输出!
【发布时间】:2010-10-06 11:37:16
【问题描述】:

我有一个执行 shell 命令的 PHP 脚本:

$handle = popen('python last', 'r');
$read = fread($handle, 4096);
print_r($read);
pclose($handle);

我回显了 shell 输出的输出。当我在命令中运行它时,我会得到如下信息:

[root@localhost tester]# python last
[last] ZVZX-W3vo9I: Downloading video webpage
[last] ZVZX-W3vo9I: Extracting video information
[last] ZVZX-W3vo9I: URL: x
[download] Destination: here.flv
[download]   0.0% of 10.09M at     ---b/s ETA --:--
[download]   0.0% of 10.09M at   22.24k/s ETA 07:44
[download]   0.0% of 10.09M at   66.52k/s ETA 02:35
[download]   0.1% of 10.09M at  154.49k/s ETA 01:06
[download]   0.1% of 10.09M at  162.45k/s ETA 01:03

但是,当我从 PHP 运行相同的命令时,我会得到以下输出:

[last] ZVZX-W3vo9I: Downloading video webpage
[last] ZVZX-W3vo9I: Extracting video information
[last] ZVZX-W3vo9I: URL: x
[download] Destination: here.flv

如您所见,底部的位丢失了,这是我需要的位!之前的问题是百分比在同一行上更新,但现在我更改了我的 Python 脚本,以便它创建一个新行。但这有所作为! :(

这个问题与to this one有关。

感谢您的帮助。

更新

需要重定向输出“2>&1”。 Arul 很幸运:P 因为我错过了选择属于 Pax 的一个真实答案的最后期限!

【问题讨论】:

  • Abs,你有机会检查一下stdout和stderr的捕获吗?
  • @Pax:在他询问后的半小时内,他已经得到了关于 STDERR 的答案。似乎是别的东西。
  • 答案中还有其他内容,@stesch,这只是要尝试的第一件事,Q'er 没有留下任何迹象表明他们已经尝试过,这就是我离开的原因提醒。
  • 2 月 9 日回答了“2>&1”问题。

标签: php linux shell command-line


【解决方案1】:

您只读取管道的前 4,096 个字节,您需要将 fread/print_r 放入循环中,并使用 feof 函数检查文件结尾。

$handle = popen('python last', 'r');

while(!feof($handle))
{
    print_r(fread($handle, 4096));
} 

pclose($handle);

【讨论】:

  • 但他只得到了大约 155 个字节。
  • 这是在 PHP 中处理远程文件时的标准行为。如文档中所述:“......读取将在数据包可用后停止。这意味着您应该按照以下示例将数据一起收集到块中。”
  • stesch 的观点可能是答案开始于“您只从管道中读取前 4,096 个字节”,这有点误导。
【解决方案2】:

你能在while循环中读取而不是使用fread()吗?

while( !feof($handle) )
    echo fgets($handle);

你可能还需要flush()。

【讨论】:

    【解决方案3】:

    你看到了什么

    python last | less
    

    ?

    也许你想要的输出是在 STDERR 上发出的。然后你必须这样开始:

    $handle = popen('python last 2>&1', 'r');
    

    2>&1 将 STDERR 引导到 STDOUT,您正在使用 popen 捕获它。

    【讨论】:

    • 我用 | 什么也没看到少 - 这是什么意思?
    【解决方案4】:

    第一步是查看输出的去向。我要做的第一件事是选择一个稍微小一点的文件,这样您每次测试就不会等待 7 分钟。

    第 1 步/查看内容在 shell 中的写入位置。执行命令python last >/tmp/stdout 2>/tmp/stderr 然后查看这两个文件。理想情况下,所有内容都将写入标准输出,但情况可能并非如此。这为您提供了脚本的基线行为。

    第 2 步/使用$handle = popen('python last >/tmp/stdout 2>/tmp/stderr', 'r'); 从 PHP 运行时执行相同的操作。在这种情况下,您的 PHP 脚本可能不会返回任何内容,但仍应填充文件。这将在非终端环境中运行时捕获任何更改的行为。

    如果一些输出到stderr,那么解决方案应该像$handle = popen('python last 2>&1', 'r');一样简单

    另外,PHP 的文档说明(我的粗体字):

    Returns a file pointer identical to that returned by fopen(), except that it is unidirectional (may only be used for reading or writing) and must be closed with pclose(). This pointer may be used with fgets(), fgetss(), and fwrite().

    所以我不确定您是否应该使用fread(),尽管它显示在其中一个示例中。不过,我认为基于行的输入更符合您想要实现的目标。

    不管这一切,您应该循环读取输出,以确保您可以在超过 4K 时获得输出,例如:

    $handle = popen ('python last 2>&1', 'r');
    if ($handle) {
        while(! feof ($handle)) {
            $read = fgets ($handle);
            echo $read;
        } 
        pclose ($handle);
    }
    

    需要注意的另一件事是,如果您的输出将发送到浏览器并且花费的时间太长,则浏览器本身可能会超时,因为它认为服务器端连接已经消失。如果您发现一个小文件有效,而您的 10M/1 分钟文件无效,则可能是这种情况。您可以尝试flush(),但并非所有浏览器都支持这一点。

    【讨论】:

    • 这看起来很有希望。明天晚上我回到自己的机器上时会对其进行测试。
    • 效果很好!我需要重定向输出“2>&1”。抱歉,赏金在我没有做出选择的情况下结束了,它是随机分配的。我欠你150,如果需要我会随机代表你!!!
    【解决方案5】:

    这样做要容易得多:

    $output = `python last`;
    var_dump($output);
    

    'ticks' (`) 将执行该行并捕获输出。这是一个测试示例:

    文件test.php:

    <?php
    echo "PHP Output Test 1\n";
    echo "PHP Output Test 2\n";
    echo "PHP Output Test 3\n";
    echo "PHP Output Test 4\n";
    echo "PHP Output Test 5\n";
    ?>
    

    文件捕获.php:

    <?php
    $output = `php test.php`;
    var_dump($output);
    ?>
    

    php capture.php的输出:

    string(80) "Test PHP Script
    Test PHP Script
    Test PHP Script
    Test PHP Script
    Test PHP Script
    "
    

    然后您可以根据换行符将输出拆分为数组:

    $outputArray = explode('\n', $output);
    

    使用proc_open(),它比popen给你更多的控制权,因为你可以指定你想在哪里处理stdin、stdout和stderr。

    或者你可以打开STDINphp://stdin然后通过管道连接到php:

    ? python last | php script.php
    

    我会选择选项 1 并使用反引号,这是最简单的方法。

    【讨论】:

    • 您的方法的问题是数据没有流式传输。相反,它会在脚本完成后显示他想要的所有信息。
    • 你说得对,我需要实时查看shell输出以收集进度。
    【解决方案6】:

    当输出不发送到 tty 时,如果发现进度报告被省略,我不会感到惊讶。也就是说,PHP 正在捕获发送的所有内容,但没有发送进度报告。

    根据输出的位置而不同的命令有很多先例 - 从古老的 ls 命令开始。

    当然,如果您编写了正在运行的 Python 脚本,那么这不太可能是造成问题的原因。

    你如何验证这个假设是否有效?好吧,您可以从在命令行运行脚本开始,标准输出到一个文件,标准错误到另一个文件。如果您在其中一个文件中看到进度信息,您就会对正在发生的事情有更多的了解。如果您仍然在屏幕上看到进度信息,那么脚本可能正在将进度信息回显到/dev/tty,但是当 PHP 运行它时,该进程没有/dev/tty。如果您根本看不到进度信息(在屏幕上或文件中),那么我最初的假设可能已经得到验证。

    【讨论】:

    • wget 这样做。根据它是 tty,将打印实时进度条或下载后统计信息(都打印到 stderr、iirc)。不清楚“last”是做什么的,但输出看起来类似于 wget。
    【解决方案7】:

    如果您只是想在终端窗口中显示 python 命令的进度,那么我建议您改为:

    <?php
    system("python last > `tty`");
    

    你不需要捕获输出,用户甚至可以在不中止 PHP 脚本的情况下 Ctrl+C 下载。

    【讨论】:

    • 我正在尝试在网络浏览器中实时显示输出命令。
    • 我试图运行交互式 mysql 命令行 shell。这成功了!非常感谢。
    【解决方案8】:

    尝试在 $handle 上运行 stream_get_contents()。在您不知道要检索的确切大小的资源中,这是一种更好的处理方式。

    【讨论】:

      【解决方案9】:

      您错过了同花顺跟注。 (在您的 python 应用程序中,也可能在您的 php 应用程序中)

      这是因为当您以交互方式(从 cmdline)使用标准流 stdin/stdout 时,它们处于行缓冲模式(简而言之,系统在每个新行上刷新),但是当您从程序中调用它时,流在全缓冲模式(系统缓冲区满后才输出)。

      更多信息在这里buffering in standard streams

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-07-29
        • 1970-01-01
        • 2014-07-28
        • 1970-01-01
        • 2012-06-29
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多