SIGPIPE из-за файловых дескрипторов и подстановки процессов

Я пытаюсь сохранить некоторые журналы из функций bash, которые выполняют инструменты (некоторые из них работают в подоболочках). Кроме того, я хотел бы распечатать все ошибки на терминале.

Мой код приводит к sigpipe и коду выхода 141 при нажатии ctr-c плюс странный файл журнала. Сбой канала, по-видимому, вызван перенаправлением stdout на stderr внутри ловушки, что прерывает поток stdout команды tee. Интересно, что код завершается, как и ожидалось, с кодом выхода 130 без перенаправления, используемого в ловушке или команде cat.

  1. Я все еще не могу исправить и объяснить полученный файл журнала. Почему некоторые эхо-сигналы повторяются дважды и почему эхо-ловушки также записываются в файл?

  2. Почему sigpipe не вызывается ранее перенаправлением внутри функции?

trap '
echo trap_stdout
echo trap_stderr >&2
' INT

fun(){
    echo fun_stdout
    echo fun_stderr >&2
    ( sleep 10 | cat )
}

echo > log
fun >> log 2> >(tee -a log)

журнальный файл

fun_stdout
fun_stderr
fun_stderr
trap_stdout

РЕДАКТИРОВАТЬ: рабочий пример в соответствии с ответом oguz ismail

exec 3>> log
exec 4> >(tee -ai log >&2)
fun 2>&4 >&3
exec 3>&-
exec 4>&-

person pyr0    schedule 22.07.2020    source источник


Ответы (2)


Почему есть эхо дважды

Стандартный вывод fun перенаправляется на log до того, как его стандартный вывод будет перенаправлен в FIFO, созданный для tee, таким образом, tee наследует стандартный вывод, который перенаправляется на log. Я могу доказать это так:

$ : > file 2> >(date)
$ cat file
Sat Jul 25 18:46:31 +03 2020

Изменение порядка перенаправлений исправит это. Например.:

fun 2> >(tee -a log) >> log

и почему эхо-ловушки тоже записываются в файл?

Если ловушка, установленная для SIGINT, срабатывает, когда оболочка все еще выполняет fun, совершенно нормально, что перенаправления, связанные с fun, вступают в силу.

Чтобы соединить stdout и stderr действия ловушки с таковыми в основной оболочке, вы можете сделать:

exec 3>&1 4>&2

handler() {
  : # handle SIGINT here
} 1>&3 2>&4

trap handler INT

Или что-то подобное; идея состоит в том, чтобы копировать stdout и stderr основной оболочки.

Почему sigpipe не вызывается ранее перенаправлением внутри функции?

Потому что tee жив, пока echo fun_stderr >&2 выполняется. И sleep ничего не записывает в свой стандартный вывод, поэтому он не может запустить SIGPIPE.

Причина, по которой этот сценарий завершается из-за SIGPIPE, заключается в том, что tee также получает SIGINT, сгенерированный клавиатурой, и завершается до того, как будет выполнено действие ловушки, связанное с SIGINT. В результате при выполнении echo trap_stderr >&2, поскольку его stderr подключен к каналу, который был закрыт несколько минут назад, оболочка получает SIGPIPE.

Чтобы избежать этого, как уже предлагалось, вы можете заставить tee игнорировать SIGINT. Для этого не нужно устанавливать пустую ловушку, достаточно опции -i.

fun 2> >(tee -a -i log) >> log
person oguz ismail    schedule 22.07.2020

Источником SIGPIPE является то, что SIGINT (инициированный ctrl/c) отправляется ВСЕМ запущенным процессам: как основному процессу bash (выполняющему функцию «fun»), так и вспомогательной оболочке, выполняющей «tee -a». В результате по Ctrl/C убиваются оба. Когда основной процесс пытается отправить trap_stderr процессу tee, он получает SIGPIPE, потому что tee уже умер.

Учитывая роль 'tee -a', имеет смысл защитить его от SIGINT и позволить ему работать до тех пор, пока 'fun' не завершится (или не будет убит). Рассмотрим следующее изменение в последней строке

fun >> log 2> >(trap '' INT ; tee -a log >&2)

который создаст файл журнала:

Console (stderr)
fun_stderr
^Ctrap_stderr

Log File: (no duplicates)

fun_stdout
fun_stderr
trap_stdout
trap_stderr

Вышеизложенное также касается второго вопроса о повторяющихся строках в файле журнала. Это результат использования tee для отправки каждой строки stderr в файл журнала И stdout. Учитывая, что stdout только что получил перенаправление (по журналу) в файл журнала, обе копии вывода отправляются в файл журнала, а не на терминал.

Учитывая, что перенаправление выполняется последовательно, изменение строки «тройник» для отправки вывода на исходный stderr (вместо уже перенаправленного stdout) покажет вывод на терминале (или что-то еще stderr)

person dash-o    schedule 22.07.2020