Last Updated:

Exit, exit! Interrupt, lift... Get me out of here!

Exit, Stage left.

Every time you log into an irb session, load a ruby script, or run a test run, you start the process. This applies to everything on your system, not just Ruby code. For example, the same is true for shell commands such as grepcattail

We spend a lot of time and effort discussing the right way to write code and ensure it runs efficiently, but what about how it will make its mark on the world? There is a precious ability to effectively convey information when exiting the process.

This article sheds light on why the right output is important and how you can make it out of your Ruby programs.

Code, exit view

When a process completes, it always does so with an exit code. An exit code is a numeric value between 0 and 255. In the Unix world, there is a convention that an exit code of 0 means that the program was successful. Any other exit code indicates some kind of failure.

Why do output codes matter?

Exit codes are a link.

The basic option for exit codes is success, you hope your process completes successfully every time. In case something goes wrong, you can specify up to 255 different reasons for the failure. Exit codes are critical to providing a common way of understanding and reporting opt-out cases to users so they can fix the action.

Also, if you're not sure if exit codes are something worth caring about, think about HTTP status codes; in the case of an error response, there are many status codes that indicate why the request failed, which tell the user what to do next. Do you think HTTP status codes are inspired?

Example without Ruby

We'll start by looking at the grep command Note that I'm using zsh with setopt printexitvalue

$ echo foo | grep bar
zsh: done echo foo |
zsh: exit 1 grep bar

In this example, we typed the string "foo" and added "bar". Obviously, that won't fit. zsh informs us that the echo command is the grep command for the grep command

Compare that to:

$ echo foo | grep foo

foo

In this example, it could match the string 'foo' and completed successfully. Also, zsh doesn't tell us that everything went well.

Now let's see what happens when we send the wrong 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 grep option.

So, we know that for grep, if the exit code was 2, then the command is not used properly.

 

Exit Code-Based Deployments

Another example showing the importance of exit codes before we dive into how to specify them in Ruby.

Have you ever used your code?

rake test && cap deploy

You may be familiar with && It combines two commands together, indicating that the second command should only be executed if the first command completes successfully. A successful exit means an exit with a code of 0.

Any Ruby tester is smart enough to come out with a non-zero termination code in the event of a test failure. This is an example of how there are not enough print errors on the console. Proper use of exit codes allows teams to work together in scripts and allows you to automate tasks.

Basic case

If you've never written a test run or a command-line program, then there's a good chance 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 basic case where Ruby quits after all your code has been executed is a successful exit! There's nothing surprising there. Unless you specify otherwise, your process will terminate with code 0.

It's too early to liberate

The first situation where you want to explicitly use one of the exit statements is probably when you want to exit the process before executing all the code.

Suppose you had a Ruby script called hasit The script takes a template as the first argument and some data from STDIN. If any string in the input matches the template, the script completes successfully. If it reaches the end and finds no matches, it terminates with code 1.

Here's the whole scenario.

#!/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 A call to this method without an argument will terminate with exit code 0.

Failure

In the last line of the script you can see that you can pass an integer argument to kernel.exitkernel.abort

What if, in this case, we're evaluating the error message and not just the exit code? abort Challenge . . .

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

abort "No matches found" Let's update the script to exit with the message:

hasit

With any of these methods, our script $ hasit debugger lib/* && echo "Don't commit debuggers..."
$ hasit debugger lib/* || cap deploy

Kernel.at_exit

Cleaning up after yourself

Going hand-in-hand with the correct exit codes is a little-known but useful feature of Ruby handlers: at_exit.

...
at_exit {
# cleanup temp files
# close open connections
}

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

abort "No matches found"Kernel.exit!

Kernel.exit

Now, no matter which method your process uses to exit, you can count on this block of code to be called before exiting. Except in one case! There is a Kernel.exit method (note the explosion) that behaves exactly like Kernel.abort This method can be used to exit a process immediately, skipping any exit handlers in the path.

Falling through the cracks

The last way to exit the process is with an unhandled exception. This is something you'll never want to do in a production application, but it happens all the time, such as when you run tests.

An unhandled exception will set your exit code to 1 and print the exception information to STDERR.

My Stylish Outing

Generally, if you're writing an application for the command line, you'll want to use Kernel.exit! hasitend In extreme cases you will use do ... end , The full source {}bundle installhere .