In Linux and, to my knowledge, all Unix systems, terminal emulators run interactive, non-login shells by default. This means that, for bash, the started shell will:

When an interactive shell that is not a login shell is started, bash reads and executes commands from /etc/bash.bashrc and ~/.bashrc, if these files exist. This may be inhibited by using the --norc option.

The --rcfile file option will force bash to read and execute commands from file instead of /etc/bash.bashrc and ~/.bashrc.

And for login shells:

When bash is invoked as an interactive login shell, or as a non-interactive shell with the --login option, it first reads and executes commands from the file /etc/profile, if that file exists. After reading that file, 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.

The --noprofile option may be used when the shell is started to inhibit this behavior.

On OSX, however, the default shell (which is bash) started in the default terminal (Terminal.app) actually sources ~/.bash_profile or ~.profile etc. In other words, it acts like a login shell.

Main question: Why is the default interactive shell a login shell on OSX? Why did OSX choose to do this? This means that all instructions/tutorials for shell based things that mention changing things in ~/.bashrc will fail on OSX or vice versa for ~/.profile. Still, while many accusations can be leveled at Apple, hiring incompetent or idiotic devs is not one of them. Presumably, they had a good reason for this, so why?

Subquestions: Does Terminal.app actually run an interactive login shell or have they changed bash's behavior? Is this specific to Terminal.app or is it independent of the terminal emulator?

  • 1
    Terminal.app runs a login shell. I don't know why Apple chose to do that. – Gilles Mar 14 '14 at 1:29
up vote 29 down vote accepted

The way it's supposed work is that, at the point when you get a shell prompt, both .profile and .bashrc have been run. The specific details of how you get to that point are of secondary relevance, but if either of the files didn't get run at all, you'd have a shell with incomplete settings.

The reason terminal emulators on Linux (and other X-based systems) don't need to run .profile themselves is that it will normally have been run already when you logged in to X. The settings in .profile are supposed to be of the kind that can be inherited by subprocesses, so as long as it's executed once when you log in (e.g. via .Xsession), any further subshells don't need to re-run it.

As the Debian wiki page linked by Alan Shutko explains:

"Why is .bashrc a separate file from .bash_profile, then? This is done for mostly historical reasons, when machines were extremely slow compared to today's workstations. Processing the commands in .profile or .bash_profile could take quite a long time, especially on a machine where a lot of the work had to be done by external commands (pre-bash). So the difficult initial set-up commands, which create environment variables that can be passed down to child processes, are put in .bash_profile. The transient settings and aliases which are not inherited are put in .bashrc so that they can be re-read by every subshell."

All the same rules hold on OSX, too, except for one thing — the OSX GUI doesn't run .profile when you log in, apparently because it has its own method of loading global settings. But that means that a terminal emulator on OSX does need to run .profile (by telling the shell it launches that it's a login shell), otherwise you'd end up with a potentially crippled shell.


Now, a kind of a silly peculiarity of bash, not shared by most other shells, is that it will not automatically run .bashrc if it's started as a login shell. The standard work-around for that is to include something like the following commands in .bash_profile:

[[ -e ~/.profile ]] && source ~/.profile    # load generic profile settings
[[ -e ~/.bashrc  ]] && source ~/.bashrc     # load aliases etc.

Alternatively, it's possible to have no .bash_profile at all, and just include some bash-specific code in the generic .profile file to run .bashrc if needed.

If the OSX default .bash_profile or .profile doesn't do this, then that's arguably a bug. In any case, the proper work-around is to simply add those lines to .bash_profile.


Edit: As strugee notes, the default shell on OSX used to be tcsh, whose behavior is much saner in this respect: when run as an interactive login shell, tcsh automatically reads both .profile and .tcshrc / .cshrc, and thus does not need any workarounds like the .bash_profile trick shown above.

Based on this, I'm 99% sure that the failure of OSX to supply an appropriate default .bash_profile is because, when they switched from tcsh to bash, the folks at Apple simply didn't notice this little wart in bash's startup behavior. With tcsh, no such tricks were needed — starting tcsh as a login shell from an OSX terminal emulator Just Plain Works and does the right thing without such kluges.

  • 1
    Thanks, I sort of knew all that. My question is why did OSX choose to set up in such a way as to render .bashrc irrelevant? Why did they choose to make all shells login shells? As far as I can tell, only your last sentence addresses that and only to say that it's a bug. – terdon Mar 14 '14 at 15:31
  • 1
    No, the issue is that they start login shells in their terminal emulators. The dotfiles are default bash behavior, starting login shells will read profile etc, not bashrc etc. The question is why login shells are run instead of non-login ones. – terdon Mar 14 '14 at 16:53
  • 2
    Yes, and both I and Alan have explained that it's because, unlike Linux, OSX doesn't execute .profile when the user logs into the GUI, so they have to execute it later to get environment variables like $PATH, which are normally set in .profile, configured correctly. The fact that, as a side effect, this causes .bashrc not to be sourced is a bug; you can argue about whether it's a bug in bash or in OSX, but that doesn't change the fact that the correct behavior would be to ensure that both the environment variables from .profile and the bash config setting from .bashrc are loaded. – Ilmari Karonen Mar 14 '14 at 17:01
  • 1
    And I'm saying the answer to "why do they use a login shell?" is "because they need to load .profile", while the answer to "why don't they source .bashrc from .profile, then?" is "because it's a bug!" Seriously, that cannot possibly be an intentional decision; it's just something they overlooked, presumably because, in the Mac ecosystem, shells are second-class citizens that most users aren't supposed to deal with. (Ps. See also strugee's answer for a historical explanation; it's almost certainly a regression from the switch from tcsh to bash.) – Ilmari Karonen Mar 14 '14 at 17:29
  • 1
    @huangzonghao: I'm assuming you're on some type of Linux system? It's likely that your GUI login manager is set up to source /etc/profile and ~/.profile when you log in; but since it's not bash, it won't source ~/.bash_profile, since that's purely a bash-only thing. When you log in via Ctrl+Alt+1, you're logging directly into your shell, which will take care of setting up the profile; if your login shell is bash, that means it will source ~/.bash_profile if it exists (and will try to source ~/.profile otherwise). – Ilmari Karonen Jun 12 at 20:58

