Отличия STDIN дочернего процесса Windows / Linux

Я создал простой скрипт обработки текста на работе, который будет использоваться другой программой. Когда я закончил, кто-то вспомнил, что скрипт не должен блокировать STDIN / STDOUT, чтобы инструмент, использующий его, работал правильно, и изменил скрипт соответствующим образом. Скрипт открывает cat * nix в подпроцессе через IPC::Open2 и печатает в него STDIN, читает его обратно. а затем обрабатывает и распечатывает его в STDOUT. Я понятия не имею, как это делает скрипт неблокирующим, но, видимо, он работал.

Я хотел, чтобы она работала и в Windows, поэтому заменил cat на type CON, простую команду Windows для печати STDIN. Ниже приведен пример сценария:

use strict;
use warnings;
use IO::Handle;
use IPC::Open2;

my $command = ($^O eq 'MSWin32') ? 'type CON' : 'cat';

my ( $com_reader, $com_writer ) = ( IO::Handle->new, IO::Handle->new );
open2( $com_reader, $com_writer, $command );
# input
while (<STDIN>) {
    print "first line: $_";
    print $com_writer "$_";
    my $line = <$com_reader>;
    # ...process $line...

    print "next line: $line";
}

Однако результаты совсем другие. В Windows потоки STDIN для основного сценария и дочернего сценария кажутся разными, в то время как в Linux они одинаковы. В Windows (я набираю 1 и 2 на отдельных строках ввода):

>perl test.pl
>1
first line: 1
>2
next line: 2
>1
>2
first line: 2
next line: 1
>1
>2
first line: 2
next line: 1
>1
>2
first line: 2
next line: 1

В Linux (тот же ввод):

>perl test.pl
>1
first line: 1
next line: 1
>2
first line: 2
next line: 2
>1
first line: 1
next line: 1
>2
first line: 2
next line: 2

Почему результат отличается, и как я могу заставить поведение Windows соответствовать поведению Linux? Кроме того, почему этот трюк «открыть кота в подпроцессе и передать его через конвейер» вообще работает?


person Nate Glenn    schedule 17.06.2015    source источник
comment
@Harry Johnston, Двустороннее интерактивное общение с дочерним процессом.   -  person ikegami    schedule 17.06.2015
comment
@harry Johnston, на практике программа будет посложнее.   -  person ikegami    schedule 17.06.2015
comment
@Nate: кстати, причина того, что сценарий в том виде, в каком он написан, ведет себя странно, заключается в том, что сценарий и дочерний процесс конкурируют за один и тот же источник ввода.   -  person Harry Johnston    schedule 17.06.2015
comment
@Harry Johnston, они не должны использовать один и тот же источник входного сигнала. Даже если вы это исправите, у него все равно будут проблемы.   -  person ikegami    schedule 17.06.2015
comment
@ikegami: действительно не похоже, что cat заменяет более сложную программу. Оператору OP сказали, что передача данных в cat и обратно делает скрипт неблокирующим. Я совершенно уверен, что это ерунда.   -  person Harry Johnston    schedule 17.06.2015
comment
Нейт, я предполагаю, что передавать данные по конвейеру cat и обратно совершенно не нужно, и все, что вам действительно нужно было сделать, чтобы скрипт заработал, - это отключить буферизацию, сказав $| = 1; Если да, то это должно работать как в Linux, так и в Windows. Если cat действительно творит что-то волшебное, решающее реальную проблему, вам нужно будет точно выяснить, в чем проблема и почему cat исправляет ее; нам понадобится дополнительная информация, чтобы помочь вам в этом. Для начала покажите нам, как выглядел ваш исходный сценарий (до того, как его исправил ваш коллега), и расскажите, что идет не так, когда вы его используете.   -  person Harry Johnston    schedule 17.06.2015
comment
@HarryJohnston Действительно, кота используют как волшебную палочку; Окружающая программа более сложна, но на самом деле используется cat. Предлагаемое вами исправление имеет большой смысл, и я собираюсь попробовать его завтра.   -  person Nate Glenn    schedule 17.06.2015
comment
@HarryJohnston на самом деле меня озадачивает. В чем практическая разница между print while(<STDIN>) и $|++; print while(<STDIN>)? Кажется, что они оба печатаются сразу после получения одной строки, без каких-либо проблем с буферизацией.   -  person Nate Glenn    schedule 17.06.2015
comment
@Nate: когда вы пишете в консоль, буферизация автоматически отключается, поэтому нет никакой разницы. Это имеет значение, когда перенаправляется стандартный вывод. Я предполагал, что инструмент, который использует ваш скрипт, перенаправляет стандартный вывод вашего скрипта, потому что это казалось единственно разумным предположением относительно исходной проблемы.   -  person Harry Johnston    schedule 17.06.2015
comment
@HarryJohnston Это просто буферизация. Остальное было просто (неработающим) безумным обходным решением, которое откуда-то пришло.   -  person Nate Glenn    schedule 18.06.2015


Ответы (1)


Это не Windows по сравнению с Linux. Вы просто выбрали два ужасных примера.

  1. type con читает из консоли, а не из STDIN. Это можно увидеть с помощью type con <nul.

  2. cat крайне необычный. Буферизация в любой системе полностью зависит от конкретного приложения, но почти все приложения работают одинаково, и это отличается от того, как работает cat. cat делает все возможное, чтобы этот сценарий работал.

Замените cat на perl -pe1, чтобы увидеть поведение практически всех остальных программ:

1
first line: 1
<deadlock>

Способ убедить эти «нормальные» программы использовать строковый буфер, а не блочный буфер для их вывода, - это создать псевдотерминал. Это то, что, например, делают Expect и unbuffer. Это, конечно, не будет работать в Windows. Я не уверен, какие программы Windows основывают свое решение о буферизации, но я не думаю, что это можно легко подделать, потому что я никогда не слышал о том, как это сделать.

person ikegami    schedule 17.06.2015
comment
Почему perl -pe1 вызывает тупик? Кроме того, type CON повторяет все, что я набираю в cmd, который, как я предполагал, был STDIN. Что он вместо этого читает? - person Nate Glenn; 17.06.2015
comment
@Nate: CON - это консольный ввод, то есть клавиатура. - person Harry Johnston; 17.06.2015
comment
<$com_reader> блокируется до тех пор, пока от дочернего элемента не будет получена новая строка, но child не будет ничего печатать, пока не наберет 4 КБ (старое значение по умолчанию Perl) или 8 КБ (новое значение по умолчанию Perl) для печати, которые он может получить только от своего родителя, но его родитель больше не будет отправлять данные, потому что они заблокированы. - person ikegami; 17.06.2015