Table of Contents
Every command we run successfully or unsuccessfully in Bash leaves a particular code behind, which we call an exit code or exit status. We use this code to analyze the last command in Bash.
Using $?
Variable
Use shell variable, $?
, to get the exit code of the last-run command in Bash.
1 2 3 4 5 |
date exit_code=$? echo "The exit code of 'date' command: $exit_code" |
1 2 3 4 |
Mon May 22 04:58:50 AM UTC 2023 The exit code of 'date' command: 0 |
We ran the date
command to get the current date and time, which ran successfully without prompting errors. Then, we used the shell variable represented by $?
to grab the exit code of the date
command and stored it in the exit_code
variable.
Finally, we used an echo
command to print the value of the exit_code
variable. Note that the variables are prefixed with $
to capture their values. It does not matter whether the command ran successfully; the value of the $?
variable would always be a number.
Usually, the exit code of the successfully executed command is 0
, and any non-zero number ranging from 1
to 255
denotes an error or some particular condition that occurred while running the command. For example, the 127
exit code means command not found; you can learn more about exit codes here.
As we have limited numbers, every non-zero value will be unique to the script or program. For example, the meaning of exit code 1
differs for ls
and grep
commands. See the following.
1 2 3 |
man ls |
Scroll downwards and look for the Exit status
as shown below.
1 2 3 4 5 6 |
Exit status: 0 if OK, 1 if minor problems (e.g., cannot access subdirectory), 2 if serious trouble (e.g., cannot access command-line argument). |
1 2 3 |
man grep |
Scroll downwards and search for the Exit status
as given below.
1 2 3 4 5 6 7 |
EXIT STATUS Normally the exit status is 0 if a line is selected, 1 if no lines were selected, and 2 if an error occurred. However, if the -q or --quiet or --silent is used and a line is selected, the exit status is 0 even if an error occurred. |
It is always recommended to use the
man
command to check the exit status of a particular command you will use.
If your user is a technical person, then it is OK to display the exit codes to inform whether the command was executed successfully or not. But what if you are working for a non-tech person? In that case, we have to communicate a message in his understandable language. So let’s see how to do it.
Use $?
Variable with if-else
Statement
Use shell variable ($?
) with if-else
statement to show the customized message if the command ran successfully/unsuccessfully in Bash.
1 2 3 4 5 6 7 8 |
date if [ $? -eq 0 ]; then echo "The 'date' Command Succeeded." else echo "The 'date' Command Failed." fi |
1 2 3 4 |
Mon May 22 06:03:16 AM UTC 2023 The 'date' Command Succeeded. |
This approach is best suited for a non-tech user to know whether the command succeeded or not. In the above example, as soon as we ran the date
command, the exit code was stored in the $?
variable.
We used the $?
with the -eq
operator in the if
statement to determine if the value of the shell variable ($?
) equals 0
. If it is, display the The 'date' Command Succeeded.
message using the echo
command; otherwise, jump to the else
section to print the The 'date' Command Failed.
message on the Bash console.
Use $?
with &&
and ||
Operators
Use the shell variable ($?
) with &&
and ||
operators to display a custom message based on the command. It is the compact form of if-else
. See the following example.
1 2 3 |
date && echo "The 'date' Command Succeeded." || echo "The 'date' Command Failed." |
1 2 3 4 |
Mon May 22 06:10:19 AM UTC 2023 The 'date' Command Succeeded. |
Here, the command after the &&
operator would run if the previous command was executed successfully (exit status was 0
), whereas the command after the ||
operator would run if the last command failed (non-zero exit status). The date
command succeeded in the above example, so the echo
after &&
was executed.
Using PIPESTATUS
Array
If you are working with piped commands, use the PIPESTATUS
array to capture the exit codes of all commands.
1 2 3 4 |
echo "Hello, world!" | grep "world" | sed 's/world/Java2Blog/' echo ${PIPESTATUS[@]} |
1 2 3 4 |
Hello, Java2Blog! 0 0 0 |
In the above example, we have three commands that we piped together. First, we executed the echo
command and forwarded its output ("Hello, world!"
) to the grep
, our second command. Here, the grep
searched and found the world
pattern and piped it to the sed
command. The sed
command replaced the world
with Java2Blog
.
Next, we used an echo
command with bash array (${PIPESTATUS[@]}
) to print exit statuses of all commands in the most recently executed pipeline. The value at index 0
represented the exit code for the first command in the pipeline, the value at index 1
denoted the value of the second command in the pipeline and so on.
In the ${PIPESTATUS[@]}
bash array, every element represents an exit code for a particular command within the pipeline. Therefore, as all commands were executed successfully so, we got all zeros (0 0 0
), as you can see in the above output.
Let’s see another example where at least one command fails.
1 2 3 4 |
echo "Hello, world!" | grep "planet" | sed 's/world/Java2Blog/' echo ${PIPESTATUS[@]} |
1 2 3 |
0 1 0 |
Here, we got 1
for the grep
command, which searched for the planet
that did not exist in the received input; here, the input for the grep
command was the output of the echo "Hello, world!"
command.
The
${PIPESTATUS[@]}
bash array contains the exit codes for the most recently executed pipeline. In${PIPESTATUS[@]}
, the${PIPESTATUS}
refers to thePIPESTATUS
array, which is an array to capture the exit codes of the commands within the pipeline. At the same time, the[@]
notation expands the array into the list of its items.
If you are not interested in all commands’ exit codes, then you can access the exit code for a particular command using index notation as follows.
1 2 3 4 |
echo "Hello, world!" | grep "planet" | sed 's/world/Java2Blog/' echo ${PIPESTATUS[0]} |
1 2 3 |
0 |
You must know your command’s number (n
) in the pipeline for the above approach. Note that the index in the index notation would be n-1
because arrays always start with 0
.
Why am I getting the exit code for the last-run command regardless of whether the exit code was zero or non-zero, while I am only interested in getting the exit code for the last-run command causing an error (with a non-zero exit code)? For that, we must use the trap
command. So let’s learn it below.
Using trap
Command with ERR
Signal
Use the trap
command with the ERR
signal to get the exit code of the last-run command, which caused an error in Bash.
1 2 3 4 5 |
trap 'exit_code=$?' ERR dates echo "Exit Code: $exit_code" |
1 2 3 4 |
bash: dates: command not found Exit code: 127 |
First, we used the trap
command to set up a trap for the ERR
signal, which was triggered when any command within the script failed and exited with a non-zero code. Then, the code written in single quotes ('exit_code=$?'
) was executed when the ERR
signal was caught. Here, the exit_code=$?
line assigned the exit code of the last-run command to the exit_code
variable; you can refer to [Using $? Variable](add the link here) section to learn about this line in detail.
Why did we set up this trap? We did it to capture the exit code for the last executed command after the trap
statement. The echo
command was used to display the exit status of the last run command.
The
exit_code
variable will only hold the command’s exit code executed immediately after thetrap
command/statement. If you have additional commands to run, their exit code will not be captured in this variable.
Let’s see another example which does not have any errors.
1 2 3 4 5 |
trap 'exit_code=$?' ERR date echo "Exit Code: $exit_code" |
1 2 3 4 |
Mon May 22 16:21:35 UTC 2023 Exit code: |
We could not see the exit code in the above output because the ERR
signal was not triggered. As the ERR
signal was not activated, the code enclosed within single quotes ('exit_code=$?'
) would not run. Why the ERR
was not triggered because the command within the trap
block (date
command) was executed successfully.
Now, think of a situation where you have some commands running in the background for which you want to grab the exit code. So, let’s explore how we can do it.
Using wait
Command
Use the wait
command to get the exit code of the last command running in the background and completed.
1 2 3 4 5 6 |
date & wait $! exit_code=$? echo "Exit code: $exit_code" |
1 2 3 4 5 6 |
[1] 361 Tue May 23 05:38:12 UTC 2023 [1]+ Done date Exit code: 0 |
We used the date
command with the &
sign to run it in the background. Executing commands in the background allowed the script to continue running while the date
command ran concurrently.
Here, the wait
command waited for the background process, identified by $!
, representing the process ID (PID
) of the last-run background command (date
in this case). Here, the wait
command suspended the execution of the script until the background process finished.
Once finished, we captured the exit code using the shell variable ($?
) and stored it in the exit_code
variable, which was further used with the echo
command to display on the Bash console.
You can observe the above output; first, we see the process id running in the background. Then, we see the date
command’s output and a message saying the command was done. Lastly, we saw the exit code of the date
command running in the background.
Let’s modify the code and write an incorrect command; we are using the dates
command in the following example.
1 2 3 4 5 6 |
dates & wait $! exit_code=$? echo "Exit code: $exit_code" |
1 2 3 4 5 6 |
[1] 507 bash: dates: command not found [1]+ Exit 127 dates Exit code: 127 |
The above code worked fine. We waited for the dates
command to finish, grabbed its exit code and displayed it. As the dates
command was incorrect, we got a 127
exit code indicating a command not found error.
Remove the wait $!
line from the code as follows and re-run it to see the importance of wait $!
.
1 2 3 4 5 |
dates & exit_code=$? echo "Exit code: $exit_code" |
1 2 3 4 5 |
[1] 623 Exit code: 0 bash: dates: command not found |
This time, we got exit code 0
, but we also had an error stating command not found, which means the command exit_code=$?
was executed before the dates &
finished. This is why we use wait $!
to let the command complete and then grab the exit code.
Further reading:
Until now, we executed all the commands on the Bash terminal. What if we are instructed to write commands in the script and exit the script immediately as soon as any command causes a non-zero exit code? See the following section.
Using set
Command
Use the set
command with the -e
option to exit the script immediately if any command within the script returns the non-zero exit code in Bash. We write the following script in the test.sh
file.
1 2 3 4 5 6 7 8 |
set -e echo "Welcome to Java2Blog!" dates exit_code=$? echo "Exit code: $exit_code" echo "Let's learn together" |
1 2 3 |
bash test.sh |
1 2 3 4 |
Welcome to Java2Blog! test.sh: line 3: dates: command not found |
We used the set
command enabling the -e
option, which would exit the script immediately if any of the commands returned a non-zero exit code. For example, in the above script, the first echo
command successfully printed Welcome to Java2Blog!
, but the dates
command returned a non-zero exit code (127
). That’s the reason the last three statements were not executed.
Let’s remove the set -e
command from the script and observe the output below.
1 2 3 4 5 6 7 |
echo "Welcome to Java2Blog!" dates exit_code=$? echo "Exit code: $exit_code" echo "Let's learn together" |
1 2 3 |
bash test.sh |
1 2 3 4 5 6 |
Welcome to Java2Blog! test.sh: line 2: dates: command not found Exit code: 127 Let's learn together |
See, the first echo
command succeeded and printed Welcome to Java2Blog!
on the Bash console. Then, we got an error displayed on the console saying the command was not found, which was caused by the dates
command. At this step, the script didn’t stop but continued the execution and printed the exit code (127
) and the Let's learn together
message.
The
set -e
assists us in terminating the script if any of the commands from the script causes an error (returns the non-zero exit code).
So, we explored various approaches to capture the exit code of the last run command. It’s up to you for which command you want to grab the exit code. Is it the last executed command, the command running in the background, or any from the script? You can go ahead with the solution as per your use case.
That’s all about how to get exit code of last command in Bash.