Right, let’s demystify the single most common source of shell-related head-scratching: why the heck your aliases and environment variables sometimes vanish into thin air. It all boils down to understanding the difference between a login shell and an interactive non-login shell, and which file gets read when. It’s a historical artifact, and like most things in computing, it’s a bit of a kludge that we’re all stuck with.

Think of your shell’s startup sequence as a series of doors it walks through when it starts. Which doors it opens depends on how it was invited to the party.

Login vs. Interactive Shells: The Core Confusion

First, we need to get our terms straight, because this is where everyone gets lost.

A login shell is, unsurprisingly, a shell you log into. This happens in a few key ways:

  1. You log into a text console (tty1, etc.).
  2. You ssh into a remote machine (ssh user@host).
  3. You use su - or sudo -i to become another user (note the all-important hyphen).

An interactive non-login shell is what you’re probably staring at right now. It’s a shell that is already running and you start a new one from within it. This happens when:

  1. You open a new terminal tab in your graphical terminal emulator (like iTerm2 or GNOME Terminal).
  2. You just type bash or zsh at your existing prompt.

The key difference? The login shell is the beginning of your session. It’s responsible for setting up the core environment for your entire session. The interactive shell is a child of that session; it inherits the environment from its parent and just needs to add its own interactive bling.

The bash Startup Sequence: A Flowchart in Text

Here’s the decision tree for bash. Follow along; it’s less scary than it looks.

  1. Is this a login shell? (e.g., you ssh in)

    • Yes: It reads /etc/profile first (for system-wide settings). Then it goes looking for the first file that exists in this order: ~/.bash_profile, ~/.bash_login, or ~/.profile. It stops at the first one it finds. This is crucial! If you have a ~/.bash_profile, it will ignore your ~/.profile. This is a classic “oops, my settings aren’t loading” moment.
    • No: It’s an interactive non-login shell (e.g., a new terminal tab). It reads ~/.bashrc. That’s it.
  2. Is this an interactive shell? (Both login and non-login interactive shells do this)

    • Yes: After reading the files above, it also reads ~/.bashrc. Wait, what? So a login shell reads both ~/.bash_profile and ~/.bashrc? Not by default! This is the million-dollar trick.

The designers of bash, in their infinite wisdom, decided that ~/.bashrc was only for interactive non-login shells. But practically everyone wants their aliases and functions in every interactive shell. The solution? We hack the design.

The standard, time-honored practice is to source ~/.bashrc from within your ~/.bash_profile. This way, a login shell gets everything. Check if your ~/.bash_profile does this. If it doesn’t exist or doesn’t do this, you can make it happen:

# ~/.bash_profile
if [ -f ~/.bashrc ]; then
    source ~/.bashrc
fi

Now, your login shell runs the ~/.bash_profile, which then pulls in all your goodies from ~/.bashrc. Problem solved.

The zsh Simplification: One File to Rule Them All

zsh looked at this bash nonsense, said “are you kidding me?”, and simplified it. By default, zsh doesn’t even distinguish between login and interactive shells in the same way. It’s far more sane.

When zsh starts as an interactive shell (login or not), it just reads ~/.zshrc. That’s it. No fuss, no mess. You can put your entire setup in there. It’s glorious.

However, zsh can be persuaded to behave like bash if you really want it to. It will read ~/.zprofile if it’s a login shell, but honestly, you probably don’t need to bother. Just put everything in ~/.zshrc and live a happier, simpler life.

Best Practices and Pitfalls

  • The Golden Rule: Put all your interactive stuff—aliases, functions, shell options, prompt customization—in ~/.bashrc (for bash) or ~/.zshrc (for zsh). Then, make damn sure your ~/.bash_profile sources your ~/.bashrc.
  • Environment Variables (PATH, EDITOR, etc.): The “correct” place for these is in ~/.profile or ~/.bash_profile/~/.zprofile, as they should be set once per session. But in practice, tossing them in your rc file often works fine because it’s sourced by every shell. Just be aware that if you ever run a non-interactive shell, they won’t be set.
  • The --rcfile Option: You can force bash to use a specific file with bash --rcfile ~/.my_custom_file. Useful for testing a new config without nuking your current session.
  • The -l (login) Option: You can force any bash/zsh instance to act as a login shell by starting it with bash -l or zsh -l. This is useful for reloading your entire environment in a current terminal without logging out.

So, to summarize: bash’s rules are weird, so we fake a sensible setup. zsh’s rules are sensible, so we just use them. Now you know why your shell sometimes seems to have amnesia, and more importantly, you know how to fix it for good.