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.

Exit 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.


** There is no comment system yet, but you can send me a message on twitter @rderik or send me an email: derik[at]rderik[dot]com.