12

I'd like to send the result of a series of commands to a variable:

variable=$(a | few | commands)

However, the command substitution resets PIPESTATUS, so I can't inspect where it went wrong after the fact. One solution would be to use mktemp and put the result there temporarily:

variable_file=$(mktemp) || exit 1
a | few | commands > $variable_file
exit_codes="${PIPESTATUS[*]}"
variable=$(<$variable_file)

Is there a more elegant solution?

l0b0
  • 55,365
  • 30
  • 138
  • 223
  • This is a good question indeed. I think there is no better solution than yours, I hope someone proves me wrong ;-) You probably know about "set -o pipefail", but anyway this is (more or less) orthogonal to your question. – tokland Dec 10 '10 at 16:16
  • Ok now I am intrigued because I do this same way you do... and it works... but if there is something more elegant... let me have it... – Cipi Dec 10 '10 at 21:00
  • 1
    See this [question](http://stackoverflow.com/questions/3450960/using-xargs-to-assign-stdin-to-a-variable) for more ideas. See also [here](http://stackoverflow.com/questions/2413166/bash-redirect-and-append-stdout-and-stderr-to-file-and-terminal). – Dennis Williamson Dec 11 '10 at 02:21
  • Regardless of the approach I'd suggest `exit_codes=("${PIPESTATUS[@]}")` instead of `exit_codes="${PIPESTATUS[*]}"`, so that you keep the codes as an array. – dimo414 Dec 06 '18 at 06:59

2 Answers2

8

Kinda hacky but I think you could fudge it like this.

variable=$(a | few | commands; echo ": ${PIPESTATUS[*]}")
PIPESTATUS=(${variable##*: })
variable=${variable%:*}
variable=${variable%$'\n'}
ephemient
  • 198,619
  • 38
  • 280
  • 391
1

Building on ephemient's answer, if we need the output of the piped commands stored without them being mixed in with the pipestatus codes, but we don't really care what the exit codes themselves are (just that they all succeeded), we can do:

variable=$(a | few | commands; [[ ${PIPESTATUS[*]} == "0 0 0" ]])

This will check on the status of all the piped command status in the above example and if its exit code is not 0, will set $? to 1 (false)

If you want to exit with a different code instead of 1, you could capture the contents of PIPESTATUS[#], e.g. r_code=${PIPESTATUS[2]}, and then use (exit ${r_code[2]}) instead of false.

Below captures all the codes of PIPESTATUS, ensures they're all 0, and if not, sets the exit code to be the $? value of commands:

declare -a r_code 
variable=$(a | few | commands
           r_code=(${PIPESTATUS[@]})
           [[ ${r_code[*]} == "0 0 0" ]] || (exit ${r_code[2]})
)

echo ${?}         # echoes the exit code of `commands`
echo ${variable}  # echoes only the output of a | few | commands
Jon
  • 952
  • 1
  • 11
  • 17
  • 1
    Good ideas for some more flexible options. You could also use `set -o pipefail` before running the commands to avoid having to check the whole `PIPESTATUS` contents for non-zeros. – l0b0 Feb 16 '21 at 05:46