The main reason that X terminal applications run non-login shells by default is that in the beginning of time, your .Xsession would have run the .profile to set up your initial login items. Then, since that was all set up already, terminal apps didn't need to run it, they could run the .bashrc. Discussion of why this would matter is at https://wiki.debian.org/DotFiles:

Let's take xdm as an example. pierre comes back from vacation one day and discovers that his system administrator has installed xdm on the Debian system. He logs in just fine, and xdm reads his .xsession file and runs fluxbox. Everything seems to be OK until he gets an error message in the wrong locale! Since he overrides the LANG variable in his .bash_profile, and since xdm never reads .bash_profile, his LANG variable is now set to en_US instead of fr_CA.

Now, the naive solution to this problem is that instead of launching "xterm", he could configure his window manager to launch "xterm -ls". This flag tells xterm that instead of launching a normal shell, it should launch a login shell. Under this setup, xterm spawns /bin/bash but it puts "-/bin/bash" (or maybe "-bash") in the argument vector, so bash acts like a login shell. This means that every time he opens up a new xterm, it will read /etc/profile and .bash_profile (built-in bash behavior), and then .bashrc (because .bash_profile says to do that). This may seem to work fine at first -- his dot files aren't heavy, so he doesn't even notice the delay -- but there's a more subtle problem. He also launches a web browser directly from his fluxbox menu, and the web browser inherits the LANG variable from fluxbox, which is now set to the wrong locale. So while his xterms may be fine, and anything launched from his xterms may be fine, his web browser is still giving him pages in the wrong locale.

On OS X, the user environments aren't started by a pile of shell scripts, and launchd doesn't source .profile at any time. (That's sort of a shame, since it means it's a lot more annoying to set environment variables, but such is life.) Since it doesn't, when would it run .profile? Only if you ssh'd in? That seems kind of pointless, since many boxes will never be targets of ssh. Might as well make the terminals run login shells by default, so that .profile will be run sometime.

What about .bashrc, then? Is it useless? No. It still has the purpose that it had in the days of the VT100. It is used for any time you open a shell other than opening a Terminal window. So if you shell out of Emacs or vi, or if you do a su user.

  • The Debian page you're quoting explains why Debian's .profile sources .bashrc, not why OSX decided to make all shells login shells by default. Actually, you are answering another question of mine, and thanks! However, your answer does not explain the OSX choice and the Debian quote is completely irrelevant here as far as I can tell (please let me know if I'm just missing the point). The OSX way make .bashrc etc useless and does not make .profile any more useful. – terdon Mar 14 '14 at 4:28

I don't know why they would have done that. However, here's my guess.

To start with, it's worth noting that on a GNU/Linux system, you can of course switch to vt1, vt2, etc. You get a login shell there. On an OS X system, there's no equivalent. The only way to access the UNIX underpinnings is through a terminal emulator or through single user mode (disclaimer: I've never actually used single user mode; it's commandline-driven IIRC but I may be wrong). Therefore, in OS X whatever the default is in the emulator is the default for the entire system.

Now, why would you make the default a login shell? There are a couple (read: not many) reasons I can think of to do this.

  • It provides a consistent user experience if you SSH into the box. (Especially important for the server edition of OS X - presumably if you're running an OS X server, you're a newbie.)
  • The default shell of OS X used to be tcsh. This is about as wild a guess as you can get, but it may be that tcsh normally did something when run as a login shell, and the historical pattern stuck. (I doubt it, though - maybe one of the older regulars could tell us.)
  • "We are Apple. We are the vendors of the largest UNIX distribution on the planet. It doesn't matter how trivial our reasons are; if we make a decision, your tool must deal with it."

Honestly, I've been using Darwin for ~6 years and I can't answer this question properly. It doesn't really make sense to me, either.

To answer your subquestion, bash is not patched or anything (at least for this). The default terminal emulator runs login shells by default, and presumably iTerm copies that.

  • 1
    +1 for mentioning tcsh, since its behavior is far saner in this respect than that of bash: when started as an interactive login shell, tcsh sources both .profile and .tcshrc / .cshrc without requiring any kluges like the one I gave in my answer. Given that, I suspect this behavior is an unfixed regression caused by the switch from tcsh to bash. – Ilmari Karonen Mar 14 '14 at 17:09
  • @IlmariKaronen Do you mean (here and there) that tcsh sources .login and .tcshrc/.cshrc? It would make no sense for tcsh to source .profile; it typically contains commands with sh syntax that tcsh won't accept. I don't have macOS but I made /bin/tcsh my login shell on Ubuntu 16.04. .profile isn't sourced. tcsh(1) doesn't mention that file, nor does this tcsh(1). – Eliah Kagan Oct 17 '17 at 19:23

This is an update to current status: the answers have become stale as new MacOSX versions have been released and login behavior has changed.

This question was asked and answered in 2014. I've been researching the subject in an attempt to build a common .bashrc & .bash_profile set to use across different Linux distros and BSD (whatever they call them if they're not distros).

