Your Terminal and You: Dotfiles

Your Terminal and You: Dotfiles

Have you ever watched a skilled engineer pull up a terminal, issue a few keystrokes and kick off a whole series of processes in seconds? It can be downright magical, some even wizard-like. A few are hard to distinguish from cackling necromancers raising processes from the dead.

Photo by Rhett Wesley / Unsplash

I'll let you in on a little secret, engineers who are comfortable on the command line are not wizards, they are pack rats. They accumulate techniques and store them away in a toolbox and take it everywhere they go.

That engineer can sit down at any machine and within minutes reach into their toolbox and become productive.

Let's take a look at one technique that I use. It is not my technique, like all good pack rats I borrowed it from countless others and added them to my toolbox.

The technique I use, is fairly macOS focused. It will work on macOS, Linux and WSL on Windows. You can do similar things in PowerShell, I encourage you to take a look at Michael Jolly's devtoolbox project for examples.

Let's start by creating your toolbox in a way that you can access it from any machine. Head to GitHub and create a new repository called dotfiles. This is a common name for this kind of repository, remember it. You can find all kinds of great examples by looking at other peoples dotfiles repos.

Recommended settings for new dotfiles repository

If you want to allow others to use tools from your toolbox, consider adding an MIT license and making your repo public. Be sure to add a README file. It will be useful later when you want to start documenting things for others to consume.

Once you have created the repository, let's clone it to your machine. We will do this in a special place called your home directory located in your user directory. We will also give it a special name.

cd ~
git clone .dotfiles

Use git to clone the repository to a directory named .dotfiles. Notice the period in front of the directory name. This makes the directory a hidden one. Trying running these two commands to see what I mean.

ls -a

The first command lists the files and directories in the current directory. Notice that the .dotfiles directory is not displayed. The second command uses the -a flag to show all files and directories.

Here is what it looks like on my machine currently.

running ls vs. ls -a

As you can see, I have a variety of dot files for various things. Can you spot my .dotfiles directory?

Next, you will need to wire up your .dotfiles directory in a way that your terminal application will use it. By default, macOS terminal uses zsh as it's shell. So create a zsh directory in the .dotfiles directory and add a .zshrc file to it.

cd ~/.dotfiles
mkdir zsh
touch ./zsh/.zshrc

When a new shell is started in a terminal application, zsh will look for a .zshrc file in the home directory of the current user. It will then execute that file. Of course, your file is located in the .dotfiles/zsh directory.

We can fix that by creating a symbolic link between the file's current location on disk and where zsh expects it to be. To do that, create an install script in your .dotfiles directory.

echo "#\!/usr/bin/env sh" > ./install
echo "echo \"Installing..\"" >> ./install
chmod +x ./install

There is a bit of magic going on here, so let's break it down. The first command creates a file named install and adds a special value as the first line. This value is called a shebang. It is used to decide how a file will be executed. In this case, the shebang checks the local system environment for the configured shell. So this install script will execute in zsh.

The next command, adds a line to the install file that will write the text "Installing..." to the terminal when the install script is executed. We will use this to verify everything is working correctly.

Text files in macOS are not typically executable. So the next command explicitly set the install text file as executable. This only needs to be done once when you create a script file.

The final command executes the install script. The output on my machine looks like this.

Finally, add the command needed to create the symbolic link to the .zshrc file to the install script. Open the install script in a text editor and add the following line.

# shell settings
ln -sv /Users/bobby/.dotfiles/zsh/.zshrc ~/.zshrc

Note that your user directory name will be different than mine. Be sure to change the bobby value to the name of your user directory. If you are not sure what that name is run echo ~ in your terminal to display the full path to your home directory.

This is a key concept in creating your toolbox. Instead of just looking up commands and running them once to set things up make a script file that does it so you do not have to look it up again.

I simply looked at my install script in my dotfiles repository to find the symlink command. See, I am not a wizard I am a pack rat.

At this point you should commit your changes to the Git repository. Now, run the install script.


You may recieve an error that a file named .zshrc already exists in your home directory. In that case, make a backup of that file for later review and run the install again.

mv ~/.zshrc ~/.zshrc_backup

You can now list the contents of your home directory and you should see the .zshrc file.

ls -a ~

Remember though, this is a symbolic link to the file in our .dotfiles repository. Which basically makes it our entry point to setup our shell any way we want.

Now, let's make it do something interesting. Open the ~/.dotfiles/zsh/.zshrc file in a text editor and add the following.

for DOTFILE in `find /Users/bobby/.dotfiles/system`
  [ -f $DOTFILE ] && source $DOTFILE

This script will execute each of the files located in the system directory in our .dotfiles directory. Making them available to the current shell.

I typically setup a series of files in the system folder: .alias, .env, .function, .path, and .prompt.

  • .alias - contains a set of command aliases to commands that already exist on my machine.
  • .env - contains commands to add additional functionality into my terminal environment.
  • .function - contains a set of functions that are useful in my terminal environment.
  • .path - updates the $PATH to include any other binary sources on my machine.
  • .prompt - contains the configuration needed to setup my fancy shell prompt.

Aliases can be very useful for common commands containing elaborate flags and values that you use regularly. Let's set up a few so you can see how easy it is. First, we need to set up the .alias file.

cd ~/.dotfiles
mkdir system
touch system/.function

Then add the following code to the .alias file.

alias l="ls -la"       # List in long format, include dotfiles
alias ld="ls -ld */"   # List in long format, only directories
alias ..="cd .."
alias ...="cd ../.."
alias ....="cd ../../.."

These aliases are ones that I use for navigating around the file system. As you can see they are simply shortcuts for other commands.

Functions on the other hand allow you to do more complex tasks with a single command. Let's set you up with one of my favorite functions. Typically when I create a new directory using the mkdir command the very next thing I do is change directories into the one I just created.

Let's create a function that does this in one command instead of two. First, you need to create the .function file.

cd ~/.dotfiles
touch system/.function

Next, add the following code to the .function file using a text editor.

# Create a new directory and enter it
function mk() {
  mkdir -p "$@" && cd "$@"

Now when you wish to create a directory and enter it you can execute the command mk foo.

Commit these changes to your dotfiles repository.

And with that your toolbox is setup. Start collecting the tools that make you look like a wizard. You can take a look at my dotfiles repository for ideas. Or, search GitHub for others.

Follow me on Mastodon!