When developing shell scripts, it is common usage to check the exit code of a command in order to check if its execution was successful or not. It is pretty easy when you execute only one command, because the exit code can be find using the special parameter “$?”. But if you use pipelines (“command1 | command2 | …”), it is not that simple because “$?” will only give you the exit code of the last command of your pipeline.
So, today, I’m going to show you how to get the exit code for each command in a pipeline. Unfortunately, this only works in bash.
Simple test
Let’s start with a simple test.
Here, I’m trying to print the contents of a non-existent file and check the exit code:
[root@linuxlab01 tmp]# cat titi cat: titi: No such file or directory [root@linuxlab01 tmp]# echo $? 1
As expected, the exit code is 1 because the file does not exist.
Now, I’m going to pipe the output of the “cat” command on a non-existent file to the “awk” command:
[root@linuxlab01 tmp]# cat titi|awk '{print $1}' cat: titi: No such file or directory [root@linuxlab01 tmp]# echo $? 0
The exit code is 0 because “$?” gave me the result of the “awk” command that ran successfully despite the fact that the “cat” command failed. Fortunately, we can use the bash internal variable “PIPESTATUS” to handle this situation.
Introducing the special variable “PIPESTATUS”
The special variable “PIPESTATUS” is an array containing the exit code of each commands executed in a pipeline. If no pipe is used, it will hold the exit code of the last executed command.
In my previous example using “cat” and “awk”, the PIPESTATUS variable contains:
PIPESTATUS[0] : 1 <----- "cat" command PIPESTATUS[1] : 0 <----- "awk" command
The exit code of the first command of your pipe will be stored at index 0 in PIPESTATUS, command 2 at index 1 …
Very useful isn’t it? 🙂
You can use the examples of my previous post “Work with bash/ksh associative arrays” to extract the exit codes from the array but I’ll give you a few simple examples to get started.
Advices about PIPESTATUS
It is recommended to store the contents of PIPESTATUS in a temporary variable before using it. The reason is very simple: since PIPESTATUS contains the exit code of the last command(s), its value will change if you print its content several times in a row.
Let’s illustrate my words:
[root@linuxlab01 tmp]# cat titi|awk '{print $1}' cat: titi: No such file or directory [root@linuxlab01 tmp]# echo ${PIPESTATUS[@]} 1 0
“echo ${PIPESTATUS[@]}” returns the exit code of the “cat” and “awk” commands.
If I execute the same “echo” command again:
[root@linuxlab01 tmp]# echo ${PIPESTATUS[@]} 0
Now, the PIPESTATUS variable contains the exit code of the first echo I made in the first code block! So be very careful and always use a temporary variable to store PIPESTATUS content. Especially if you intend to use its content later in your script!
Examples
Test the exit code of one specific stage of your pipeline
cat titi|awk '{print $1}' TMPSTATUS=("${PIPESTATUS[@]}") if [[ ${TMPSTATUS[0]} -eq 0 ]];then echo "cat OK" else echo "cat KO" fi
Test the exit code of all stages of your pipeline
cat titi|awk '{print $1}' TMPSTATUS=("${PIPESTATUS[@]}") if [[ ${TMPSTATUS[@]} == "0 0" ]];then echo "Everything OK" else echo "Something wrong" fi
Including more stages in the pipeline:
cat titi|awk '{print $1}'|head|grep -c "anything" TMPSTATUS=("${PIPESTATUS[@]}") if [[ ${TMPSTATUS[@]} == "0 0 0 0" ]];then echo "Everything OK" else echo "Something wrong" fi
If you want to identify exactly which command failed:
cat titi|awk '{print $1}'|head|grep -c "anything" TMPSTATUS=("${PIPESTATUS[@]}") if [[ ${TMPSTATUS[0]} -ne 0 ]];then echo "cat KO" elif [[ ${TMPSTATUS[1]} -ne 0 ]];then echo "awk KO" elif [[ ${TMPSTATUS[2]} -ne 0 ]];then echo "head KO" elif [[ ${TMPSTATUS[3]} -ne 0 ]];then echo "grep KO" else echo "Everything OK" fi
I hope these tips will be useful for your scripting work. Stay tuned for more DBA stuff!
Hello guy,
possible to test the sum or the array. If >0, then there’s a problem.
Don’t matter how many commands have been launched
( IFS=+; echo “${TMPSTATUS[@]}” ) | bc
Hello Pierhomme,
Really nice way to check everything’s OK, thank you for sharing!