There are 3 common ways of doing this:
Pipefail
The first way is to set the pipefail option (ksh, zsh or bash). This is the simplest and what it does is basically set the exit status $? to the exit code of the last program to exit non-zero (or zero if all exited successfully).
#$ false | true; echo $?
0
#$ set -o pipefail
#$ false | true; echo $?
1
$PIPESTATUS
Bash also has an array variable called $PIPESTATUS ($pipestatus in zsh) which contains the exit status of all the programs in the last pipeline.
#$ true | true; echo "${PIPESTATUS[@]}"
0 0
#$ false | true; echo "${PIPESTATUS[@]}"
1 0
#$ false | true; echo "${PIPESTATUS[0]}"
1
#$ true | false; echo "${PIPESTATUS[@]}"
0 1
You can use the 3rd command example to get the specific value in the pipeline that you need.
Separate executions
This is the most unwieldy of the solutions. Run each command separately and capture the status
#$ OUTPUT="$(echo foo)"
#$ STATUS_ECHO="$?"
#$ printf '%s' "$OUTPUT" | grep -iq "bar"
#$ STATUS_GREP="$?"
#$ echo "$STATUS_ECHO $STATUS_GREP"
0 1