Choosing between .bashrc, .profile, .bash_profile, etc [duplicate]

TL;DR:

  • ~/.bash_profile should be super-simple and just load .profile and .bashrc (in that order)

  • ~/.profile has the stuff NOT specifically related to bash, such as environment variables (PATH and friends)

  • ~/.bashrc has anything you'd want at an interactive command line. Command prompt, EDITOR variable, bash aliases for my use

A few other notes:

  • Anything that should be available to graphical applications OR to sh (or bash invoked as sh) MUST be in ~/.profile

  • ~/.bashrc must not output anything

  • Anything that should be available only to login shells should go in ~/.profile

  • Ensure that ~/.bash_login does not exist.


Over the last few years, I've had a lot of time to waste, so I have researched this for a bit more than just 10 minutes. I have no idea if this is the best layout, it's just one that happens to work correctly in pretty much all cases.

The requirements:

  • ~/.profile must be compatible with any /bin/sh – this includes bash, dash, ksh, whatever else a distro might choose to use.

  • Environment variables must be put in a file that is read by both console logins (i.e. a 'login' shell) and graphical logins (i.e. display managers like GDM, LightDM, or LXDM).

  • There is very little point in having both ~/.profile and ~/.bash_profile. If the latter is missing, bash will happily use the former, and any bash-specific lines can be guarded with a check for $BASH or $BASH_VERSION.

  • The separation between *profile and *rc is that the former is used for 'login' shells, and the latter every time you open a terminal window. However, bash in 'login' mode doesn't source ~/.bashrc, therefore ~/.profile needs to do it manually.

The simplest configuration would be:

  • Have a ~/.profile that sets all environment variables (except bash-specific ones), perhaps prints a line or two, then sources ~/.bashrc if being run by bash, sticking to sh-compatible syntax otherwise.

    export TZ="Europe/Paris"
    export EDITOR="vim"
    if [ "$BASH" ]; then
        . ~/.bashrc
    fi
    uptime
    
  • Have a ~/.bashrc that performs any shell-specific setup, guarded with a check for interactive mode to avoid breaking things like sftp on Debian (where bash is compiled with the option to load ~/.bashrc even for non-interactive shells):

    [[ $- == *i* ]] || return 0
    
    PS1='\h \w \$ '
    
    start() { sudo service "$1" start; }
    

However, there's also the problem that certain non-interactive commands (e.g. ssh <host> ls) skip ~/.profile, but environment variables would be very useful to them.

  • Certain distributions (e.g. Debian) compile their bash with the option to source ~/.bashrc for such non-interactive logins. In this case, I've found it useful to move all environment variables (the export ... lines) to a separate file, ~/.environ, and to source it from both .profile and .bashrc, with a guard to avoid doing it twice:

    if ! [ "$PREFIX" ]; then   # or $EDITOR, or $TZ, or ...
        . ~/.environ           # generally any variable that .environ itself would set
    fi
    
  • Unfortunately, for other distributions (e.g. Arch), I haven't found a very good solution. One possibility is to use the (enabled by default) pam_env PAM module, by putting the following in ~/.pam_environment:

    BASH_ENV=./.environ        # not a typo; it needs to be a path, but ~ won't work
    

    Then, of course, updating ~/.environ to unset BASH_ENV.


Conclusion? Shells are a pain. Environment variables are a pain. Distribution-specific compile-time options are an immense pain in the ass.


Have a look at this excellent blog post by ShreevatsaR. Here's an extract, but go to the blog post, it includes an explanation for terms like "login shell", a flow chart, and a similar table for Zsh.

For Bash, they work as follows. Read down the appropriate column. Executes A, then B, then C, etc. The B1, B2, B3 means it executes only the first of those files found.

+----------------+-----------+-----------+------+
|                |Interactive|Interactive|Script|
|                |login      |non-login  |      |
+----------------+-----------+-----------+------+
|/etc/profile    |   A       |           |      |
+----------------+-----------+-----------+------+
|/etc/bash.bashrc|           |    A      |      |
+----------------+-----------+-----------+------+
|~/.bashrc       |           |    B      |      |
+----------------+-----------+-----------+------+
|~/.bash_profile |   B1      |           |      |
+----------------+-----------+-----------+------+
|~/.bash_login   |   B2      |           |      |
+----------------+-----------+-----------+------+
|~/.profile      |   B3      |           |      |
+----------------+-----------+-----------+------+
|BASH_ENV        |           |           |  A   |
+----------------+-----------+-----------+------+
|                |           |           |      |
+----------------+-----------+-----------+------+
|                |           |           |      |
+----------------+-----------+-----------+------+
|~/.bash_logout  |    C      |           |      |
+----------------+-----------+-----------+------+

I offer you my "comprehensive" guidelines:

  • Make .bash_profile and .profile load .bashrc if it exists, using e.g. [ -r $HOME/.bashrc ] && source $HOME/.bashrc
  • Put everything else in .bashrc.
  • Stop worrying.
  • Every four years or so, spend ten minutes researching this very question before giving up and going back to "not worrying".

EDIT: Added scare quotes to "comprehensive" just in case anyone is tempted to believe it. ;)