Я написал этот простой скрипт, он генерирует одну строку вывода в секунду (generator.sh):
for i in {0..5}; do echo $i; sleep 1; done
Программа raku запустит этот скрипт и напечатает строки, как только они появятся:
my $proc = Proc::Async.new("sh", "generator.sh");
$proc.stdout.tap({ .print });
my $promise = $proc.start;
await $promise;
Все работает как положено: каждую секунду мы видим новую строку. Но перепишем генератор в раку (generator.raku):
for 0..5 { .say; sleep 1 }
И измените первую строку программы на это:
my $proc = Proc::Async.new("raku", "generator.raku");
Теперь что-то не так: сначала мы видим первую строку вывода ("0"), потом долгая пауза и, наконец, видим все оставшиеся строки вывода.
Я попытался получить вывод генераторов с помощью команды script:
script -c 'sh generator.sh' script-sh
script -c 'raku generator.raku' script-raku
А проанализировать их в шестнадцатеричном редакторе, и похоже, что они одинаковые: после каждой цифры следуют байты 0d и 0a.
Почему такая разница в работе с вроде бы одинаковыми генераторами? Мне нужно понять это, потому что я собираюсь запустить внешнюю программу и обрабатывать ее вывод онлайн.
Почему такая разница в работе с вроде бы одинаковыми генераторами?
Во-первых, что касается названия, проблема не в том, кто его читает, а в том, что он пишет.
Реализация ввода-вывода Raku проверяет, подключен ли STDOUT к TTY. Если это TTY, любой вывод немедленно записывается в дескриптор вывода. Однако, если это не TTY, то будет применена буферизация, что приведет к значительному повышению производительности, но за счет разбиения вывода на части по размеру буфера.
Если вы измените generator.raku, чтобы отключить буферизацию вывода:
$*OUT.out-buffer = False; for 0..5 { .say; sleep 1 }
Тогда результат будет виден сразу.
Мне нужно понять это, потому что я собираюсь запустить внешнюю программу и обрабатывать ее вывод онлайн.
Это будет проблемой только в том случае, если внешняя программа, которую вы запускаете, также имеет такую политику буферизации.
В дополнение к ответу @Jonathan Worthington. Хотя буферизация является проблемой на стороне записи, с этим можно справиться на стороне чтения. stdbuf, unbuffer, script можно использовать в Linux (см. это обсуждение). На windows мне помогает только winpty, который я нашел здесь.
Итак, если в рабочем каталоге есть файлы winpty.exe, winpty-agent.exe, winpty.dll, msys-2.0.dll, этот код можно использовать для запуска программы без буферизации:
my $proc = Proc::Async.new(<winpty.exe -Xallow-non-tty -Xplain raku generator.raku>);