This question is similar to the following link, but focused on using the command line (bash shell).

Using a simple example, when doing the following command:

$ cat <(date); echo $?
Fri Jul  7 21:04:38 UTC 2017

The exit value is 0 as expected.

In the following command there is an error introduced on purpose, but the return value is still 0:

$ cat <(datE); echo $?
bash: datE: command not found...
Similar command is: 'date'

Is there a way to catch that there was an error in the process substitution when run on the command line (i.e. without having to put it into a script) ?

The solution in the included link above kills the script that is running the command.

  • 1
    Not sure if this fits your definition of "detect an error" but cat <(datE || echo $? >&2) ??
    – B Layer
    Jul 8, 2017 at 1:07
  • @BlairM. Thanks, that is a good idea and helps. I was thinking more along the lines of how the cat command would exit with a non-zero value. I was using the linux parallel command to run a bunch of commands in a file and want it to return a non-zero value if one using "process substitution" fails. That was a detail not really needed for this question.
    – steveb
    Jul 8, 2017 at 1:20
  • Cool. I'll add as an answer then.
    – B Layer
    Jul 8, 2017 at 1:21
  • 1
    This simplified approach will also work : cat <(datE; echo $?) Jul 8, 2017 at 22:28
  • 1

4 Answers 4


In bash, process substitutions are started as background jobs. You can get the pid of the leading process of the last one with $! and you can get its exit status with wait thatpid like for other background jobs:

$ bash -c 'cat <(exit 3); wait "$!"; echo "$?"'

Now, that won't help if you need to use two process substitutions in the same command as in diff <(cmd1) <(cmd2).

$ bash -c 'cat <(exit 3) <(exit 4); wait "$!"; echo "$?"'

The pid of exit 3 above is lost

It's recoverable with this kind of trick:

unset -v p1 p2
cat <(exit 3) ${!x#${p1=$!}} <(exit 4) ${!x#${p2=$!}}

where both pids are stored in $p1 and $p2, but that's of no use as only the last one is inserted in the shell's job table and wait will refuse to wait on $p1 wrongly claiming that it is not a child of this shell, even if $p1 has not terminated yet:

$ cat test-script
unset -v p1 p2
set -o xtrace
cat <(echo x; exec >&-; sleep 1; exit 3) ${!x#${p1=$!}} <(exit 4) ${!x#${p2=$!}}
ps -fH
wait "$p1"; echo "$?"
wait "$p2"; echo "$?"
$ bash test-script
++ echo x
++ exec
++ sleep 1
+ cat /dev/fd/63 /dev/fd/62
++ exit 4
+ ps -fH
chazelas 15393  9820  0 21:44 pts/4    00:00:00 /bin/zsh
chazelas 17769 15393  0 22:19 pts/4    00:00:00   bash test-script
chazelas 17770 17769  0 22:19 pts/4    00:00:00     bash test-script
chazelas 17772 17770  0 22:19 pts/4    00:00:00       sleep 1
chazelas 17776 17769  0 22:19 pts/4    00:00:00     ps -fH
+ wait 17770
test-script: line 6: wait: pid 17770 is not a child of this shell
+ echo 127
+ wait 17771
+ echo 4
$ ++ exit 3

More on that at @mosvy's answer to Run asynchronous tasks and retrieve their exit code and output in bash

  • 1
    see also this
    – user313992
    Jan 15, 2020 at 22:46

An alternative to using process substitution is to use /dev/stdin as the file arg so that pipes work as expected:

set -o pipefail
datE | cat /dev/stdin

The above example is a bit contrived since cat will read from stdin if not given a file arg. It's useful when a command must be given a file.


There is no idiomatic way to cause the calling process (bash) to propagate an error to cat, since they are started separately. The only thing connecting cat and datE is likely a temporary file called /dev/fd/63 whose lifetime is connected to the lifetime of the containing shell statement.

The simplest fix I'm aware of is to put the output into a temp file, and check the return code of dateE.

Something like:

# Make a tempfile
# Delete the tempfile when this shell process exits
trap "rm ${tmpfile}" 0
# Run the command and check for success
if datE > "${tmpfile}"; then
    cat < "${tmpfile}"
    # Report the error.

That being said, there are hacky ways that might involve other files used to propagate the error value. But the general idea is that processes cannot (and should not) mutate their parent or sibling processes unless there is some mutually agreed upon shared state (like a file, or a fifo queue, or pipe, etc..).


In your example:

cat <(datE); echo $?

What happens is that datE throws the error and generates no output. It then throws an error code. However, the (null) input is then presented to cat which happily chews on nothing, and now your exit code is zero.

If you take out the intermediary step, it works as you expect:

$ datE; echo $?
datE: command not found

If you want bash to to abort on any failures in a pipeline and any uncaught error, run the following two commands:

set -e
set -o pipefail

Other shells may provide similar settings.

  • I do understand that removing the cat from the command line will then cause the error to be caught ($? is 127). My example is simple to keep the question clear. Using set -e and set -o pipefail with the cat... version won't cause $? to be 127. This problem came up when using gnu parallel to run commands in a file, some of which use process substitution.
    – steveb
    Jul 9, 2017 at 1:32

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .