Managing Dotfiles

Have you ever asked yourself what the ~/.bash_profile, ~/.profile and ~/.bashrc files are good for? What do you do with them in the first place? Why are there so many of such files? And which one do you use in which case? If so, this blog post is for you.

From the Bash Reference Manual:

When bash is invoked as an interactive login shell, […] it looks for ~/.bash_profile, ~/.bash_login, and ~/.profile, in that order, and reads and executes commands from the first one that exists and is readable.

When an interactive shell that is not a login shell is started, bash reads and executes commands from ~/.bashrc, if that file exists.

What does that mean for macOS users?

On macOS, opening a new terminal window invokes a login shell by default. A shell can be login or non-login as well as interactive or non-interactive. So there are four possible combinations. Non-interactive login shell are very rare, so don’t worry about them. When we talk about login shells on macOS, let’s agree we’re talking about interactive login shell. That being said, whenever you open a new terminal windows bash will try to read ~/.bash_profile before it reads anything else, according to the bash reference manual. Only in case ~/.bash_profile does not exist, bash will try to read ~/.profile as a fallback.

In former days, ~/.bash_profile was meant for the bash shell and ~/.profile was for its predecessor, the sh shell. But since sh isn’t used very much on macOS anymore, it doesn’t matter whether you use ~/.bash_profile or ~/.profile for the bash shell. This is mostly a concern if you’re working on multiple machines, e.g. a Mac at home and Linux at work, and you want your dotfiles to also work on machines where your login shell isn’t bash.

If you have that kind of problem, you can put your environment variable definitions like PATH, JAVA_HOMEor GOPATH into ~/.profile and source them from ~/.bash_profile. If you only have a Mac and that is your only computer, it doesn’t matter whether you choose ~/.bash_profile or ~/.profile. Just pick one of them and stick with it, since ~/.profile will not be read if ~/.bash_profile exists.

In case you start to use bash inside of existing sessions of bash, you get an interactive non-login shell. You can find a really good explanation of the four different kinds of shells here. Only such interactive non-login shells read ~/.bashrc. But these “second-instance shells” read neither ~/.bash_profile nor ~/.profile, they read only ~/.bashrc. So ~/.bashrc is the place where you put stuff that isn’t inherited by subshells automatically and applies only to bash itself, such as alias and function definitions, shell options, and prompt settings. Don’t put environment variable definitions (e.g. as PATHor JAVA_HOME) in ~/.bashrc, since they will then only be set in programs launched via the terminal, not in programs started directly with an icon or menu or keyboard shortcut. For example, you need to set JAVA_HOME for build tools like Maven or Gradle, and I think Matlab needs JAVA_HOME, too. If you put JAVA_HOME into ~/.bashrc, I think these programs wouldn’t know where Java lives on your machine and therefore wouldn’t work. If you want to read more, this answer on StackOverflow was really helpful to me.

How to edit these files

Now that we have a reasonable understanding of these files and what goes where, you can choose to either create new files from scratch or migrate your existing configuration to the more appropriate files. Be sure to backup you configuration so that you don’t lose data and your applications aren’t working anymore afterward.

Enter the following commands into a terminal to create the relevant files if they don’t exist yet. In case they already exist, these commands will do no harm and only update the “Last modified on” date:

touch ~/.bash_profile
touch ~/.profile
touch ~/.bashrc
touch ~/.aliases

I decided to put my aliases in a separate file called .aliases rather than in ~/.bashrc and source it from ~/.bashrc instead. This allows me to keep my aliases, should I decide to switch to a different shell, say zsh or fish. More on this later.

Since these files are hidden, you won’t be able to see them in Finder by default. There are several options to open them. In case you’re familiar with the text editor called nano, you can open and edit a file like so:

nano ~/.bash_profile

To exit first press Ctr+X and then Y to save.

In case you’re not comfortable with nano, you can edit a file with Apple’s TextEdit like so:

open -e ~/.bash_profile

In case you have Sublime Text installed, you could also open a file in Sublime as follows:

subl ~/.bash_profile

After you’ve opened ~/.bash_profile, replace the existing content with the following lines, so that ~/.bash_profile contains nothing else than this:

if [ -f ~/.profile ] ; then
    . ~/.profile

# If the shell is interactive, source ~/.bashrc
case "$-" in *i*) if [ -r ~/.bashrc ]; then . ~/.bashrc; fi;; esac

Open ~/.bashrc the same way and replace its content with:

source ~/.aliases

Then, open ~/.aliases and enter your shortcuts for frequently used terminal commands. A few examples could be:

alias ..="cd .."
alias ...="cd ../.."
alias softwareupdate='softwareupdate -i -a'
alias bu="brew update; brew upgrade; brew cleanup; brew doctor"
alias cu="conda update --all --yes; conda clean --all --yes --quiet"
alias pu="pip3 list --outdated --format=columns"

I’m sure you’ll find a lot more if you do a quick Google search.

The last file missing is ~/.profile. Open it and add your environment variables, the lines of code that look like this:

# Homebrew
export PATH=/usr/local/bin:$PATH
# Java
export JAVA_HOME=$(/usr/libexec/java_home)
# Ruby
if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi
# Go
export GOPATH=$HOME/go
export PATH=$PATH:$(go env GOPATH)/bin

Of course these are not my complete dotfiles. Everybody has different preferences and uses different aliases, so a Google search how other developers are organizing their dotfiles is worthwhile. I hope, however, I could set you on your feet with this basic introduction, so that you can build your own dotfiles.

What if I’m using the Z shell?

If you followed my guide on how to customize your terminal, you are probably using the much more powerful and customisable Z shell instead of the default shell bash. The Z shell offers many features bash is lacking, like auto-completion or support for plugins such as syntax-highlighting. It also let’s you change prompts which is especially handy when working with git.

However, by switching your shell from bash to zsh you have to change your current configuration. The Z shell does not read ~/.bash_profile nor ~/.profile, since its syntax is not completely compatible with the sh shell or bash. Both ~/.bash_profile and ~/.profile are designed for Bourne-like shells, thus bash and sh but not zsh. The Z shell uses its own file, ~/.zprofile.

However, zsh works a little bit different than bash. Unlike bash which reads either ~/.bash_profile or ~/.bashrc depending on whether bash runs as login shell or non-login shell, zsh doesn’t make that difference. The Z shell reads both ~/.zprofile and ~/.zshrc, in that order, no matter whether an interactive shell is a login or non-login shell.

So if you want to re-use your current configuration and also be able to switch between bash and zsh without losing your configuration or the need to write everything twice, you can source ~/.profile from ~/.zprofile, so that the environment variables in ~/.profile work for bash and zsh. First, create ~/.zprofile:

touch ~/.zprofile

As I said, the syntax of zsh is not completely compatible with sh or bash. To read sh syntax with zsh, we need to emulate sh. To do so, open ~/.zprofile like we did before:

open -e ~/.zprofile

Then, replace its content with:

[[ -e ~/.profile ]] && emulate sh -c 'source ~/.profile’

All other settings regarding zsh, such as aliases, go into ~/.zshrc. That’s why we created a separate file for our aliases and didn’t put them into ~/.bashrc. This way we’re able to source them from ~/.zshrc, too:

source ~/.aliases

That’s it! I hope you’ve got everything to work on your machine. If you have any questions, please don’t hesitate to ask in the comment section down below.