Want to see the full-length video right now for free?Sign In with GitHub for Free Access
To start, when we say "CLI" or "command line utility," we're talking about a
program that we can run at the command line by simply typing the command name.
git is a great example of the sort of commands we could write (although ours
will of course be a good bit simpler).
To begin, we'll work with a very simple bash script with the following contents
echo 'hello world'
We can then run this very basic shell script by specifying the
interpreter (similar to using
$ sh script.sh
This is a great start, but our goal is be able to run this without specifying
the interpreter, so we can try that as follows (for now we'll need to specify
the current path with
$ ./script.sh zsh: permission denied: ./script.sh
Here we hit our first wall since file can not be executed directly.
By default, the text files we create are not executable and we have to
explicitly mark them as executable if we want to use them without always
needing to specify the language / interpreter to use. We can mark our script
as executable using the
$ chmod +x script.sh
And now we can directly run our script with
Shell is fine, but it is far from my favorite language to write in. Instead,
I'd rather write in a language like Ruby. We can rename our script file to
script.rb and update the contents to:
puts 'hello world'
but now we've broken our script. If we try to run it based on its new executable mode, we'll get an error:
$ ./script.rb ./script.rb: line 2: puts: command not found
This error occurs because the shell, by default, is running our script with
sh (shell) interpreter. It doesn't much care about the file extension
being "rb", that's for humans and text editors.
Instead, we can specify the interpreter to use,
ruby in this case, using a
"shebang" line. This is a special comment that must be the first line in our
file, and specifies the interpreter to use. The contents of our
file are now:
#!/usr/bin/env ruby puts 'hello world'
Here, we've added the "shebang" line to specify that we want to run via
ruby. In fact, we're going a bit further and using
/usr/bin/env which is a
command to which we pass the argument
ruby, and this command will then
provide a specific version of
ruby. This is useful if we're working with
multiple rubies via
chruby, or other tool to manage multiple
version of ruby.
Now, we can run our script directly without needing to re-specify
we can even go a bit further and remove the ".rb" extension:
$ ./script.rb hello world $ mv script.rb script $ ./script hello world
These all work, but just to go crazy, we can actually change our shebang line
to specify many different interpreters, even
The final step in building up our command line script is to free ourselves from having to know the location of the script, and instead be able to run it from anywhere.
In order to do this, we can hook into a special shell variable called
$PATH is an ordered list of directories, separated by
:s, that the
shell will search through to find executables.
As an example, here is the (abbreviated) value of
$PATH on my system,
splitting to show one directory per line:
$ echo $PATH | tr \: \\n /Users/christoomey/.gem/ruby/2.1.5/bin /Users/christoomey/.rubies/ruby-2.1.5/lib/ruby/gems/2.1.0/bin /Users/christoomey/.rubies/ruby-2.1.5/bin /Users/christoomey/Library/Haskell/bin /Users/christoomey/bin /usr/local/bin /usr/bin /bin /sbin /opt/X11/bin /usr/local/go/bin
In this case, I can take our
script file and move it into my
directory which is on my path, and from there I can run the script from
$ mv script ~/bin/ $ script hello world
You can add a directory like
~/bin to your path by adding a snippet like the
following to your shell startup file (
For a more in depth overview of
$PATH setting, check out this great Unix
StackExchange answer, or take a peek at how the thoughtbot dotfiles work
with the PATH.
Now that we know how to build these command line scripts and make them available everywhere, the question becomes, "What can we do with them?" It turns out we can do a whole lot.
The first and likely largest category these sort of command line scripts fall into are general purpose helper scripts. A few examples are:
bin/setup, this script automates deploying Upcase.
One specific use case for command line scripts is to automate Git workflows.
git will automatically look for executable scripts on your
match the command name, so running
git churn will search for a script named
git-churn that is marked as executable and available on your path.
As an example, I have the script called git-shalector in my
directory which I can then run as:
$ git shalector
git-shalector is written in Bash, but like with all our command line
scripts, we can choose any interpreted language to use, so git-publish is
written in Ruby.
Similar to Git, Ruby's Bundler has the same sort of path lookup for
subcommand magic built in. The thoughtbot dotfiles includes a custom
bundler-search subcommand that uses bundle and
(the_silver_searcher) to search for a pattern in all of your active gems.
$ bundle search 'semantic_form_for'
Often we want to chain together a number of commands using
&&, knowing that
if any command in the sequence fails, the whole sequence will stop. In order
to support this, we need to return a specific exit code from our script.
0 as the exit code means "success" and the next command will run, any other
value means "failure" and that no further commands in the
&& sequence should
run. Also, we can always check
$? as it will contain the exit code of the
$ true && echo hello hello $ false && echo hello # no output as `false` returned non-zero exit code $ echo $? 1
By default our scripts will exit with
0 as the status code indicating
success, but we can explicitly set the exit code to indicate failure as
# new script file called 'hello' puts 'hello world' exit 1
With the addition of
exit 1, this command now is viewed as failing, so if we
$ ./hello && echo after hello world
This is a bit more advanced, but since this often confuses newer users, we
wanted to cover how to work with error text. By default, all output is sent to
the standard output, aka
stdout, stream. In the event of an error, we often
want to alert the user by presenting some sort of message, but we don't want
to muck up the normal output of the script.
Luckily, there is a second output stream known as standard error or
and we can send any error text to it.
In bash this would done by echoing and redirecting to
2, which is the
numeric way to reference
echo 'this is an error' >&2
Similarly, in Ruby we have a special IO global we can use called
$stderr.puts 'this is an error'
In some cases, we just want to silence both the output and error messages. We
can do this by redirecting
stderr. Given we have the following
contents in our
hello script file:
#!/usr/bin/env ruby puts 'hello world' $stderr.puts 'errroorrrrrr' exit 1
We can run the script, redirecting the output to the file
allowing the error message to be displayed:
$ ./hello > output.txt errroorrrrrr $ cat output.txt hello world
Similarly, we can combine
stdout and redirect both into our
$ ./hello > output.txt 2>&1 $ cat output.txt errroorrrrrr hello world
Lastly, we can explicitly ignore the error output by redirecting
/dev/null which is essentially a black hole:
$ ./hello > output.txt 2>/dev/null $ cat output.txt hello world