Shell scripting: getting exit status from a piped command

Some programs don't behave nicely when used in a pipe on unix-like systems. For example, I was using pg_dump piped through gzip like so:

Broken script: error condition never occurs

export PGPASSWORD=secret
pg_dump -h host -U user db | gzip - > backup.sql.gz

if [ $? -ne 0 ]; then
    ### Never happens: gzip never fails! ###
    echo Backup failed.
    exit 1
fi

This uses the normal way of checking the exit status of the previous command ($?), but it doesn't work. If the pg_dump fails for any reason, gzip doesn't return any error response. $? is set to 0, indicating success.

Fortunately, there's a better way. In bash, the PIPESTATUS environment variable is an array with the return codes of all the commands executed in the last pipe. Checking for the overall return status and the status of pg_dump is now done like this:

Correct script: check the result of pg_dump separately

export PGPASSWORD=secret
pg_dump -h host -U user db | gzip - > backup.sql.gz

if [ $? -ne 0 -o ${PIPESTATUS[0]} -ne 0 ]; then
    echo Backup failed.
    exit 1
fi

Now I can be sure my automated database backups aren't going to fail silently.

Portrait of Matt Ryall

About Matt

I’m a technology nerd, husband and father of four, living in beautiful Sydney, Australia.

My passion is building technology products that make the world a better place. In 2021, I started Mawson Rovers to develop robotics and software for space exploration. Prior to this, I led product teams at Atlassian to create collaboration tools for 15 years.

I'm also a startup advisor and investor, with an interest in advancing the Australian space industry. You can read more about my work on my LinkedIn profile.

blog@mattryall.net