Я читаю потоки в PHP, использую proc_open и fgets ($ stdout), пытаюсь получить каждую строку по мере ее поступления.
Многие программы linux (менеджеры пакетов, wget, rsync) просто используют символ CR (возврат каретки) для строк, которые периодически обновляются «на месте», например, прогресс загрузки. Я хотел бы поймать эти обновления (как отдельные строки), как только они происходят.
На данный момент fgets ($ stdout) просто продолжает читать до LF, поэтому, когда прогресс идет очень медленно (например, большой файл), он просто продолжает читать до полного завершения, прежде чем вернуть все обновленные строки в виде одной длинной строки, в том числе КР.
Я попытался установить опцию «mac» для определения CR как конца строки:
ini_set('auto_detect_line_endings',true);
Но это не похоже на работу.
Теперь stream_get_line позволил бы мне устанавливать CR как разрывы строк, а не решение «поймать все», которое рассматривает и CRLF, CR и LF как разделители.
Конечно, я мог бы прочитать всю строку, разделить ее, используя различные методы PHP, и заменить все типы разрывов строк на LF, но это поток, и я хочу, чтобы PHP мог получать информацию о прогрессе, пока он еще работает.
Итак, мой вопрос:
Как я могу читать из канала STDOUT (из proc_open) до LF или же CR происходит, без необходимости ждать, пока вся линия не будет?
Заранее спасибо!
Я использовал класс фильтра Fleshgrinder, чтобы заменить \ r на \ n в потоке (см. Принятый ответ), и заменил fgets () на fgetc (), чтобы получить больше доступа в реальном времени к содержимому STDOUT:
$stdout = $proc->pipe(1);
stream_filter_register("EOL", "EOLStreamFilter");
stream_filter_append($stdout, "EOL");
while (($o = fgetc($stdout))!== false){
$out .= $o; // buffer the characters into line, until \n.
if ($o == "\n"){echo $out;$out='';} // can now easily wrap the $out lines in JSON
}
Используйте потоковый фильтр, чтобы нормализовать символы новой строки перед использованием потока. Я создал следующий код, который должен добиться цели, основываясь на примере со страницы руководства PHP на stream_filter_register
.
<?php
// https://php.net/php-user-filter
final class EOLStreamFilter extends php_user_filter {
public function filter($in, $out, &$consumed, $closing)
{
while ($bucket = stream_bucket_make_writeable($in)) {
$bucket->data = str_replace([ "\r\n", "\r" ], "\n", $bucket->data);
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
}
stream_filter_register("EOL", "EOLStreamFilter");
// Open stream …
stream_filter_append($yourStreamHandle, "EOL");
// Perform your work with normalized EOLs …
РЕДАКТИРОВАТЬ: Комментарий Марка Бейкера, размещенный на ваш вопрос, является правдой. Большинство дистрибутивов Linux используют линейный буфер для STDOUT
и вполне возможно, что Apple делает то же самое. С другой стороны, большинство STDERR
потоки не буферизированы. Вы можете попытаться перенаправить вывод программы на другой канал (например, STDERR
или любой другой) и посмотрим, повезет ли вам больше с этим.
Других решений пока нет …