I'm writing some bash code, and I'd like the script to exit if there's any error. The set -e trick works pretty well, but not with subshells. Here's a simplified example:

set -e

chmod a=r file.txt
x=`grep value file.txt | awk '{print $2*2}'`
echo "okay, x=$x"

chmod a= file.txt
x=`grep value file.txt | awk '{print $2*2}'`
echo "should never be printed"

In this example, the second grep command fails with a non-zero exit status, but the subshell executes the awk command anyway, which completes with a status of zero. So, the subshell as a whole has an exit status of zero, and the script continues.

A websearch shows several articles about the limitations of set -e, but can anyone recommend a clean workaround?

  • You might also include set -o pipefail to catch any errors inside the pipeline. This does not work with /bin/sh but should with most modern shells.
    – doneal24
    Jan 1 at 19:01
  • The backquote ` is used in the old-style command substitution. The foo=$(command) syntax is recommended instead. Backslash handling inside $() is less surprising, and $() is easier to nest. See mywiki.wooledge.org/BashFAQ/082 Jan 1 at 20:23
  • Hmm, I just tried set -o pipefail, and the "should never be printed" is still printed.
    – jyoung
    Jan 2 at 1:40

1 Answer 1

 x=`grep value file.txt | awk '{print $2*2}'`

the second grep command fails with a non-zero exit status, but the subshell executes the awk command anyway

That doesn't really have anything to do with the subshell, but due to how pipelines work. The shell starts both (all) commands in the pipeline concurrently, so it doesn't even know the exit status of the left hand side before the right hand command starts.

And yes, by default, the exit status of a pipeline is that of the rightmost command. In general, that's useful, since pipelines can also finish so that the rightmost command exits without reading all input, and the left command is terminated by the SIGPIPE signal when trying to write more to the now-closed pipe.

That's not an error, since you might really not care about the rest of data. Consider something like zcat file.gz | grep -q xyz, which only looks if xyz exists somewhere in the data, at least once.

Anyway, as they say in comments, you use set -o pipefail in many shells (Bash, ksh, zsh, Busybox, at least) to have the rightmost nonzero exit status determine the exit status of the whole pipeline.

But note that grep returns with a falsy status not only in case of an error, but also if it doesn't find any matching lines. If you don't want that to be considered an error, you'll have to do more work.

In this particular case, you could have awk do the pattern matching too, e.g. x=$(awk '/value/ {print $2 * 2}' < file.txt). That should only exit with an error if the file can't be read.

  • Interesting, I didn't know they were executed concurrently.
    – jyoung
    Jan 2 at 1:41
  • set -o pipefail doesn't seem to catch this error.
    – jyoung
    Jan 2 at 1:41
  • @jyoung, seems to work for me, sh -c 'set -eo pipefail; x=$(cat nosuchfile | cat); echo end' returns with an error and no output with all shells I tried (except Dash, which doesn't support pipefail)
    – ilkkachu
    Jan 2 at 13:33
  • Okay, not sure what was going on, that works. Thanks a lot!
    – jyoung
    Jan 3 at 0:58

You must log in to answer this question.

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