PHP: пытаюсь заставить fgets() срабатывать как на CRLF, CR, так и на LF


Я читаю потоки на PHP, используя proc_open и fgets($stdout), пытаясь получить каждую строку по мере ее поступления.

Многие программы Linux (менеджеры пакетов, wget, rsync) просто используют символ CR (возврат каретки) для строк, которые периодически обновляются "на месте", например, ход загрузки. Я хотел бы отслеживать эти обновления (в виде отдельных строк), как только они произойдут.

На данный момент fgets($stdout) просто продолжает чтение до LF, поэтому, когда прогресс идет очень медленно (большой файл для пример) он просто продолжает чтение, пока не будет полностью завершен, прежде чем возвращать все обновленные строки в виде одной длинной строки, включая CRs.

Я попытался установить опцию "mac", чтобы определять CRS как окончания строк:

ini_set('auto_detect_line_endings',true); 

Но, похоже, это не работает.

Теперь stream_get_line позволил бы мне установить CRS в качестве разрывов строк, но не решение "охватить все", которое рассматривает как CRLF, CR и LF в качестве разделителей.

Я, конечно, мог бы прочитать всю строку целиком, разделить ее с помощью различные методы PHP и заменяют все типы разрывов строк на LFS, но это поток, и я хочу, чтобы PHP мог получать информацию о прогрессе, пока он все еще работает.

Итак, мой вопрос:

Как я могу читать из канала STDOUT (из proc_open) до тех пор, пока не произойдет LF или CR, не дожидаясь, пока будет введена вся строка?

Заранее спасибо!

Решение:

Я использовал класс фильтра Fleshgrinder, чтобы заменить \r на \n в потоке (см. Принятый ответ) и заменил fgets() на fgetc(), чтобы получить больше доступа "в реальном времени" к содержимому стандартного вывода:

$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
}
Author: Fx32, 2015-01-12

1 answers

Используйте фильтр потока, чтобы нормализовать символы новой строки перед использованием потока. Я создал следующий код, который должен выполнить трюк на основе примера со страницы руководства 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 или любой другой) и посмотреть, повезет ли вам в этом.

 1
Author: Fleshgrinder, 2015-01-12 20:16:23