Twitter

Some bash tips -- 3 -- Exit codes management in pipelines

This blog is part of a bash tips list I find useful to use on every script -- the whole list of it can be found here.

Return codes (aka exit codes) are heavily used in any language to know if a command has succeded or has failed. In shell script, the return code of the last executed command is accessible in the $? variable and is 0 if the command has completed successfully and different from 0 if the command has failed like we can see in the below example:
$ echo "blabla" > /tmp/iexist
$ cat /tmp/iexist
blabla
$ echo $?
0
$ cat /tmp/idontexist
cat: /tmp/idontexist: No such file or directory
$ echo $?
1
$
A return code can easily be tested and different actions triggered or a message shown depending on the success or the failure of a command:
$ cat /tmp/iexist
blabla
$ if [ $? -eq 0 ]; then echo "Success"; else echo "Failure"; fi
Success
$ cat /tmp/idontexist
cat: /tmp/idontexist: No such file or directory
$ if [ $? -eq 0 ]; then echo "Success"; else echo "Failure"; fi
Failure
$
In the above example, we can clearly see (and test) that the cat command succeeds when a file exists and fails when a file does not exist.

So far so good but now comes the scenario where you also need to sort the output of the file you just cat:
$ cat /tmp/iexist | sort
blabla
$ if [ $? -eq 0 ]; then   echo "Success"; else   echo "Failure"; fi
Success
$ cat /tmp/idontexist | sort
cat: /tmp/idontexist: No such file or directory
$ if [ $? -eq 0 ]; then   echo "Success"; else   echo "Failure"; fi
Success   <== ?? what ??
$
We can see that in this scenario, $? returns 0 even if the file I cat does not exist ! This is because $? returns the last command return code which in my case is 0 because this is the return code of the sort command !

So how to get the return code of the cat and not the sort ? well, bash has a mechanism for this, the return codes are in the PIPESTATUS array which you can check instead of $? in a multiple pipes command line scenario:
$ cat /tmp/iexist | sort
blabla
$ cecho ${PIPESTATUS[0]}":"${PIPESTATUS[1]}
0:0
$ cat /tmp/idontexist | sort
cat: /tmp/idontexist: No such file or directory
$ echo ${PIPESTATUS[0]}":"${PIPESTATUS[1]}
1:0
$
Here, ${PIPESTATUS[0]} (the first element of the PIPESTATUS array) contains the return code of the first command which is the cat and ${PIPESTATUS[1]} the return code of the sort command.

PIPESTATUS is a special array, you can print it as shown in the more complex example below:
$ cat /tmp/iexist | tac | sort | grep "something" | sort | tac
$ echo ${PIPESTATUS[@]}
0 0 0 1 0 0
      ^
      |
grep grepped nothing so it returned 1
$
Alternatively, you can aso use the set -o pipefail directive on top of our script to return the rightmost non zero exit code of a pipeline; you won't know exactly which one has failed but you will know that something in the pipeline has failed -- which may be enough in some cases:
$ cat /tmp/idontexist | sort
cat: /tmp/idontexist: No such file or directory
$ echo $?
0  <== default behavior
$ set -o pipefail
$ cat /tmp/idontexist | sort
cat: /tmp/idontexist: No such file or directory
$ echo $?
1  <== You don't know which part has failed but the overall result of the pipe is failure
$

PIPESTATUS and pipefail are good to be known to write robust scripts -- enjoy !


< Previous bash tip / Next bash tip >

3 comments:

  1. I had this issue, but since I only cared if the whole pipe failed or not I've used bash "set -o pipefail"

    ReplyDelete
    Replies
    1. Indeed, set -o pipefail returns the exit code of the rightmost command which returns non 0; it is a good alternative indeed, I will add a paragraph on it in the blog, it fits here as well.

      Delete

CUDA: Getting started on Google Colab

While getting started with CUDA on Windows or on WSL (same on Linux) requires to install some stuff, it is not the case when using Google...