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.
set -o pipefail
to catch any errors inside the pipeline. This does not work with/bin/sh
but should with most modern shells.foo=$(command)
syntax is recommended instead. Backslash handling inside$()
is less surprising, and$()
is easier to nest. See mywiki.wooledge.org/BashFAQ/082