Примечание:
1-й раздел применяется к сценариям Ruby, предназначенным для работы в качестве служебных программ командной строки на основе терминала , при условии, что они не требуют никакой пользовательской обработки или очистка при получении SIGPIPE
и при условии, что вы хотите, чтобы они демонстрировали поведение стандартных утилит Unix, таких как cat
, которые тихо завершаются с специальный код выхода при получении SIGPIPE
.
2-й раздел предназначен для сценариев, для которых требуется пользовательская обработка из SIGPIPE
, например явная очистка и (условная) вывод ошибки сообщения .
Выбор системы по умолчанию из SIGPIPE
:
В дополнение к полезному ответу wallyk и полезному ответу tokland :
Если вы хотите, чтобы ваш скрипт демонстрировал системное поведение по умолчанию , так как большинство утилит Unix (например, cat
) делают , используйте
Signal.trap("SIGPIPE", "SYSTEM_DEFAULT")
в начале вашего сценария.
Теперь, когда ваш скрипт получает сигнал SIGPIPE
(в Unix-подобных системах), поведение системы по умолчанию будет:
- тихо прекратить ваш скрипт
- отчет код выхода
141
(который рассчитывается как 128
(указывает на завершение сигналом ) + 13
(SIGPIPE
* число *) 1082 *))
(В отличие от этого Signal.trap("PIPE", "EXIT")
сообщит код выхода 0
при получении сигнала, который указывает success .)
Обратите внимание, что в контексте shell код выхода часто не виден в такой команде, как ruby examble.rb | head
, поскольку оболочка (по умолчанию) сообщает только о выходе команды last код.
В bash
вы можете проверить ${PIPESTATUS[@]}
, чтобы увидеть коды выхода всех команд в конвейере.
Минимальный пример (запуск от bash
):
ruby -e "Signal.trap('PIPE','SYSTEM_DEFAULT');(1..1e5).each do|i| puts i end" | head
Код Ruby пытается вывести 100 000 строк, но head
выводит только первые 10 строк и затем выходит, что закрывает конец чтения канала, соединяющего две команды.
В следующий раз, когда код Ruby пытается завершить запись этого теперь разорванного канала (после заполнения буфера конвейера), он запускает сигнал SIGPIPE
, который завершает процесс Ruby незаметно, с кодом выхода 141
, который вы можете проверить с помощью echo ${PIPESTATUS[0]}
впоследствии.
В отличие от этого, если вы удалили Signal.trap('PIPE','SYSTEM_DEFAULT')
, то есть с поведением по умолчанию в Ruby, команда шумно прервалась (несколько строк вывода stderr), а код выхода был бы неописуемым 1
.
Пользовательская обработка SIGPIPE
:
Следующее основано на полезном ответе donovan.lampa и добавляет улучшение, предложенное
Киммо Лехто , который указывает, что в зависимости от цели вашего скрипта, получение SIGPIPE
не всегда должно завершаться тихо , поскольку это может указывать на допустимую ошибку условие , особенно в сетевом коде, таком как код для загрузки файла из Интернета.
Он рекомендует следующую идиому для этого сценария:
begin
# ... The code that could trigger SIGPIPE
rescue Errno::EPIPE
# ... perform any cleanup, logging, ... here
# Raise an exception - which translates into stderr output -
# but only when outputting directly to a terminal.
# That way, failure is quiet inside a pipeline, such as when
# piping to standard utility `head`, where SIGPIPE is an expected
# condition.
raise if $stdout.tty?
# If the stack trace that the `raise` call results in is too noisy
# use something like the following instead, which outputs just the
# error message itself to stderr:
# $stderr.puts $! if $stdout.tty?
# Or, even simpler:
# warn $! if $stdout.tty?
# Exit with the usual exit code that indicates termination by SIGPIPE
exit 141
end
Как однострочник:
... rescue Errno::EPIPE raise if $stdout.tty?; exit 141
Примечание: Спасение Errno::EPIPE
работает, потому что, если сигнал игнорируется, запись системного вызова в конвейер возвращается к вызывающей стороне (вместо процесса вызывающей стороны прекращается ) а именно со стандартной ошибкой код EPIPE
, которую Ruby выдает как исключение Errno::EPIPE
.