bash: how to propagate errors in process substitution?

You could only work around that issue with that for example:

cat <(false || kill $$) <(echo ok)
other_command

The subshell of the script is SIGTERMd before the second command can be executed (other_command). The echo ok command is executed "sometimes": The problem is that process substitutions are asynchronous. There's no guarantee that the kill $$ command is executed before or after the echo ok command. It's a matter of the operating systems scheduling.

Consider a bash script like this:

#!/bin/bash
set -e
set -o pipefail
cat <(echo pre) <(false || kill $$) <(echo post)
echo "you will never see this"

The output of that script can be:

$ ./script
Terminated
$ echo $?
143           # it's 128 + 15 (signal number of SIGTERM)

Or:

$ ./script
Terminated
$ pre
post

$ echo $?
143

You can try it and after a few tries, you will see the two different orders in the output. In the first one the script was terminated before the other two echo commands could write to the file descriptor. In the second one the false or the kill command were probably scheduled after the echo commands.

Or to be more precisely: The system call signal() of the kill utillity that sends the the SIGTERM signal to the shells process was scheduled (or was delivered) later or earlier than the echo write() syscalls.

But however, the script stops and the exit code is not 0. It should therefore solve your issue.

Another solution is, of course, to use named pipes for this. But, it depends on your script how complex it would be to implement named pipes or the workaround above.

References:

  • http://mywiki.wooledge.org/BashFAQ/106
  • http://mywiki.wooledge.org/RaceCondition
  • http://lists.gnu.org/archive/html/bug-bash/2010-09/msg00017.html

For the record, and even if the answers and comments were good and helpful, I ended implementing something a little different (I had some restrictions about receiving signals in the parent process that I did not mention in the question)

Basically, I ended doing something like this:

command <(subcomand 2>error_file && rm error_file) <(....) ...

Then I check the error file. If it exists, I know what subcommand failed (and the contents of the error_file can be useful). More verbose and hackish that I originally wanted, but less cumbersome than creating named pipes in a one-liner bash commmand.


This example shows how to use kill together with trap.

#! /bin/bash
failure ()
{
  echo 'sub process failed' >&2
  exit 1
}
trap failure SIGUSR1
cat < <( false || kill -SIGUSR1 $$ )

But kill can not pass a return code from your sub process to the parent process.