BrokenPipeError в Python, но не в Perl - PullRequest
       7

BrokenPipeError в Python, но не в Perl

0 голосов
/ 30 августа 2018

Рассмотрим скрипт Python:

for i in range(4000):
    print(i)

и этот скрипт на Perl:

for my $i (0..4000-1) {
    print $i, "\n";
}

python3 pipe.py | head -n3000 >/dev/null выдает ошибку:

Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>
BrokenPipeError: [Errno 32] Broken pipe

но

perl pipe.pl | head -n3000 >/dev/null не выдает ошибок (в Perl v5.26.1).

Почему такое расхождение между Python и Perl? Как заставить Perl выдавать подобное сообщение об ошибке?

Ответы [ 2 ]

0 голосов
/ 30 августа 2018

Здесь происходит то, что в обоих случаях у вас есть процесс записи в канал, чей конец чтения был закрыт (на head выход после определенного количества байтов).

Это приводит к отправке сигнала SIGPIPE в процессе записи. По умолчанию это убивает процесс. Процесс может игнорировать сигнал, если захочет, что просто приводит к сбою вызова write с ошибкой EPIPE.

Начиная с версии 3.3 , Python вызывает исключение BrokenPipeError в этом случае, поэтому похоже, что Python 1) игнорирует SIGPIPE по умолчанию и 2) переводит EPIPE в BrokenPipeError исключение.

Perl не игнорирует и не обрабатывает сигналы по умолчанию. Это означает, что в вашем примере это будет уничтожено SIGPIPE, но поскольку это не последняя команда в конвейере (это будет head здесь), оболочка просто игнорирует ее. Вы можете сделать его более заметным, не используя конвейер:

perl pipe.pl > >(head -n3000 >/dev/null)

Эта часть хитрости bash заставляет perl писать в канал, но не как часть конвейера оболочки Я не могу проверить это сейчас, но как минимум это установит $? (статус завершения команды) в 141 в оболочке (128 + номер сигнала, который для SIGPIPE равен 13), и он также может сообщить Broken pipe.

Вы можете справиться с этим вручную в коде Perl, хотя:

  • Вариант 1: сгенерировать ошибку из обработчика сигнала

    $SIG{PIPE} = sub { die "BrokenPipeError" };
    
  • Вариант 2: игнорировать сигнал, обрабатывать ошибки записи

    $SIG{PIPE} = 'IGNORE';
    ...
    print $i, "\n" or die "Can't print: $!";
    

    Обратите внимание, что в этом случае вам следует подумать о буферизации. Если вы не включите автоматическую очистку (как в STDOUT->autoflush(1)), и вывод идет в канал или файл, Perl сначала соберет текст во внутренний буфер (и вызов print будет успешным). Только когда буфер заполнится (или когда дескриптор файла будет закрыт, в зависимости от того, что произойдет раньше), текст будет фактически выписан и буфер будет очищен. Вот почему close также может сообщать об ошибках записи.

0 голосов
/ 30 августа 2018

Возникает исключение Python, поскольку процесс чтения (head) закрывает свой конец, поэтому скрипт получает SIGPIPE при следующей попытке записи; см. этот пост . Это включало решения в сообществе Python, чтобы изменить настройки по умолчанию, чтобы информировать пользователя (см. Связанный пост).

Это не видно в Perl, потому что он убит этим сигналом (каково его расположение), ничего не сказав. Так что вы можете переопределить это

use warnings;

$| = 1;

$SIG{PIPE} = sub { die $! };

for my $i (0..4_000-1) {
    print $i, "\n";
}

(без $| = 1 мне нужно больше 5_000 выше, чтобы это произошло).

Или, скорее, введите warn (вместо die), чтобы программа продолжала

local $SIG{PIPE} = sub { warn "Ignoring $_[0]: $!" };

Обновление Учитывая пояснения, приведенные в комментарии, я бы порекомендовал этот обработчик быть глобальным. Это может все еще быть переопределено с local одним в определенных областях. Кроме того, нет ничего плохого в том, чтобы пережить SIGPIPE вместо того, чтобы быть уничтоженным, если есть предупреждение.

Обратите внимание, что даже без этого состояние выхода процесса Perl покажет проблему. Запустите echo $? после того, как процесс "завершится" (тихо завершается); Я получаю 32 в моей системе.

Чтобы подражать поведению Python, вы можете ввести die в обработчике сигналов и обработать это исключение, поместив все это в eval.

Спасибо melpomene и ikegami за комментарии.

...