Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get exit codes of a pipe when output is assigned to variable (Command Substitution) [duplicate]

Tags:

bash

shell

pipe

Getting the exit code of the pipe command works fine.

echo "ABC" | false | true
echo ${PIPESTATUS[0]} ${PIPESTATUS[1]} ${PIPESTATUS[2]}
#Output is 0 1 0

But when I assign the output to a variable a can not get the exit codes.

TEST=$(echo "ABC" | false | true)
echo ${PIPESTATUS[0]} ${PIPESTATUS[1]} ${PIPESTATUS[2]}
#Output is 0

How can I get the exit codes of the piped processes?

like image 325
powerpete Avatar asked Jan 18 '17 10:01

powerpete


1 Answers

Bash command substitution happens in a sub-shell, so those values exist but only inside that sub-shell. You're seeing ${PIPESTATUS[0]} reflect the $? from the assignment (it'll be 1 if you ended the substitution with | false).

I've changed your example to actually include some output. This will work with the original code too.

# without command substitution
echo "ABC" | false | echo "DEF"
echo "${PIPESTATUS[*]}"

echo "---"

# within command substitution
TEST="$( echo "ABC" | false | echo "DEF"; printf :%s "${PIPESTATUS[*]}" )"
declare -a PIPESTATUS2=( ${TEST##*:} )  # make array w/ content after final colon
if [[ -n "${TEST%:*}" ]]; then          # if there was original output
  TEST="${TEST%:*}"                     # remove trailing results from $TEST
  TEST="${TEST%$'\n'}"                  # remove trailing \n like plain $(…)
else
  TEST=""                               # no original output -> empty string
fi

echo "$TEST"
echo "${PIPESTATUS2[*]}"

Output:

DEF
141 1 0
---
DEF
141 1 0

Exit code 141 comes from the fact that false terminated the pipeline prematurely (SIGPIPE).

This basically just appends the sub-shell's $PIPESTATUS array to the stored value using a colon as a delimiter (any non-digit will do; I picked one I didn't have to escape) and then pulls it out of the response, populating the $PIPESTATUS2 array with those values. The removal of the final line break is another bashism. We could have used that as the delimiter, but then this would break if the original output wasn't terminated by a line break.

Simpler solution if you just want one of the exit codes:

TEST=$( echo "ABC" | false | true; exit ${PIPESTATUS[0]} )
echo $?  # 141 from `echo "ABC"

Much more complex POSIX-compliant solution (there's no need for $PIPESTATUS when you can use some dark magic): search for "Consider the pipeline" in §1d of the famous Csh Programming Considered Harmful rant and then adapt that to your POSIX-compliant needs. This will be nontrivial but highly educational if you're the kind of coder that needs to avoid bash and real languages.

like image 75
Adam Katz Avatar answered Oct 21 '22 16:10

Adam Katz