为什么 exec() 默默地丢弃字节?

Why is exec() silently discarding bytes?

经过一番挣扎,我发现了一些我无法解释/解决的行为,所以在这里寻求帮助。在我们的服务器上(Ubuntu 16.04.5 LTS with PHP 7.0.30),我们使用了 'httpdocs' 之外的一些工具,这使用 exec() 调用以获取其输出。在这种情况下,它是一个 QRCode 生成器。

但是,某些二维码不会显示。我们从该工具获得了输出(正在输出 PNG 数据),但是当我们将其显示为图像时,由于某种原因它似乎已损坏。

经过大量调试后,我发现结果有时与工具的输出相差 1 或 2 个字节。

我使用下面的二维码 (12345) 进行了最后一次调试,这是一个 240 字节的文件。但是,最终输出的时候,长度好像是239字节,所以我们在某处丢失了一个字节?

我发现使用 exec(),我们正在创建一个输出数组。尾随空格,例如 \n,不包含在此数组中,因此 implode() 将数组粘合回字符串,如下所示:

<?php

$cmd = 'cat qr.png';
$output = array();
$exit_code = 0;

exec($cmd, $output, $exit_code);

if($exit_code === 0)
{
    header ("Content-type: image/png;");
    print implode(PHP_EOL, $output);
}
die();

但由于某种原因,在此过程中丢失了一个字节?我已经使用 shell_exec() 尝试了一些其他解决方案( 但这里没有 return_var,所以我们无法检查验证过程...

<?php

$command = 'cat qr.png';

$output = shell_exec($command);
if($output !== null)
{
    header ("Content-type: image/png;");
    print $output;
}
die();

... 并使用 passthru() ( 但这会直接输出内容,这是不希望的。实际代码中不可能像下面的示例中那样输出缓冲...)

<?php

$command = 'cat qr.png';
$return_var = 0;

ob_start();
passthru($command, $return_var);

if($return_var === 0)
{
    header ("Content-type: image/png;");
    $output = ob_get_clean();
    print $output;
}
die();

到目前为止,exec()-函数对我们来说总是工作得很好,产生了 $return_var AND $输出,但现在我' m 丢失字节。我已经尝试了 PHP_EOL 的一些变体,我们现在用作胶水(\n、\r 和 \n\r),但前两行-结尾,我仍然只有 239 个字节,最后一个字节,我最终有 241 个字节,所以 1 个字节太多了。

为什么我在这里丢失了一个字节?将 $output-array 转换回字符串的正确方法是什么?有没有办法通过内爆数组来取回 240 字节的输出?或者还有其他我还没有找到的函数来执行一个命令,它会给我输出以及 return_var?

根据exec的手册输入,函数returns最后一行输出。最后一行是否也作为 $output 的最后一个元素包括在内尚不清楚。

但更重要的是,手册指出:

Trailing whitespace, such as \n, is not included in this array.

我猜这是数组的每个元素(即输出的每行),但无论哪种方式,你的 whitespace 在这里都很重要,因为你没有处理文本 - 所有字节在这里都很重要且有意义。请记住,白色 space 不仅仅意味着 \n。它也可以表示 \t</code>(一个 space),例如。</p> <p>如果一行以 <code>F\t\n 结尾(大写字母 F,或任何其他非白色space 字符,一个制表符,然后是换行符),两者都是白色space 字符将从末尾删除。在执行 implode 操作时,您可能会将 \n 放回去,但您永远不会知道被剥离的 \t

这里要意识到的关键是 exec 期望处理纯文本而不是原始二进制数据

我建议不要使用 cat qr.png,而是使用 base64 qr.png 将二进制数据编码为 ASCII 字符串,然后在 PHP 中使用 base64_decode 对其进行解码.如果您不打算在现实中使用 cat,您仍然可以通过 base64 来传输 QR 生成命令的输出,如下所示:generate-qr-png |base64。在那种情况下不应该有任何重要的白色space,所以exec不会破坏任何东西。