I recently bought a used Mac Mini with Sierra installed, so I now have systems with 10.6, 10.10, 10.11, and 10.12. Although I have munged the older ones (leaving clues to their previous status), the 10.12 Sierra installation is untouched.

Results: In 10.12 Sierra, there is NO default .bashrc, .bash_profile, or .profile created. In /etc, there are bashrc, bashrc_Apple_Terminal, and profile. Contents of /etc/profile:

# System-wide .profile for sh(1)

if [ -x /usr/libexec/path_helper ]; then
    eval `/usr/libexec/path_helper -s`
fi

if [ "${BASH-no}" != "no" ]; then
    [ -r /etc/bashrc ] && . /etc/bashrc
fi

Contents of /etc/bashrc:

# System-wide .bashrc file for interactive bash(1) shells.
if [ -z "$PS1" ]; then
   return
fi

PS1='\h:\W \u\$ '
# Make bash check its window size after a process completes
shopt -s checkwinsize

[ -r "/etc/bashrc_$TERM_PROGRAM" ] && . "/etc/bashrc_$TERM_PROGRAM"

The /etc/bashrc_Apple_Terminal script sets the PROMPT_COMMAND variable and sets up a mechanism for preserving session state for each terminal; if the terminal app is quit, the status of previous session is restored when the app is started again. Otherwise, almost nothing (minimal $PS1) is set in /etc/bashrc, leaving any other customization (real $PS1, aliases, functions) to be set in ~/.bashrc and $PATH settings in .bash_profile (or in .profile, sourced in by .bash_profile).

I have confirmed that iTerm2 behaves the same way as the Terminal app, except that the default behavior of starting a login session is visible and can be edited.

If you run an X11 (now XQuartz) XTerm terminal session, you will see a non-login session, the same as on a Linux system; .bash_profile (or .profile) is skipped, and you just get .bashrc.

And, here's my answer:

Although the Terminal app claims to be an xterm (TERM=xterm-256color), it does not set the $DISPLAY variable unless X11 is installed. We are seeing a simulation of an X environment, but not a completely real one. If you SSH into another system with the -X switch (enable X11 forwarding), it will fail because there is no $DISPLAY variable. If you have installed X11, then SSH into another system, X11 forwarding will succeed.

Bottom line (and short answer): The Terminal app is not a true X11 terminal (does not set $DISPLAY); it behaves much more like an SSH login than an XTerm session, in that it has to be a login session in order to set values from /etc/profile and ~/.bash_profile or ~/.profile

The answers above explained the reason why interactive shells are login shells on macOS by default: settings in /etc/profile, ~/.profile are inherited by a non-login shell on X-based systems, but not on macOS. Here I wanna remind you that you should always use login shell on macOS because of the existence of path_helper:

❯ cat /etc/profile # or cat /etc/zprofile
# System-wide .profile for sh(1)

if [ -x /usr/libexec/path_helper ]; then
    eval `/usr/libexec/path_helper -s`
fi

if [ "${BASH-no}" != "no" ]; then
    [ -r /etc/bashrc ] && . /etc/bashrc
fi

The path_helper utility reads the contents of the files in the directories /etc/paths.d and /etc/manpaths.d and appends their contents to the PATH and MANPATH environment variables respectively. (The MANPATH envi- ronment variable will not be modified unless it is already set in the environment.)

If you're using a non-login shell, some paths will not be imported.

I think it's always a good idea for developer to put a file into /etc/paths.d to import their path values, but not to symlink the callable binaries into locations like /usr/bin or /bin. (The default PATH is /usr/bin:/bin:/usr/sbin:/sbin. Not everyone uses Homebrew or MacPorts adding custom values into PATH. So there's not always a safe place to put the symlinks of callable commands.)

Here is an example of files in /etc/paths.d. Basically you're putting one value each line.

❯ cat /etc/paths.d/Wireshark
/Applications/Wireshark.app/Contents/MacOS

Reference:

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.