How to get return value of function / command with tee
techAdmin
Status: Site Admin
Joined: 26 Sep 2003
Posts: 4126
Location: East Coast, West Coast? I know it's one of them.
Reply Quote
I ran into this problem today, and the solution is ridiculously simple. I was researching all kinds of convoluted methods involving stdout redirection etc, but the more I looked at these solutions, the less I liked them. This is standard bash, nothing exotic.

Finally, I found out about $PIPESTATUS, which is an array of all return values from each component of a piped command.

for my purposes, the code was fairly simple, but I also wanted to maintain the stdout so users could read what is happening since this is apt-get stuff.

this is the solution I finally ended up with:

:: Code ::
if [ "$USE_LOG" != 'true' ]
then
   apt-get update
else
   logfile=$(date +%Y-%m-%d-%H:%M:%S)'-update-up1'
   apt-get update 2>$logfile 2>&1 | tee $logfile
fi
exitStatus=${PIPESTATUS[0]}

This is part of a larger script, but I include it to show all the parts working.

As you can see, ${PIPESTATUS[0]} also returns the exit value for commands that are not piped, which is in this case quite convenient.

We're asking for the first element in the PIPESTATUS array, which is either the first item in the piped command series, or the last command issued.

I was surprised to see it work like that, but that's what it does.

:: Code ::
2>$logfile 2>&1
sends the error output to both the logfile AND the stdout, or screen.

I needed to add this because the actual error on exit is not captured by tee, only the internal errors as they happen.

A few very nonintuitive things happened in terms of using PIPESTATUS with this syntax, the main one is that in the pipe, tee does not appear to count at all, since
:: Code ::
command | tee somefile.txt
echo $PIPESTATUS

does not output what you would expect, the exit values of 'command' and of 'tee', but instead only of 'command'. This is due to the weird way bash decides to handle arrays without explicit indexing, they use the 0 element as default value to return when nothing is specified.

This example shows this clearly:
:: Code ::
aTest=(one two three four)
echo $aTest
one


Read more on bash arrays at the really good advanced bash scripting guide. And read on internal variables like $PIPESTATUS

tee weirdnesses
tee is also an odd creature, even for bash. Here in this example
:: Code ::
ls | tee cc | grep a

tee acts basically as if its pipe entry is simply not there, the values of command one, ls, are sent directly to command 3, grep, while being shot out to the log file.

A closer look at how this works
Here I create an error on command one, and as you see, you get the expected error output from ls.
:: Code ::
ls sadf | tee cc | grep a
echo ${PIPESTATUS[0]}
2

And here I create an error in command 3:
:: Code ::
ls | tee cc | grep -sa dfa
echo ${PIPESTATUS[2]}
1

and you also get what you would have expected.

And when we unwrap it, like this, we also get what we'd expect:
:: Code ::
ls | tee cc | grep -sa dfa
echo ${PIPESTATUS[@]}
0 0 1

So don't leave it up to chance, pull out the value you want explicitly by using the array index.

All in all, very odd, bash continues to be the most bizarre of languages, convoluted, twisted, but with strange solutions thrown in just when you are about to give up hope entirely.

How bash grew into such a convoluted mess shows that although having a lot of cool tools is nice, at some point the inconsistencies really aren't that great long term, it's like learning a language with a very weird and irregular grammar, with a lot of exceptions etc. Not really an ideal for a programming language if you ask me.
Back to top
Display posts from previous:   

All times are GMT - 8 Hours