SIGPIPE due to file descriptors and process substitution

362 Views Asked by At

I am trying to save some logs from bash functions which execute tools (some of them run in subshells). In addition I would like to print all errors to the terminal.

My code leads to a sigpipe and exit code 141 upon hitting ctr-c plus a strange log file. The pipe fail seems to be caused by the redirection of stdout to stderr within the trap, which breaks the stdout stream of the tee command. Interestingly the code terminates as expected with exit code 130 without the redirection used in the trap or the cat command.

  1. I am still unable to fix and explain the resulting log file. Why are there some echos twice and why are the trap echos written to the file as well?

  2. Why isn't the sigpipe caused earlier by the redirection within the function?

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)

log file

fun_stdout
fun_stderr
fun_stderr
trap_stdout

EDIT: working example according to oguz ismail answer

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

There are 2 best solutions below

0
On BEST ANSWER

Why are there some echos twice

fun's stdout is redirected to log before its stderr is redirected to the FIFO created for tee, thus tee inherits a stdout that is redirected to log. I can prove that like so:

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

Changing the order of redirections will fix that. E.g.:

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

and why are the trap echos written to the file as well?

If the trap set for SIGINT is triggered while the shell is still executing fun, its perfectly normal that the redirections associated with fun takes effect.

To connect the trap action's stdout and stderr to those of the main shell, you can do:

exec 3>&1 4>&2

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

trap handler INT

Or something alike; the idea is making copies of the main shell's stdout and stderr.

Why isn't the sigpipe caused earlier by the redirection within the function?

Because tee is alive while echo fun_stderr >&2 is being executed. And sleep does not write anything to its stdout, so it can not trigger a SIGPIPE.

The reason why this script terminates due to a SIGPIPE is that tee receives the SIGINT generated by the keyboard as well and terminates before the trap action associated with SIGINT is executed. As a result, while executing echo trap_stderr >&2, since its stderr is connected to a pipe that has been closed moments ago, the shell receives the SIGPIPE.

To avoid this, as already suggested, you can make tee ignore SIGINT. You don't need to set an empty trap for that though, the -i option is enough.

fun 2> >(tee -a -i log) >> log
0
On

The source of the SIGPIPE is that the SIGINT (initiated by ctrl/c) is sent to ALL processes running: both the "main" bash process (executing the 'fun' function), and the sub shell executing the 'tee -a'. As a result, on Ctrl/C, both get killed. When the main process tries to send 'trap_stderr' to te "tee" process, it get SIGPIPE, because the "tee" has already died.

Given the role of the 'tee -a', it make sense to protect it from the SIGINT, and allow it to run until 'fun' complete (or killed). Consider the following change to the last line

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

which will produce the log file:

Console (stderr)
fun_stderr
^Ctrap_stderr

Log File: (no duplicates)

fun_stdout
fun_stderr
trap_stdout
trap_stderr

The above will also address the second question is about duplicate lines in the log file. This is the result of using tee to send each stderr line to log file AND stdout. Given that stdout just got redirect (by the '>>log') to the 'log' file, both copy of the output are sent to the log file, and none to the terminal

Given the redirection are performed sequentially, changing the 'tee' line to sent the output to the original stderr (instead of the already redirect stdout) will show the output on the terminal (or whatever stderr is)