Exit codes for Ruby scripts Oct 15 2018
One of the most common mistakes I see in ruby scripts is the lack of good exit status, this prevents the script from being trusted in composition with other commands. The purpose of this post is to explain why exit codes are important and how to use them on our scripts.
Table of Contents
Bash Beyond Basics Increase your efficiency and understanding of the shell
If you are interested in this topic you might enjoy my course Bash Byond Basics. This course helps you level up your bash skills. This is not a course on shell-scripting, is a course on improving your efficiency by showing you the features of bash that are seldom discussed and often ignored.
Every day you spend many hours working in the shell, every little improvement in your worklflows will pay dividends many fold!
Learn moreExit codes
Every command, after completing execution, returns an exit status. This indicates if the command completed successfully or if it finished execution with an error. The exit status is represented by an integer. The general convention is that a 0 (zero) means success and anything else is an error. There are many different standards on what each code might represent, you could read the BSD Sysexits or The Linux Documentation Project - Exit codes for some ideas. In general most of the codes form 1-255 have a specific meaning if you want to raise a custom exit signal, choose a number above 255.
Let’s see the exit codes in action. If you run the following command in your shell:
1
ls
It should have listed all the files in your current directory successfully, so when we check the status code of the previous ran command we should see a 0, bash
saves the exit status of the last command in the environment variable $?
. Let’s print it:
1
echo $?
If we ran a command that exits with an error we should see a non zero number in the $?
variable:
1
2
cat non_existent_file_123845845.txt
echo $?
Composing commands
Using the exit code of a command as a conditional we can take an action, imagine we are working late and we have to leave a command running through the night but we want to make sure if there is any error on the processing we get an email saying that we need to go early to work the next day to fix it. We could do something like this:
1
generate_important_report || mail -s ‘error generating report’ ‘sysadmin@example.com’ “The report won’t be ready, an error occurred”
If the generate_important_report
command exits with anything but zero the mail will be sent.
We are assuming that our generate_important_report
was well written and will exit with a non zero. Imagine if the script was poorly written and it didn’t matter if the program finished execution successfully or not it will always return 0. We won’t get the notification and assume everything was fine, just to arrive next day to the office for a meeting without the important report.
Ruby and exit codes
We should be very conscious of how our program exits, not because we display an error it means the script is reporting that it finished with an error. Imagine that the implementation of our generate_important_report
script looks something like the following:
1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env ruby
balance = 10_000
expenses = 11_000
if expenses < balance
puts "Generate important report for employee payment"
else
puts "ERROR: we don't have enough money to pay our employees"
end
If we run that script and then check the exit status of the program we will still be getting that it finished successfully ($?
will be zero), even if we see on our screen a message that says ERROR: we don't have enough money to pay our employees
.
Ruby’s default exit value is 0 if we want to send a different exit value we should use the exit
method. The exit
method receives as a parameter a true or false value if the value is an integer that would be the returned value of our script.
Fixing our previous script it’ll look like this:
1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env ruby
balance = 10_000
expenses = 11_000
if expenses < balance
puts "Run script to make payments to our employees"
else
puts "ERROR: we don't have enough money to pay our employees"
exit 1
end
Now we can compose our script and it will correctly return 1 when the logic of the script should produce an error.
What the exit
method does is raise a SystemExit
exception, and this exception is bubbled up until it is either handled by a rescue
clause or returned by the interpreter. This means that the interpreter will return an exit code different than zero if the script producess an exception it can’t handle. For example, if our script has a division by zero we would get a $?
of 1.
1
ruby -e "x = 1 / 0"
In general, those exceptions will bubble up, but we should still be the ones in charge of exiting the script with a correct exit code.
Well, that was the basics of exit codes for ruby, there are more cases when this comes into play, for example, when we are executing commands from Ruby with the system
command. But I think this is enough for you to get started and make your scripts more robust.
As always if you have any comments or suggestions let me know.