Learn how to create a Ruby executable
Tags: bash, zsh, ruby, tutorial
Published: 2022-10-31 02:42:23

Don't like reading?  Watch the video [here](https://dev.to/jvon1904/turn-a-ruby-file-into-an-executable-command-39of).

If you have Ruby installed on your computer, we know that you can execute `.rb` files with the `ruby <file name>.rb` command.  This is <u>not</u> what we are talking about in this article.

This article is about executing ruby programs without typing `ruby`, `./`, or an `.rb` extension and being able to execute the command from anywhere in your file system.

Just like when you type `gem`, `rails`, or `irb`, and your console immediately recognizes and runs a program, we want to do this with our own custom-built Ruby file.

## Writing the program

Create a new directory anywhere.  Name it `favorite_color`.  This new Ruby program is going to do one thing, tell us our favorite color.  

Make two sub-directories inside `favorite_color`. Name them `lib`, and `bin`.  The lib directory will house the logic of the program.  The bin directory will house the executable file that requires and calls the program in lib.

By the way, 'lib' stands for 'library', and 'bin' stands for 'binaries' (executable files).  You could also name the bin folder 'exe' for 'executables.  Housing your program source code in `lib` and your executable in `bin` or `exe` is standard for Ruby gem packages.

Name the lib file `favorite_color.rb`, and name the bin file `fav-color.rb`. (We will eventually remove the `.rb` extension.)
```zsh
~ mkdir -p favorite_color/{lib,bin} && touch favorite_color/lib/favorite_color.rb favorite_color/bin/fav-color.rb
```
Your file tree should look like this:
```zsh
favorite_color
├── bin
│   └── fav-color.rb
└── lib
    └── favorite_color.rb

2 directories, 2 files
```
Now, add some logic to `favorite_color.rb`.  Here's what my class will look like.  Write this however you want.
```ruby
  1 class FavoriteColor
  2   def initialize color
  3     @color = color
  4   end
  5
  6   def declaration
  7     "Your favorite color is #{@color}"
  8   end
  9
 10   def declare
 11     puts declaration
 12   end
 13 end
```
The executable file inside the `bin` directory is responsible for loading the FavoriteColor class, initializing it, and calling it.
```ruby
  1 require_relative "../lib/favorite_color.rb"
  2
  3 favorite_color = FavoriteColor.new("teal") #insert your real favorite color.
  4
  5 favorite_color.declare
```
At this point, if you `cd` into `bin` you can execute the `fav-color.rb` file like you normally would and it should print the output to your console.
```zsh
ruby fav-color.rb
Your favorite color is teal
```
At this point, we could implement functionality in `fav-color.rb` to read an argument passed in which sets the color of the class, but for now we only want to focus on making this file a true executable.

## Adding the shebang

A _shebang_ refers to the characters `#!`.  It's the prefix used when referencing the path to the interpreter that the shell should use to execute a script from the terminal. For instance, if you look at some of the executables in your `/bin` directories that are written in shell scripts, you will see `#!/bin/sh` at the top, or something similar.  For Ruby scripts, you should reference `/usr/bin/env`.  Why is that?  Because the `env` command here is going to search for the first `ruby` interpreter in your `PATH`.  The full line will look like this.
```ruby
#!/usr/bin/env ruby
```
You might be able to just add `#! ruby` or `#!/usr/bin/ruby`, but the safest way is to do the full reference so that the executable will work on the maximum amount of machines.

At this point, your `fav-color.rb` file should look something like this.
```ruby
  1 #!/usr/bin/env ruby
  2
  3 require_relative "../lib/favorite_color.rb"
  4
  5 favorite_color = FavoriteColor.new("red")
  6
  7 favorite_color.declare
```
Now, go ahead and try to execute the file.  (Hint: at this point, with the shebang line, it is a true executable). You can still use the `ruby` command, but in order to take advantage of the shebang you need to type `./<file-name>`.  Did it work?

