My distributable, normalized and annotated *nix shell environment initialization config - i.e., my "dotfiles".
From the GNU bash manual:
When bash is invoked as an interactive login shell, or as a non-interactive shell with the
--login
option, [...] 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. [...] When an interactive shell that is not a login shell is started, bash reads and executes commands from~/.bashrc
, if that file exists.
Disregarding name variants and the exact conditions that make a shell a login- or interactive shell, the above descriptions boil down to this mapping:
interactive: | login: | initializations read from: |
---|---|---|
true |
true |
~/.bash_profile |
false |
true |
~/.bash_profile |
true |
false |
~/.bashrc |
Where will I put initializations that need to run either when the shell is only a login shell, only an interactive shell, or both? (#1)
If we take no other measures, we really have no other choice than to put them in both files; which isn't particularly DRY.
What if we'd source ~/.bashrc
from ~/.bash_profile
, similar to what the bash manual suggests we'd do?
[[ -f ~/.bashrc ]] && source ~/.bashrc
The mapping would then become:
interactive: | login: | initializations read from: |
---|---|---|
true |
true |
~/.bash_profile ~/.bashrc |
false |
true |
~/.bash_profile ~/.bashrc |
true |
false |
~/.bashrc |
and we can put these initializations in our ~/.bashrc
to have them run in either case.
But where will I put initializations that need to run when the shell is only an interactive shell? (#2)
Before we dealt with question #1 this one had an easy answer, but now we can't really put these anywhere, can we? Not unless we take additional measures.
What if we'd test whether the shell is a login shell and bisect ~/.bashrc
initializations based on the outcome?
if shopt -q login_shell; then
## is-login
...
else
## is-not-login
...
fi
Then the mapping would look like:
interactive: | login: | initializations read from: |
---|---|---|
true |
true |
~/.bash_profile ~/.bashrc #is-login |
false |
true |
~/.bash_profile ~/.bashrc #is-login |
true |
false |
~/.bashrc #is-not-login |
and it's easy to see where these initializations should now go.
Where will I now put initializations that need to run either when the shell is only a login shell, only an interactive shell, or both? (#3)
Well spotted! We just broke the solution for question #1.
What about we leave a common section in addition to bisecting ~/.bashrc
?
if shopt -q login_shell; then
## is-login
...
else
## is-not-login
...
fi
## is-either
...
Then the mapping would look like:
interactive: | login: | initializations read from: |
---|---|---|
true |
true |
~/.bash_profile ~/.bashrc #is-login ~/.bashrc #is-either |
false |
true |
~/.bash_profile ~/.bashrc #is-login ~/.bashrc #is-either |
true |
false |
~/.bashrc #is-not-login ~/.bashrc #is-either |
Let's see...
initialization kind | where it should go |
---|---|
login-shell only | ~/.bash_profile |
interactive-shell only | ~/.bashrc #is-not-login |
either (or both) | ~/.bashrc _#is-either |
both only | ~/.bashrc #is-logi ...wait, no! |
Initializations in ~/.bashrc
#is-login
will also run when the shell is only a login shell.
How about we test on the shell's interactive-ness too?
if shopt -q login_shell; then
if [[ $"{-}" =~ 'i' ]] ; then
## is-login && is-interactive
...
else
## is-login && is-not-interactive
...
fi
## is-login, regardless
...
else
## is-not-login
...
fi
## is-either
...
That would certainly "work", but...
interactive: | login: | initializations read from: |
---|---|---|
true |
true |
~/.bash_profile ~/.bashrc #is-login ~/.bashrc #is-interactive ~/.bashrc #is-either |
false |
true |
~/.bash_profile ~/.bashrc #is-login ~/.bashrc #is-not-interactive ~/.bashrc #is-either |
true |
false |
~/.bashrc #is-not-login ~/.bashrc #is-either |
Will you remember any of this when you have to modify these initializations, years, month or even weeks from now?
There was a reason for writing all of this down :).
Um, not really, no...
Wouldn't it be easier to have just a single initialization file to replace both ~/.bash_profile
and ~/.bashrc
and have initializations decide for themselves when they should be run?
You could even bundle in a few test functions to help with that.
Welcome to .ix!
Check out .ix into ~/.ix
; e.g.:
$ git clone https://github.com/cueedee/.ix.git ~/.ix
Manual invocation whenever deemed appropriate is always possible.
$ source ~/.ix/.bash_init.sh
All initializations are idempotent. It doesn't matter if they are invoked once or many times over.
Either inject this line wherever you see fit into your ~/.bash_profile
, your ~/.bashrc
, or both:
source ./.ix/.bash_init.sh
Or, if you have no additional local initializations to perform, simply use ~/.ix/.bash_init.sh
as a direct substitute for either your ~/.bash_profile
, your ~/.bashrc
, or both:
$ ln -s ./.ix/.bash_init.sh ~/.bash_profile
$ ln -s ./.ix/.bash_init.sh ~/.bashrc
As discussed above, it makes sense to have a single initialization file to function as both ~/.bash_profile
as well as ~/.bashrc
.
If somehow you'd like to stick with having two separate files, traditionally source
-ing your ~/.bashrc
from your ~/.bash_profile
and still like to benefit from the .ix
config, then that is certainly possible; the .ix
initializations are idempotent so it doesn't really matter if they are done twice.
If however you think that's wasteful, you could arrange to avoid that yourself; e.g.:
-
~/.bash_profile
:source ~/.ix/.bash_init.sh ... __IX_DID_RUN=1 source ~/.bashrc
-
~/.bashrc
:[[ -z "${__IX_DID_RUN:+1}" ]] && source ~/.ix/.bash_init.sh
rbenv
's comprehensive unix shell initialization guide.bash
initialization process flowchart.