/.ix

My distributable, normalized and annotated *nix shell environment initialization config - i.e., my "dotfiles".

Primary LanguageShellThe UnlicenseUnlicense

.ix

My distributable, normalized and annotated *nix shell environment initialization config - i.e., my "dotfiles".


About bash initialization

tl;dr

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

Have we got it covered?

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 :).

Uhm, is there actually a need for keeping a separate ~/.bash_profile now?

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!


Setup

Check out .ix into ~/.ix; e.g.:

$ git clone https://github.com/cueedee/.ix.git ~/.ix

...as needed

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.

...once and for all

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

Further reading