Exit, Exit! Abort, Raise…Get Me Outta Here!

Share this article

Exit, Stage Left.

Every time you enter an irb session, boot a ruby script, or run a test runner you’re starting up a process. This goes for anything on your system, not just Ruby code. For instance, the same is true for shell commands like grep, cat, or tail. We spend lots of time and effort talking about the proper way to write code and ensure that it executes efficiently, but what about how it leaves its mark on the world? There’s a precious opportunity to communicate information effectively when a process exits. This article will shed light on why exiting properly is important and how you can do so from your Ruby programs.

Code, the Exit Kind

When a process exits, it always does so with an exit code. An exit code is a numeric value between 0 and 255. There’s a convention in the Unix world that an exit code equal to 0 means that the program was successful. Any other exit code denotes some kind of failure.

Why Do Exit Codes Matter?

Exit codes are communication. The base case for exit codes is success, you hope that your process exits successfully every time. In the case that something went wrong, you can specify up to 255 different reasons for failure. Exit codes are crucial for providing a generic way to understand and report failure cases back to users, so that they can correct the action. As an aside, if you’re not convinced that exit codes are something worth caring about, ponder HTTP status codes for a moment; in case of a failure response, there are a multitude of status codes denoting why a request failed that tell the user what they should do next. What do you think inspired HTTP status codes?

A Non-Ruby Example

We begin by looking at the grep command for an example and then look at doing the same thing from Ruby. Note that I’m using zsh with setopt printexitvalue to see non-zero exit codes.
$ echo foo | grep bar
zsh: done echo foo |
zsh: exit 1 grep bar
In this example we’ve printed the string ‘foo’ and grepped for ‘bar’. Obviously, it won’t match. zsh tells us that the echo command is ‘done’, aka it exited successfully. The grep command exited with a code of 1. This means it was unsuccessful. For the grep command an exit code of 1 means that it wasn’t able to match any of the inputs. Compare that with:
$ echo foo | grep foo

foo
In this example, it was able to match on the ‘foo’ string and so it exited successfully. Also, zsh doesn’t bother telling us that everything was successful. Now look what happens when we send an invalid option to grep:
$ echo foo | grep --whodunnit foo
grep: unrecognized option `--whodunnit'
Usage: grep [OPTION]... PATTERN [FILE]...
Try `grep --help' for more information.
zsh: done       echo foo |
zsh: exit 2     grep --whodunnit foo
This time we passed an unknown option to grep
and its exit code was 2. So we know that, for grep, if the exit code is 1 then there were no matches found; if the exit code was 2 then the command is not being used properly.

Exit-code Based Deployments

One more example showing the importance of exit codes before we dive into how to specify them from Ruby. Have you ever deployed your code like this?
rake test && cap deploy
You may be familiar with the && in your shell. It chains two commands together specifying that the second command should only be executed if the first command exits successfully. Exiting successfully means exiting with a code of 0. Any Ruby test runner is smart enough to exit with a non-zero exit code if there’s a test failure. This is an example of the fact that printing errors to the console is not enough. Using exit codes properly allows commands to work together in scripts and allows tasks to be automated.

The Base Case

If you’ve never written a test runner or command line program, then there’s a good chance that you’ve never needed to use one of the explicit methods to exit your Ruby program. So, what happens if you don’t use one? The base case, when Ruby exits after executing all of your code, is to exit successfully! No surprise there. If you don’t specify otherwise your process will exit with a code of 0.

Bailing Early

The first situation where you’d want to explicit use one of the exit statements is probably when you want to exit a process before executing all of the code. Let’s say you had a Ruby script called hasit. The script takes a pattern as the first argument and some data from STDIN. If any line in the input matches the pattern then the script exits successfully. If it gets to the end and finds no matches then it exits with a code of 1. Here’s the script in it’s entirety.
#!/usr/bin/env ruby

pattern = ARGV.shift
data = ARGF.read

data.each_line do |line|
  exit if line.match(pattern)
end

exit 1
It exits early using Kernel.exit if a match is found. Calling that method without an argument will exit with the 0 exit code.

Fail

You can see on the last line of the script that you can pass an integer argument to Kernel.exit to specify a non-success exit code. What if we valued an error message in this case rather than just an exit code? Kernel.abort was made for that. Calling abort and passing a String as the argument will cause your process to exit with a status code of 1 and print the string to STDERR. Let’s update the script to exit with a message:
...

data.each_line do |line|
  exit if line.match(pattern)
end

