Have you ever wondered what files like
~/.bashrc are good for? What do you do with them to begin with? Why are there so many of them? What’s the difference between them and which one do you use in which case? If so, this blog post is for you.
For starters, what does
~/.bash_profile even mean? Let’s dissect it.
It’s simply a text file with the name
It doesn’t have (and doesn’t need) a filename extension, i.e., there’s no
.html or something similar at the end.
On macOS and Linux, when you prepend a file name with a dot, you make it a hidden file.
That is, in order to view a hidden file in the macOS Finder, you have to press the three keys
. simultaneously in order to see such files.
With the same key combination, you can hide them away again.
Because their file name begins with a dot, such hidden files (which typically are configuration files) are commonly referred to as dotfiles.
The tilde character
~ tells us that this file named
.bash_profile lives in the home folder, e.g.,
/Users/davidculley/ on macOS or
/home/davidculley/ on Linux.
It’s an abbreviation that expands to your own user name. See for yourself and type this into the terminal:
Why use an extra character to tell us that?
Why not just say
/Users/davidculley/.bash_profile and hard code your user name into the path?
One reason is that your account is not named
If I wanted you to create a file, say,
hello.txt on your desktop, I couldn’t simply tell you to execute the command
You’d instead have to replace all references to my user name with your own if you want to follow my guide.
Another reason, as you already know, is that Linux uses
/home/ instead of
Scripts wouldn’t be very portable without abbreviations such as
Also, they make the path a lot shorter.
So in summary, the following things all mean the same thing (where the last two options are the most portable):
Into such a text file, you will write custom configurations, e.g., where Java lives on your system.
After all, when you want to compile your
*.java files with the compiler
javac, macOS needs to know where it can find
Thus, if you want to develop Java code, you need to save the path to where you installed the Java SDK in the
Imagine a colleague telling you, “Come to my place tonight, I’m throwing a party.” without ever telling you where he lives.
You could then tell the taxi driver, “Drive me to John’s place,” but how would you (or the taxi driver) know where to go?
These dotfiles are the means by which you tell the system where applications actually live.
The names of these files follow a convention.
You can’t rename them however you like.
Your operating system expects them to have a certain name, e.g.,
.zshrc, and depending on which case applies, the system looks into the according file.
If you were to rename such a file to, e.g.,
.my-configuration-file, it’s as if that file wouldn’t exist.
Each shell has its own set of files
A shell is basically a terminal, or rather the program in the background that executes all the commands you type into the macOS app
There are different shells. A really old one is
sh. You probably won’t encounter it anymore.
Its successor is called
bash, short for Bourne-again shell.
It’s a funny word play because
sh was developed by Stephen Bourne and thus is known as the Bourne shell.
Bourne. Born. Get it?
Bash is what macOS used up until macOS Catalina.
In recent years, an alternative shell has become very popular because it is much better than Bash.
It is simply called the Z shell or
With macOS Catalina, Apple made this Z shell the default shell and thus replaced Bash for fresh installations of macOS.
For existing user accounts, you have to change the shell yourself.
Imagine the outcry if Apple would force such a change upon its users.
I wrote a blog post of its own about how to switch from
zsh if you want to try this cool new shell with cool features like auto-complete for yourself.
Now, depending on which shell you use, you need to use different corresponding dotfiles.
If you were using the shell
sh, then you’d need to write your configurations into the file
But as I said, besides systems that haven’t been updated for a really long time, nobody really uses
If you use
bash, which you probably do because
bash has been the default shell on macOS for the longest time, then you need two different files:
~/.bashrc (more on when to use which of those file in a minute).
zsh of course has its own files too.
Accordingly, they are called
~/.zshrc. It even has a third file called
~/.zshenv (short for zsh environment).
The part “rc” derives from “run commands”. In the very early days (in the 1960s), developers stored startup information for a command in a file they named
runcom (short for run commands).
Basically, it contained all the commands that are to be executed when a program is opened.
Every single time you open a new instance of
bash, the system first executes the commands in
Every single time you open a new instance of
zsh, the system first executes the commands in
The run commands in such files could be alias definitions and function definitions but also shell options and prompt settings.
Similarly, there is a
.condarc file for the Python package manager
conda and a
.vimrc file for the text editor Vim.
If you want a deeper dive into the details of
~/.bashrc, this answer on StackOverflow was really helpful to me.
To understand the differentiation between these dotfiles better, let’s first look at what types of shells there are.
The Four Types of Shells
A shell can be either login or non-login. It can be further subdivided into interactive or non-interactive. Thus there are four possible combinations.
Since you most likely use Bash, I’m going to explain
With regard to the
bash shell, macOS’s behavior is a little different from Linux.
While your operating system behaves differently regarding which of these two files it uses in which case, depending on whether you use macOS or Linux, this shouldn’t influence your behavior.
The decision which file you should use when for storing your configurations shouldn’t be influenced by which operating system you use.
Since macOS is the exception and deviates from the standard, I personally would do it the Linux way, even if you are using macOS.
Stick to the standard.
I myself am using macOS if you couldn’t tell by now, but I use my dotfiles as if I were using Linux.
Not only because it is “the right way”, but because this way I can share my files among different machines, including Linux machines, and don’t have to do the configuration work again.
The Two Interactive Shells: How Linux Does It
As I said earlier, macOS operates differently from Linux. Since macOS is the exception and not the standard, I think it’s best to first understand how Linux does it and then look at what macOS does differently. Linux and macOS really only differ in two of the four cases … the two interactive cases:
- interactive login shell
- interactive non-login shell
So let’s examine these two cases.
On Linux, an interactive login shell uses
An interactive non-login shell uses
On Linux, the interactive login shell is loaded only once (when you log in somewhere, e.g., in to your user account
davidculley—hence the name).
The interactive non-login shell by contrast is loaded each and every time you open a new terminal window.
I explicitly start my sentences with “on Linux” because—again—macOS behaves differently.
Just because Linux does it this way doesn’t mean the same is true for macOS.
Don’t let this confuse you when you read some answer on StackOverflow and they don’t mention which operating system they’re talking about.
On Linux, it would be like so:
you boot your computer and log in to your user account.
Since you just typed in a username and password combination, you logged in somewhere.
At this point, everything in
~/.bash_profile is run.
Right now, your computer has only loaded
~/.bash_profile but not
This is because you didn’t open the app
Terminal.app (or rather, the
bash shell), i.e., an interactive non-login shell.
You only logged in to your user account. (This started some sort of graphical shell, in case you’re wondering.)
Now, at this point it may be so that you want to open an application that needs to read one of those mentioned variables.
For example, Matlab needs to know where Java lives on your system, i.e., it needs to read the
Remember, at this point you’ve only loaded
~/.bash_profile but not
This is the reason why we must set the
PATH variable, the
JAVA_HOME variable, etc. in
~/.bash_profile and not someplace else.
Also, loading such variables once (when you log in) usually is enough.
After all, after a program was installed, it stays there.
You typically don’t change its location anymore, at least not very often.
And in the rare event that it actually does change it’s location or that you’ve needed to add a new variable after installing new software (after
~/.bash_profile has been loaded), e.g., the
GOPATH variable for the language Go, you can simply re-load
~/.bash_profile by sourcing it, i.e., by executing the command
Therefore, things that don’t change very often, like
GOPATH, belong into
You don’t need to tell Linux where you’ve installed Java every single time you open a new terminal window when it’s so easy to re-load it only once it became necessary.
PATH variable into
~/.bashrc would do exactly that.
On Linux, each and every time you open a new terminal window, Linux reads the file
This is why you put your aliases, your function definitions, and everything else that concerns
bash itself in
Whenever a new instance of
bash starts, it should have available the latest version of its settings (remember what we said earlier about run commands).
~/.bashrc contains everything that Linux should refresh every single time you open a new terminal window.
Think of it this way: after you’ve installed all your most used software, it is a lot more common that you add or change an alias than that you install an additional program.
When you open a new terminal window, you always want to have your latest alias and function definitions, not just those you defined prior to your last login.
Also, you don’t want to have to remember to always source your dotfiles every single time you change your aliases.
Another point: a “rc” file is meant to contain the run commands for a given program, and only those commands.
.bashrc is meant to contain (only) the run commands for
In other words, everything that has to do with
bash itself, and only those things, should go into
Java is not Bash. Therefore, telling the system where Java lives has nothing to do with
bash itself and therefore
JAVA_HOME not belong into
On the risk of repeating myself: if you didn’t enter a username and passwort combination somewhere, you did not log in anywhere. When you open a new terminal window, all you did was open a window. No login, however. Therefore, on Linux, a new terminal window will be an interactive non-login shell.
To summarize what we’ve looked at so far:
we’ve examined two of the four cases, the two interactive ones, specifically for the shell
It’s different for other shells, e.g.,
We’ve specifically examined how Linux does it.
Apple’s macOS does it differently.
We will examine how macOS does it in a moment.
From the Bash Reference Manual:
When bash is invoked as an interactive login shell, […] it looks for
~/.profile, in that order, and reads and executes commands from the first one that exists and is readable.
On Linux, an interactive login shell is what you get when you log in to your Linux user account, e.g.,
bash will read
~/.bash_profile if it exists.
In case there is no
~/.bash_profile, it will look for the file
~/.profile as a fallback.
~/.bash_profile, it will stop looking further and will not look into
In former days,
~/.bash_profile was meant to differentiate between the
bash shell and its predecessor
sh isn’t used very much anymore and
bash, as a successor of
sh, is 100% backwards-compatible with
sh, it doesn’t really matter whether you use
~/.profile for the
Just know that
~/.profile will not be read if there is a file called
This differentiation is mostly a concern if you’re working on multiple machines, e.g., a Mac at home and Linux at work, and one of these Linux machines is still using the older
sh instead of
bash, but you nevertheless want to share your dotfiles with your work machine.
If you have that kind of problem, you can put your environment variable definitions, e.g.,
~/.profile and source this
~/.profile file from within the
Quoting the Bash Reference Manual again:
When an interactive shell that is not a login shell is started, bash reads and executes commands from
~/.bashrc, if that file exists.
Only interactive non-login shells read the file
~/.bashrc, at that.1
On Linux, every regular new terminal window is an interactive non-login shell.
Maybe you’re wondering, “If interactive login shells load only
~/.bash_profile and interactive non-login shells load only
~/.bashrc, then how can it be that I can nevertheless open a terminal window and compile some Java files using the Java compiler
javac in a interactive non-login shell, even though I specified
~/.bash_profile? I thought interactive non-login shells do not load
You’re right. You opened an interactive non-login shell and it did not read
However, interactive non-login shells inherit everything the interactive login shell knows.
On Linux, this means that every terminal window started during a login session automatically inherits what was loaded from
~/.bash_profile when you logged into your user account, e.g.,
That’s why you don’t have to set environment variables such as the
JAVA_HOME variable in
~/.bashrc as well.
Consider the following example:
you boot your computer and log in to your user account
This starts an interactive login shell.
This shell loads the file
~/.bash_profile and everything it contains at this exact moment.
Five hours later, you open a new terminal window, i.e., an interactive non-login shell.
This new terminal window inherits all the information from the interactive login shell, e.g., the content of the
PATH variable, although it’s five hours old already.
The interactive non-login shell (i.e., the terminal window) won’t know the latest information from
After all, you could have changed
~/.bash_profile in the last five hours.
It will, however, have the latest information from
~/.bashrc, because the terminal window just re-loaded
~/.bashrc when it was started.
The information from
~/.bash_profile is only inherited but not re-loaded.
The Remaining Two Types
Before we look at how macOS does it and how it differs from Linux, let’s first get the remaining two types of shells out of the way.
A non-interactive non-login shell is what’s started when you execute a regular old script.
For example, if you have a script named
my-script.sh sitting on your desktop and you enter the following commands into your terminal, then you start a non-interactive non-login shell:
cd ~/Desktop ./my-script.sh
Non-interactive shells that are also login shells are rare, very specific and very advanced, so don’t worry about them. If you don’t know what a X display manager is and if you also don’t pass local scripts over an SSH connection to remote hosts, then just forget that non-interactive login shells exist. This eliminates one of the four possible cases entirely. So let’s get back to the two interactive types and see what macOS does differently from Linux.
The Two Interactive Shells: How macOS is different from Linux
What Linux does, makes sense.
When you log in to your user account, an interactive login shell is started, i.e.,
~/.bash_profile is read. Once.
After you’ve logged in, there’s no need to log in yet another time. You’re already logged in. Therefore, after your initial login, opening a new terminal window will start an interactive non-login shell, i.e., every time you open a new terminal window,
~/.bashrc is read.
In contrast, whenever you open a new terminal window on macOS, macOS opens an interactive login shell.
That is, whenever you open a new terminal window on macOS, macOS looks into
~/.bash_profile, not into
~/.bashrc as Linux does.
Even though you didn’t log in anywhere.
All you did was open a window.
You didn’t enter your username and password anywhere.
That macOS starts new terminal windows as login shell rather than as non-login shell means two things for macOS users:
- Per convention, things that don’t change very often, e.g., the
JAVA_HOMEvariable should go into
~/.bash_profiledoes not need to be reloaded very often, because its content rarely changes. It’s sufficient to reload it only when you log in to your user account and, if need be, manually via
source. Yet macOS reloads it every single time you open a new terminal window.
- Per convention, you would place your aliases and everything else you want to be reloaded with every new terminal window into
~/.bashrc. But when you open a new terminal, macOS does not look into
~/.bashrc. It instead looks into
The first point is an annoyance, but the second point is a problem.
~/.bashrc is not read when you open a new terminal window, your aliases and function definitions won’t work if you stick to the standard and put them where they belong, in
On macOS, you therefore need to source
~/.bashrc from within
~/.bash_profile as described in a following section.
To recapitulate, whenever we open a new terminal window
- Linux starts an interactive non-login shell
- macOS starts an interactive login shell
What does this mean with regard to our dotfiles setup?
The consequences are the following: if you use
bash and one of these cases applies:
- you want to stay portable, e.g.
- you own both a Mac and a Linux computer and you want them to share the same dotfiles
- you want to make your dotfiles publicly available on GitHub
- you simply want to stick to the convention and put your aliases where they belong (in
then I recommend you do it as Linux would expect it, even if you use macOS.
Maybe you’re wondering which way is better—the way Linux does it or the way macOS does it?
I recommend you read through the comments of this StackOverflow answer.
To paraphrase: in most cases, there is no harm in the way macOS does it.
But if you want to set up different variables in a terminal, the macOS way would override environment variables provided by the environment, i.e., it would override your local settings.
This can be frustrating.
The advantage of the macOS way is that after you’ve, e.g., updated your
PATH variable in
~/.bash_profile, you can simply close and re-open your terminal window. On macOS, there’s no need to explicitly source
~/.bash_profile for the changes to take effect.
So if macOS starts interactive login shells for new terminal windows, i.e., almost exclusively uses
~/.bash_profile, then what is the job of
~/.bashrc on macOS?
Sometimes you will also use
bash to start a new instance of
bash, i.e., you will start a new shell session within your existing shell session.
If you’re familiar with Homebrew and/or you’ve read my blog post on installing software on macOS via Homebrew, then you will recognize the following command:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
bash to start another instance of
Since you never entered any user credentials, this shell within a shell will be an interactive non-login shell, i.e., it will load the file
~/.bashrc. It will not re-load
~/.bash_profile, since only interactive login shells load that file.
Remember the above-mentioned quote:
When an interactive shell that is not a login shell is started, bash reads and executes commands from
~/.bashrc, if that file exists.
On macOS, only such shells within another shell (“second-instance shells”) are interactive non-login shells.
Normally, regular “first-instance shells” would be interactive non-login shells as well, just like on Linux.
But macOS treats “first-instance shells” differently than Linux, as we already know.
“Second-instance shells”, however, work the same on macOS and Linux.
They are interactive non-login on both operating systems and thus read (only)
~/.bashrc … on both operating systems.
Such shells within a shell load neither
However, they do inherit what is defined in
They inherit from the interactive login shell that started them.
Therefore, what’s defined in
~/.bash_profile is used by the first-instance
bash shell and the second-instance
On macOS, unless you source
~/.bashrc from within
~/.bash_profile as described later, the content in
~/.bashrc will be applied only to second-instance shells.
On macOS, you therefore have two options:
- deviate from the standard (not recommended) and put your aliases and environment variable definitions into
If you follow neither of these options, i.e., put your aliases into
~/.bashrc but don’t source this file, your aliases will not work because then only second-instance shells would know of these definitions.
Variables such as
JAVA_HOME would then be set only in programs that were launched via the terminal.
In case you open them via the Finder or Spotlight, these variables wouldn’t be set.
As a consequence, if you use Matlab or other programs that need to know where they can find Java, e.g., build tools like Maven or Gradle, they wouldn’t find Java and would tell you Java wasn’t installed, even though it is, simply because they don’t know of the
Does the first option mean that you can simply put everything into
You could, yes, but then you would go against the convention.
The convention says to define aliases in
~/.bashrc because on Linux that file is reloaded very frequently, much more frequently than
And you would put stuff into
~/.bashrc that is specific to second-instance shells, i.e., interactive non-login shells.
Consider Linux again.
~/.bashrc is read
- whenever you open a new terminal window or
- whenever you start a new shell session from within an existing shell session, e.g., when you use
bashto call another instance of
The first case works differently on macOS, as we’ve already seen.
And because of that different behavior of macOS, the only circumstance that
~/.bashrc will be read on macOS is this second case.
For example, when we type
/bin/bash into the terminal to install Homebrew.
To make it even more complicated,
zsh behaves a bit different than
zsh, it is not either
~/.bashrc like with Bash but first
~/.zshrc, i.e., both files are read.
With all we’ve learned so far, this seems to be an extra thing we have to pay attention to, but actually it makes things a lot easier.
Remember all the differences between Linux and macOS?
They exist because
bash only reads either the one file or the other, depending on whether it’s a login or non-login shell.
Bash does not read both files and Linux and macOS are in disagreement with one another whether a new terminal window should be a non-login or a login shell.
If you use
zsh, then all of this is irrelevant because
zsh does read both files.
zsh, it doesn’t matter whether a new terminal is an interactive login shell or an interactive non-login shell. The Z shell will read both
~/.zprofile as well as
~/.zshrc, in either case.
~/.zshrc is read in two of four cases (the exception being the rare case and the very common case of running local scripts).
If you want to cover all four cases, then you can put everything in
But sometimes you don’t want your custom settings to interfere with your scripts.
Such highly custom settings would go into
~/.zshrc, depending on whether you want to cover the rare case or not.
If you don’t know what a X display manager is and also don’t pass local scripts over an SSH connection to remote hosts (the rare case), then you don’t need
Comparison of All Four Types
What really clarified my understanding was the following image I found here. The color coding is:
- interactive login → red
- Linux: read once, when you log in to your user account
- macOS: read when you log in to your user account and every time you open a regular terminal window
- interactive non-login → green
- Linux: read every time you open a regular terminal window and the shell within a shell
- macOS: the shell within a shell
- non-interactive login → yellow
- the rare one
- non-interactive non-login → blue
- whenever you execute a local script
As you can see,
bash (on the left) is a mess and things got a lot simpler with
zsh (on the right).
You can find a really good explanation of the four different kinds of shells here.
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.
Since Bash is the default shell, I will describe the process for Bash.
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 ~/.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 for a simpler migration to a different shell, should I decide to switch to a different shell, say
fish, or back to
bash. 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:
To exit first press
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
To open the file in VS Code, you’d first need to add Visual Studio Code to your
PATH so that macOS knows what you mean by the word
echo 'export PATH="$PATH:/Applications/Visual Studio Code.app/Contents/Resources/app/bin"' >> ~/.bash_profile source ~/.bash_profile
Only now can you use VS Code to open
After you’ve opened
~/.bash_profile, add the following lines to the existing content:
if [ -r ~/.profile ]; then . ~/.profile; fi # If the shell is interactive, source ~/.bashrc case "$-" in *i*) if [ -r ~/.bashrc ]; then . ~/.bashrc; fi;; esac
This loads the content of
~/.bash_profile so that you don’t have to write things twice. This is what’s commonly referred to as “sourcing”.
~/.bashrc in the same way and replace its content with:
Since you sourced your aliases into
~/.bashrc where they belong according to convention, and your
~/.bash_profile, you don’t need to load your aliases into
~/.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"
I’m sure you’ll find a lot more useful aliases if you do a quick Google search.
Now back to
~/.bash_profile. Open it and add your environment variables, i.e., the lines of code that look like this:
# Homebrew export PATH="/usr/local/bin:/usr/local/sbin:$PATH" # Java export JAVA_HOME=$(/usr/libexec/java_home)
Of course these are not my complete dotfiles. Everybody has different preferences and uses different aliases, thus a Google search of 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, if you switch your shell from
zsh you also have to change your current configuration.
Otherwise things will break and applications will tell you that Java isn’t installed even though it is.
The Z shell reads neither
~/.profile and will not know of any definitions stored in these files.
bash is designed to read the same syntax as
bash is backwards-compatible with
sh and can read both
zsh was designed with a different syntax in mind.
zsh is not 100% compatible with the syntax of Bourne-like shells, i.e.,
Therefore, the Z shell uses its own files and I don’t recommend you simply source
~/.bash_profile into these
zsh files as we did earlier.
In case you’d nevertheless like to source
~/.zshrc, you should at least tell
zsh to emulate
sh to read
It’s no guarantee that everything will work, but it helps.
To do so, open
~/.zshrc like we did before:
open -e ~/.zshrc
Then, replace its content with:
[[ -e ~/.bash_profile ]] && emulate sh -c 'source ~/.bash_profile'
All other settings regarding
zsh, such as aliases, go into
~/.zshrc as well. That’s why we created a separate file for our aliases and didn’t put them directly into
~/.bashrc. This way we’re now able to simply source them from
~/.zshrc as well:
That’s it! I hope everything is working on your machine.
If you have any questions, please don’t hesitate to ask in the comment section down below. If this article was helpful, why don’t you let me know? 😊 Nice comments from readers like you are what keeps me motivated to write guides like this for you. Also, if you spot any errors, I’d appreciate your comment. If you want to get notified when I publish a new article like this, consider following me on Twitter. Thanks, and be well.
That’s an oversimplification. In order to not confuse you, I purposefully omit the files higher up the hierarchy located at
/etc/. Right now, you don’t need to know about them. ↩︎