Adding executables to your PATH for fun
Adding executables to your PATH is fun, easy, and a great way to learn about how your machine works.
This pattern is especially useful for long commands that you need to run frequently.
Learning how to add executables to your PATH will help you understand how libraries like pyenv use the shim design pattern to seamlessly switch between Python versions.
While we're learning about this design pattern, we'll also review how your machine uses the PATH and where executables are stored on our system.
Building a more useful file listing command
The ls
command outputs the files and folders in the current directory.
The ls
output is more useful when it is run with flags so the output shows hidden directories and file sizes.
It's common to create a ll
command that runs ls -ahlF
. You don't want to always type ls -ahlF
every time you do a file listing.
It's best to use a ~/.bash_profile
alias for something simple like this, as described in this answer. We'll create an executable and add it to our path for learning purposes. This will set us up nicely for a more complicated example.
Let's start by creating a ~/.cali/ll
file. We'll put our executables in the ~/.cali
directory, following this design pattern.
mkdir ~/.cali
touch ~/.cali/ll
Open the ll
file with a text editor and add ls -ahlF
.
We can run the ll
script with bash ~/.cali/ll
, but we can't run ~/.cali/ll
yet because the file isn't an executable.
Let's make the ll
file an executable: chmod +x ~/.cali/ll
.
We want to be able to run ll
from the command line, just like any other Terminal command. We can run ~/.cali/ll
, but we want to be able to simply run ll
from any directory, similar to ls
.
Let's append ~/.cali
to our PATH environment variable.
Add this code to the ~/.bash_profile
file:
export PATH=$PATH:~/.cali
We can reload the bash profile with source ~/.bash_profile
and then run ll
from the command line.
Understanding PATH
The PATH environment variable is an ordered list of directories that contain executables.
You can view the contents of your PATH by running echo $PATH
. It'll return something like this: /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/matthewpowers/.cali
.
Each directory is separated by a colon. It's easier to view them as a list:
- /usr/local/bin
- /usr/bin
- /bin
- /usr/sbin
- /sbin
- /Users/matthewpowers/.cali
Here are some of the files contained in /usr/local/bin
on my machine.
We can type which autoheader
to find where this executable file is stored on my system.
We can type which ls
to see that the ls
executable is stored in the /bin
directory.
When ls ~/Documents
is run, your computer will start by looking for an ls
executable in the /usr/local/bin
directory. It won't find one, so it'll look in /usr/bin
. It wont find one there either, so it'll look in /bin
. It'll find the executable in /bin
, so it'll stop looking and use the /bin/ls
executable.
We can even type which which
to see that the which
executable is stored in /usr/bin
:
This is too much fun!!!
Let's type which ll
and see where the ll
executable is stored:
Of course, ll
is in the ~/.cali
directory - that's where we put it!
Adding s3_ll
The AWS CLI offers this command to see the size of a S3 bucket: aws s3 ls --summarize --human-readable --recursive s3://bucket-name/directory
.
This command is too long to remember. Let's create an executable that takes a single argument. We want s3_ll my_bucket/some_folder
to run aws s3 ls --summarize --human-readable --recursive s3://my_bucket/some_folder
.
Let's create the ~/.cali/s3_ll
file.
touch `~/.cali/s3_ll`
Open the file and add this code: aws s3 ls --summarize --human-readable --recursive s3://$1
The $1
is how we pass the argument from the Terminal command to the shell script. In the s3_ll s3_ll my_bucket/some_folder
, s3_ll
is the command and my_bucket/some_folder
is the argument. $1
is how we access the argument in the shell script.
Shim design pattern
We appended the .cali
directory to our path - we put the directory at the end of the PATH environment variable.
The shim design pattern depends on prepending a directory to our path to intercept commands.
pyenv and rbenv use the shim design pattern to allow users to seamlessly switch between Python and Ruby versions.
Read this post to learn more about the shim design pattern.
Next steps
Adding executables to your path is fun and useful.
It teaches you about how your system executes commands.
Customizing your environment is great, as long as the customizations are documented and stored in a version controlled repo. You should be able to easily replicate your custom setup on a different machine. Take a look at cali for an example of customizations that can be easily transitioned to a new machine.