Bash OSX – Why Are Interactive Shells on OSX Login Shells by Default

bashosx

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?

Best Answer

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.

Related Question