abort "No matches found"
With either of these methods our hasit script can be used properly in shell constructs like so:
$ hasit debugger lib/* && echo "Don't commit debuggers..."
$ hasit debugger lib/* || cap deploy

Cleaning Up After Yourself

Going hand-in-hand with proper exit codes is a little-known but useful feature of Ruby: at_exit handlers. By passing a block to Kernel.at_exit you can specify code that should be executed before the process exits, a good place to clean up anything that won’t be cleaned up as part of normal execution.
...
at_exit {
  # cleanup temp files
  # close open connections
}

data.each_line do |line|
  exit if line.match(pattern)
end

abort "No matches found"
Now, no matter what method your process uses to exit, you can count on that block of code to be called before exiting. Except in one case! There is a method Kernel.exit! (notice the bang) that behaves the exact same as Kernel.exit, except it skips any at_exit handlers before exiting. This method can be used to exit a process immediately, skipping any exit handlers on the way.

Falling Through the Cracks

The last way to exit a process is an unhandled exception. This is something that you never want to happen in a production application, but it happens constantly when running tests, for instance. An unhandled exception will set your exit code to 1 and print the exception details to STDERR.

My Classy Exit

Generally if you’re writing a command line application you’ll want to use Kernel.exit or Kernel.abort to handle your needs for exiting processes properly. In extreme cases, you’ll make use of Kernel.exit!. Full source of the hasit script is available here. It’s good to keep in mind that any human being who will be looking at your exit codes will surely appreciate a little distinction between different errors so they can figure out where they went wrong. Giving your command the same conventions as other common commands will make your command that much easier to use. exit 0

Frequently Asked Questions (FAQs) about Ruby Exit Commands

What is the difference between exit and abort in Ruby?

In Ruby, both exit and abort are used to terminate a program, but they do so in different ways. The exit command ends the program normally, running any at_exit handlers before it finishes. On the other hand, abort terminates the program immediately, without running any at_exit handlers. It also prints any strings passed to it to STDERR.

How can I use exit codes in Ruby?

Exit codes in Ruby can be used to communicate the status of a program when it ends. By default, a program that ends normally returns an exit code of 0, while a program that ends due to an unhandled exception returns 1. You can specify a different exit code by passing it as an argument to the exit or abort command, like so: exit(3).

What is the purpose of at_exit handlers in Ruby?

at_exit handlers in Ruby are blocks of code that are executed just before a program ends. They can be used for a variety of purposes, such as cleaning up resources, logging, or displaying a farewell message to the user. You can define an at_exit handler using the at_exit method, like so: at_exit { puts “Goodbye!” }.

How can I handle exceptions in Ruby?

Exceptions in Ruby can be handled using the begin, rescue, and ensure keywords. The begin block contains the code that might raise an exception, the rescue block contains the code that is executed if an exception is raised, and the ensure block contains code that is always executed, whether an exception is raised or not.

What is the difference between exit and exit! in Ruby?

The difference between exit and exit! in Ruby is that exit runs any at_exit handlers before terminating the program, while exit! terminates the program immediately, without running any at_exit handlers.

Can I use exit in a Ruby on Rails application?

While you can use exit in a Ruby on Rails application, it’s generally not recommended. Exiting a Rails application abruptly can lead to unexpected behavior, such as leaving database transactions open or not sending queued emails. Instead, it’s better to let Rails handle the application lifecycle.

How can I test exit behavior in Ruby?

Testing exit behavior in Ruby can be tricky, as calling exit or abort in a test will terminate the test suite. One approach is to use the at_exit method to define a handler that raises an exception, which can then be caught and asserted in your test.

What is the difference between raise and exit in Ruby?

The raise command in Ruby is used to raise an exception, which can then be caught and handled by a rescue block. The exit command, on the other hand, is used to terminate the program. If an unhandled exception is raised, Ruby will terminate the program and return an exit code of 1.

Can I pass a message to abort in Ruby?

Yes, you can pass a message to abort in Ruby. The message will be printed to STDERR and the program will be terminated immediately. For example, you can write abort(“An error occurred.”) to display the message “An error occurred.” before terminating the program.

How can I catch an exit in Ruby?

Catching an exit in Ruby can be done using the at_exit method, which defines a block of code to be executed just before the program ends. This can be used to perform cleanup tasks or to log the exit status. However, note that at_exit handlers will not be run if the program is terminated with exit!.

Jesse StorimerJesse Storimer
View Author

Jesse Storimer is a programmer and author. Employed as a Senior Developer at Shopify, Inc., he also stays up late at night to self-publish books about system programming for Ruby developers. He writes a blog at jstorimer.com and can almost always be found spending time with his wife and two daughters when afk.

cli
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week