Site logo
Published on

Shell startup scripts

Authors

originally published 2019

2022 update: When I set up a new M1 Mac work machine homebrew recommended I load it in my ~/.zprofile. This complicates the takeaways I give below, but I guess the main update would be that tools you need only for interactive sheels can go in there. My dotfiles in whatever their current state may be are available at erikdstock/dotfiles.

MacOS 10.15 (Catalina) changed the default shell from bash to zsh, which encouraged me to finally figure out what the heck is going on when I log in and why my dotfiles always feel like such a mess. This includes both the strange loading logic (of bash in particular) as well as the quirks that Apple's macOS adds to the mix. It turns out that it's not that bad. (Note: for all purposes here I am only talking about shells loaded via a [GUI] Terminal app)

Default shell startup script loading order

The charts below are copied from a 2008 blog post that stands the test of time.

They illustrate how a given shell environment selects which scripts to execute on startup, beginning from the top of a given column and continuing through each script marked with a letter in alphabetical order- but only the first file found for each letter. The columns account for varying strategies depending on whether the shell is being created for a script or for an interactive shell and, in the later case, whether the interactive shell is a login shell.

In Unix terms, a login shell is the shell used at startup - for example, the shell which starts the X Window server or otherwise starts the system such as via ssh or when logging in through a tty terminal (not sure why we still pretend these things exist).

The key caveat on these charts for Mac users is explained here: On macOS, launching a new terminal or terminal tab is treated as a login shell. Regardless of your OS, once you are in the terminal subshells (like running bash) are not login shells.

As a result, most Linux users should read down the second column for their shell, while most Mac users would read down the first.

bash startup scripts

Bash's startup script strategy is especially complicated for Mac users:

  • For an interactive non-login shell (that is, linux), ~/.bashrc is executed and the main point for user customization
  • For macOS's interactive non-login shell (even in terminal.app), ~/.bashrc is ignored in favor of the first file found of ~/.bash_profile, ~/.bash_login and ~/.profile (below, only the first of B1, B2, or B3).

To simplify this, the common advice has been to add 'source ~/.bashrc' to your ~/.bash_profile (the first of the optional login scripts) and keep customizations in ~/.bashrc.

Interactive loginInteractive non-loginScript
/etc/profileA
/etc/bash.bashrcA
~/.bashrcB
~/.bash_profileB1
~/.bash_loginB2
~/.profileB3
source $BASH_ENVA
logout only:
~/.bash_logoutC

zsh startup scripts

zsh is straightforward by comparison. It is still a login shell on mac, though most folks only bother with .zshrc and there are no strange loading quirks.

Most folks I know use only .zshrc and/or Oh my zsh to manage their zsh.

Interactive loginInteractive non-loginScript
/etc/zshenvAAA
~/.zshenvBBB
/etc/zprofileC
~/.zprofileD
/etc/zshrcEC
~/.zshrcFD
/etc/zloginG
~/.zloginH
logout only:
~/.zlogoutI
/etc/zlogoutJ

Key Takeaways

  • Mac shells start out as login shells. This makes bash's startup script loading order more complicated.
  • Put your bash customizations in ~/.bashrc and source it from ~/.bash_profile to avoid this issue.
  • Put your zsh customizations in ~/.zshrc.
  • On a Mac, you can print any welcome or diagnostic messages from ~/.bash_profile or ~/.zlogin.

Other References