Wednesday, February 8, 2012

Adventures in bash - catching several exit values in a piped set of commands

"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." (forum post at Techpatterns)

Yesterday I was re-working a database backup script at one of my customers and stumbled onto a problem when I wanted to have both proper error handling and at the same time avoid filling the disk.

The code providing the challenge was this
I need to pipe the output of mysqldump to gzip, because otherwise I run into problems with the disk filling up. And yes, having to it like this also means that doing restores are quite a pain, but that is another problem.

Normally I do error handling in scripts by evaluating $?, but to have proper error handling in here I need to capture the exit value of both mysqldump and gzip.  And $? only gives med the exit value of gzip - the least important of the two.

Luckily, and as expected, I'm not the first person to run into this problem, ad by way of googling I found that Bash actually have a built-in way of giving me both exit values - the array $PIPESTATUS. $PIPESTATUS is an array with all the exit values from you last command line. $PIPESTATUS[0] contains the first exit value, $PIPESTATUS[1] the second and so on
sigurdur@ifconfig:~$ true | false
sigurdur@ifconfig:~$ echo ${PIPESTATUS[0]}
sigurdur@ifconfig:~$ true | false
sigurdur@ifconfig:~$ echo ${PIPESTATUS[1]}
You can also get the entire array
          sigurdur@ifconfig:~$ true | false |false |true
          sigurdur@ifconfig:~$ echo ${PIPESTATUS[@]}
          0 1 1 0
A single, non-piped command is considered to be a "pipe of one", thus leaving you with a $PIPESTATUS array with one value. Since $PIPESTATUS is updated after every command line I had to copy the array before extracting the exit values.
So my code ended up like this:
# We want the exit values of both mysqldump and gzip
PIPESTATUS have probably been part of Bash since forever, but to me it was new - and it solved my problem. Fun stuff:-)