Most likely not. You probably don't have permission to execute the file.  Each file on your machine has permissions set for reading ('r'), writing ('w'), and executing ('x').  When you first create a file, it defaults to allow you as the creator to read and write to it, and others to read it.  In order to see those permissions, you need to use the shell commands `ls -l`, the `-l` flag standing for `long format`.  If you use ZSH, you can simply type the shortcut to this which is `ll`.  You should see something like this.
```zsh
total 8
-rw-r--r--  1 jvon1904  staff   132B Oct 30 20:47 fav-color
```
The `total 8` refers to the total size of the directory in kilobytes.

On the second line, first column, you see `-rw-r--r--`.

Although cryptic at first, this is a very simple designation for the file permissions.  

The first character is either `-` meaning it's a file. It could also be `d` for directory.  

The next three characters are permissions for the owner, which in this case are `read` and `write`.  

The fourth character is the slot for `execute`, `x`.  If the owner could execute the file, it would be `-rwx-r--r--`.  

The 5th-7th characters are for the group, which on my computer is called 'staff', and the 8th-10th characters are for others.  

## Changing the permissions for the file

In order to change the permissions, you need to use the `chmod` command.

The most straightforward way to add user permissions is to type `chmod` followed by a letter representing who is receiving the permission (`u`, the user/owner, `g`, the group, `o` others, or `a` all) followed by a `+` or `-` (adding/removing permissions) followed by the permission (`r`, `w`, or `x`) and finally the file name. 

For us it would look like this.
```zsh
chmod u+x fav-color
ls -l
total 8
-rwxr--r--  1 jvon1904  staff  132 Oct 30 21:00 fav-color
```
There happens to be another fantastic way to set all permissions for all people with only three numbers!  For instance, if you want all people to have all permissions, you would type `chmod 777 <file-name>`.  

If you type `000` you will remove all permissions completely.  Can you figure out why?

The three numbers represent the three entities being permitted (u, g, and o).

0 -> no permissions

1 -> execute permission

2 -> write permission

4 -> read permission

Then add up those numbers for any combination of the three.  So a 7 means all three permissions because 7 = 1 + 2 + 4.  Cool right?! 

In our case, you could type `chmod 744 fav-color.rb` and nothing should change.

Now that we've changed the permissions, try executing the file like `./fav-color.rb` and it should work!

## Adding the executable to your path

At this point, we should go ahead and take off the `.rb` extension because it's not needed now that we've added our shebang directive and turned the file into an executable.
```zsh
mv fav-color.rb fav-color
```
The problem now is that you must reference the entire path of the file in order to execute it.

If you are inside the directory that contains `fav-color` you must type `./` before hand.  

If you are inside the `favorite_color` root directory, you must execute it as `bin/fav-color`.  This is not ideal.  We want to be able to type `fav-color` from anywhere.

There are two ways to accomplish this.

1. Exporting the full path to the executable to your `$PATH` variable, or

2. Adding a link to the executable to one of your `/bin` directories already loaded into the `$PATH`.

If you are familiar with adding to your PATH, feel free to use the first option (adding `export PATH=$PATH:path/to/favorite_color/bin` to `.zshrc`, `.zprofile` or `.bash_profile`, etc.)

The recommended option in this case is to make the executable a binary in one of your `bin` directories.  

You've probably noticed that when you venture underneath your HOME ~ directory, there are more than one 'bin' folder.

In order to not confound system critical binaries, you should place a custom binary in `/usr/local/bin`.  We can do this by linking the file to that directory.
```zsh
sudo ln -s ~/favorite_colors/bin/fav-color /usr/local/bin
```
When using the `ln` (link) command, make sure to reference both the executable and the bin directory as abosolute paths.

You can `cd` into /usr/local/bin to make sure it's there.

I have a line like this.
```zsh
lrwxr-xr-x  1 root  wheel    44B Oct 30 22:26 fav-color -> /Users/jvon1904/favorite_color/bin/fav-color
```
It appears that linking the file to /usr/local/bin automatically allows everyone to execute it.

Give it a shot. Execute the file from anywhere in your file system simply as `fav-color`.