Ignoring .bash_profile vs .bash_login vs .profile, the bash man page essentially says:
login shell .profile
interactive non-login shell .bashrc
run by the remote shell daemon .bashrc
I wasn't sure exactly what counted as a "login" shell so I got each of my .profile and .bashrc printing into a logfile. These are my observations on OS X:
log into OS X user account (nothing)
new terminal .profile
run "bash" from that terminal .bashrc
run a shell script (nothing)
ssh hostname .profile
ssh hostname somecommand .bashrc
The behaviour of ssh seems (to me, at least) to contradict the bash man page. Anyway this was all so complicated that I'll never remember it, so what I ended up doing was:
* .profile does nothing other than source .bashrc
* In .bashrc anything that should be specific to interactive shells goes inside:
'login' is when called with --login flag (which login(1) does)
'interactive' is when called with -i flag (which sshd does when reached by ssh with no command passed, i.e if you do 'ssh ls' it should not call bashrc, but should call bash_profile)
The only thing needed is, in bash_profile:
[[ $- == *i* ]] && source ~/.bashrc
because bash does not load bashrc when it is both an interactive and a login shell.
There is no madness, everything is explained in TFMs (bash and zshall, which I coincidentally went through just yesterday)
FWIW here are my dotfiles, which are designed to relieve me of the true madness of the startup scripts: the thousand-line hairy mess (bonus included: mutualization between bash and zsh configs).
> 'interactive' is when called with -i flag (which sshd does when reached by
> ssh with no command passed, i.e if you do 'ssh ls' it should not call
> bashrc, but should call bash_profile)
Do you mean that an interactive shell should load .bash_profile? Because the
bash man page says: "When an interactive shell that is not a login shell is
started, bash reads and executes commands from ~/.bashrc".
Re. ssh, the bash man page says (at least on my system, with bash 4.2.37):
"Bash attempts to determine when it is being run with its standard input
connected to a network connection, as when executed by the remote shell daemon,
usually rshd, or the secure shell daemon sshd. If bash determines it is being
run in this fashion, it reads and executes commands from ~/.bashrc".
That last quote from the bash man page contradict my own experiments. Logging
into my OS X system with ssh loads .profile, not .bashrc. The same is true
logging into my Fedora 17 system with ssh. Perhaps sshd uses --login instead
of -i (or, on my systems, happens to be configured in that way).
Running a command via ssh ("ssh hostname somecommand") loads nothing when the
remote system is OS X, and loads .bashrc when the remote system is Fedora 17.
All in all, I will respectfully disagree that "there is no madness". Normally
I completely relate to the RTFM sentiment, and I have spent plenty of time in
the bash manual myself.
I remain unconvinced that there is a better solution than putting everything
into .bashrc (with the $- guard around stuff specific to interactive shells)
and doing nothing in .profile other than unconditionally sourcing .bashrc.
> [[ $- == *i* ]]
Thanks for the sane test syntax: It's much clearer than my ugly mess of almost
every symbol character I could find. :-)
> Do you mean that an interactive shell should load .bash_profile?
No, ssh logs you in, so it is a login shell (and thus calls {bash_,z}profile), but if you call 'ssh ls', it is not an interactive shell since you just ask ssh to launch the 'ls' command remotely.
> Logging into my OS X system with ssh loads .profile
sshd starts login(1) (which shows the 'Last login:' line), which calls your shell as login
- Run on a "new" shell when there's not any environment present. It should contain things that are "carried over" from one process to child process (ie, exported variables). May also contain things that are run "only once" from the user's point of view (check mail, yadda yadda)
- You don't want these commands run for every subprocess. Imagine that bash sources /etc/profile on each "new" shell to set PATH. You append to PATH in your user profile to add some additional directories. If the profile was sourced for each child process, your path would grow and grow, containing multiple copies of the same directories.
rc file(s)
- Run for each subshell (with caveats[1]) so that you can define things that aren't carried over to subprocesses (define aliases, functions, etc)
[1] caveats being things like remote shell command execution. I honestly don't know why it's not sourced then, but my guess would be that, if you're running a remote command, you're expected to not need things like aliases and functions defined.
Thinking about it all this way, the way the files works just seems... natural to me.
if [ -z "$PATH_DEFAULT" ]; then
export PATH_DEFAULT="$PATH"
fi
export PATH="<stuff>:$PATH_DEFAULT:<other stuff>"
works in most any Bourne-derived shell. Having the _DEFAULT variables around comes in handy for clean build environments, as well (e.g., I build Emacs.app once and distribute it to several Macs, so I don't want configure to pull in random dependencies that just happen to be installed in MacPorts on my build machine).
* .profile does nothing other than source .bashrc
* In .bashrc anything that should be specific to interactive shells goes inside: