8

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
0

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'
0

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.

steveb
  • 183
  • 1
    Not sure if this fits your definition of "detect an error" but cat <(datE || echo $? >&2) ?? – B Layer Jul 08 '17 at 01: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 08 '17 at 01:20
  • Cool. I'll add as an answer then. – B Layer Jul 08 '17 at 01:21
  • The answer I am looking for should address the propagation of the error though. The above suggestion doesn't address that in its current form. – steveb Jul 08 '17 at 01:23
  • Hehe. Okay. I'll think about a more elaborate approach. – B Layer Jul 08 '17 at 01:24
  • Please leave the comment as it is likely to be helpful to someone other than me as well. – steveb Jul 08 '17 at 01:25
  • 1
    This simplified approach will also work : cat <(datE; echo $?) – George Vasiliou Jul 08 '17 at 22:28
  • @GeorgeVasiliou That works visually and is helpful for a subset of cases but it doesn't cause the cat command to exit with a non-zero value. In the case where gnu parallel is used to run a number of commands in a file (at least one using process substitution), it would be helpful to have the command to fail with a non-zero value if the process substitution argument fails with a non-zero value. There are ways to work around this (e.g. put in a bash script), but it would be handy to have the error work its way up. – steveb Jul 10 '17 at 17:24
  • 1

4 Answers4

5

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 "$?"'
3

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 "$?"'
4

The pid of exit 3 above is lost

It's recoverable with this kind of trick:

unset -v p1 p2
x='!'
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
x='!'
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
x
+ ps -fH
UID        PID  PPID  C STIME TTY          TIME CMD
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
127
+ wait 17771
+ echo 4
4
$ ++ exit 3

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

3

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.

Nathan
  • 131
1

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
tmpfile=$(mktemp)
# 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}"
else
    # Report the error.
fi

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..).

-1

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
127

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.

DopeGhoti
  • 76,081
  • 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 09 '17 at 01:32