572

What's the difference between .bashrc and .bash_profile and which one should I use?

cfischer
  • 9,163

6 Answers6

667

Traditionally, when you log into a Unix system, the system would start one program for you. That program is a shell, i.e., a program designed to start other programs. It's a command line shell: you start another program by typing its name. The default shell, a Bourne shell, reads commands from ~/.profile when it is invoked as the login shell.

Bash is a Bourne-like shell. It reads commands from ~/.bash_profile when it is invoked as the login shell, and if that file doesn't exist¹, it tries reading ~/.profile instead.

You can invoke a shell directly at any time, for example by launching a terminal emulator inside a GUI environment. If the shell is not a login shell, it doesn't read ~/.profile. When you start bash as an interactive shell (i.e., not to run a script), it reads ~/.bashrc (except when invoked as a login shell, then it only reads ~/.bash_profile or ~/.profile.

Therefore:

  • ~/.profile is the place to put stuff that applies to your whole session, such as programs that you want to start when you log in (but not graphical programs, they go into a different file), and environment variable definitions.

  • ~/.bashrc is the place to put stuff that applies only to bash itself, such as alias and function definitions, shell options, and prompt settings. (You could also put key bindings there, but for bash they normally go into ~/.inputrc.)

  • ~/.bash_profile can be used instead of ~/.profile, but it is read by bash only, not by any other shell. (This is mostly a concern if you want your initialization files to work on multiple machines and your login shell isn't bash on all of them.) This is a logical place to include ~/.bashrc if the shell is interactive. I recommend the following contents in ~/.bash_profile:

    if [ -r ~/.profile ]; then . ~/.profile; fi
    case "$-" in *i*) if [ -r ~/.bashrc ]; then . ~/.bashrc; fi;; esac
    

On modern unices, there's an added complication related to ~/.profile. If you log in in a graphical environment (that is, if the program where you type your password is running in graphics mode), you don't automatically get a login shell that reads ~/.profile. Depending on the graphical login program, on the window manager or desktop environment you run afterwards, and on how your distribution configured these programs, your ~/.profile may or may not be read. If it's not, there's usually another place where you can define environment variables and programs to launch when you log in, but there is unfortunately no standard location.

Note that you may see here and there recommendations to either put environment variable definitions in ~/.bashrc or always launch login shells in terminals. Both are bad ideas. The most common problem with either of these ideas is that your environment variables will only be set in programs launched via the terminal, not in programs started directly with an icon or menu or keyboard shortcut.

¹ For completeness, by request: if .bash_profile doesn't exist, bash also tries .bash_login before falling back to .profile. Feel free to forget it exists.

64

From this short article

According to the bash man page, .bash_profile is executed for login shells, while .bashrc is executed for interactive non-login shells.

What is a login or non-login shell?

When you login (eg: type username and password) via console, either physically sitting at the machine when booting, or remotely via ssh: .bash_profile is executed to configure things before the initial command prompt.

But, if you've already logged into your machine and open a new terminal window (xterm) inside Gnome or KDE, then .bashrc is executed before the window command prompt. .bashrc is also run when you start a new bash instance by typing /bin/bash in a terminal.

Jarvin
  • 7,386
47

Back in the old days, when pseudo tty's weren't pseudo and actually, well, typed, and UNIXes were accessed by modems so slow you could see each letter being printed to your screen, efficiency was paramount. To help efficiency somewhat you had a concept of a main login window and whatever other windows you used to actually work. In your main window, you'd like notifications to any new mail, possibly run some other programs in the background.

To support this, shells sourced a file .profile specifically on 'login shells'. This would do the special, once a session setup. Bash extended this somewhat to look at .bash_profile first before .profile, this way you could put bash only things in there (so they don't screw up Bourne shell, etc, that also looked at .profile). Other shells, non-login, would just source the rc file, .bashrc (or .kshrc, etc).

This is a bit of an anachronism now. You don't log into a main shell as much as you log into a gui window manager. There is no main window any different than any other window.

My suggestion - don't worry about this difference, it's based on an older style of using unix. Eliminate the difference in your files. The entire contents of .bash_profile should be:

[ -f $HOME/.bashrc ] && . $HOME/.bashrc

And put everything you actually want to set in .bashrc

Remember that .bashrc is sourced for all shells, interactive and non-interactive. You can short circuit the sourcing for non-interactive shells by putting this code near the top of .bashrc:

[[ $- != *i* ]] && return

Rich Homolka
  • 32,350
28

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      |           |      |
+----------------+-----------+-----------+------+
Flimm
  • 10,730
11

A BETTER COMMENT FOR THE HEAD OF /ETC/PROFILE

Building on Flimm's great answer above, I inserted this new comment at the head of my Debian /etc/profile, (you might need to adjust it for your distro.):

#!/bin/bash     # /etc/profile
#version=1      # In vi/vim say  :set ts=4

FOR ALL LOGINS:

FIRST RUN /etc/profile (THIS) FOR SYSTEM LOGIN, e.g. PS1 and PATH ??. It runs:

/etc/bash.bashrc FOR SYSTEM STARTUP profile might also run:

/etc/profile.d/*.sh FOR SYSTEM STARTUP

THEN RUN ~/.bash_profile FOR USER LOGIN. e.g. colors. It runs:

~/.bashrc FOR USER STARTUP. it runs:

/etc/bash.shared FOR USER STARTUP

ELSE:

FIRST RUN /etc/bash.bashrc FOR SYSTEM STARTUP

THEN RUN ~/.bashrc FOR USER STARTUP It runs:

/etc/bash.shared FOR USER STARTUP

From http://tristram.squarespace.com/home/2012/7/20/why-profile-bash_profile-and-bash_login.html

"bash allows two alternatives for .bash_profile (to get bash user specifics when logging in):

~/.bash_login , derived from the C shell's file named .login, and

~/.profile , derived from the Bourne and Korn shell files named .profile.

Only one of these three is read when you log in. Read first one found in this order:

~/.bash_profile i.e. if this is found read it, or <-- I have this: hwj

~/.bash_login if this is found read it, or

~/.profile if this is found read it, else fail.

One advantage of bash's ability to look for either synonym is that

if you have been using sh (the Bourne shell) you can retain your .profile .

If you need to add bash-specific commands, you can put them in .bash_profile

followed by the command source .profile.

When you log in, all the bash-specific commands will be executed and

then bash will source .profile, executing the remaining shared bash AND Bourne commands.

If you decide to switch to using sh (the Bourne shell) you don't have

to modify your existing files.

A similar approach was intended for .bash_login and the C shell .login,

but due to differences in the basic syntax of the shells, this is not a good idea."

For BASH: Read down the appropriate column.

Executes A, then B, then C.

Executes only the first found of B1, B2, B3.

For bash A then sources (A) and B2 then sources (B2).

+----------------------------------+-------+-----+------------+

| | Interactive | non-Inter. |

| | shell | script |

+----------------------------------+-------+-----+------------+

| | login | non-login |

+==================================+=======+=====+============+

| | | | |

| Runs first | | | |

| ALL USERS (system) | | | |

| | | | |

+==================================+=======+=====+============+

|/etc/profile (all shells) | A | | | (this file) set PS1, & then source the following:

+----------------------------------+-------+-----+------------+

| /etc/bash.bashrc (bash only) | (A) | A | | Better PS1 + command-not-found

+----------------------------------+-------+-----+------------+

| /etc/profile.d/bash_completion.sh| (A) | | |

+----------------------------------+-------+-----+------------+

| /etc/profile.d/vte-2.91.sh | (A) | | | Virt. Terminal Emulator

| /etc/profile.d/vte.sh | (A) | | |

+----------------------------------+-------+-----+------------+

| | | | |

|BASH_ENV | | | A | not interactive script (only reads the environment)

+==================================+=======+=====+============+

+==================================+=======+=====+============+

| | | | |

| Runs second | | | |

| SPECIFIC USER (root & howard) | | | |

| | | | |

+==================================+=======+=====+============+

|~/.bash_profile (bash only) | B1 | | | renamed /root/.profile and /home/howard/.bash_login to this

+----------------------------------+-------+-----+------------+

|~/.bash_login (bash & csh) | B2 | | | (didn't exist) **

+----------------------------------+-------+-----+------------+

| ~/.bashrc (bash only) | (B2) | B | | 1) extends bash PS1 prompt to colorize: su=red, other_users=green

| . ~/bash.shared | | | | 2) sources ~/bash.shared to load shared stuff

+----------------------------------+-------+-----+------------+

|~/.profile (all shells) | B3 | | | (didn't exist - now a link to .bash_profile) ????

+----------------------------------+-------+-----+------------+

+----------------------------------+-------+-----+------------+

|~/.bash_logout | C | | | gets sourced when we log off

+----------------------------------+-------+-----+------------+

** (sources !/.bashrc to colorize login, for when booting into non-

And for what it's worth here is the rest of my /etc/profile script

    # === Get debugging functions ==========================================================
    caller="/etc/profile"
    . /etc/bash.say.shared      # debugging primitives for bash_say function used below and $LS_COLORS var
    n=$(( n + 2 ))  #  indent
# === Source stuff to setup a monitor ==================================================
if [[ $- == *i* ]]; then        # 'himBHs', where i means interactive
    bash_say 'Interactive login.  Setting up monitor.'
    . /etc/bash.bashrc          # Set PATH, umask.  
                                # If interactive also setup dircolors, shell options, chroot prompt, command-completion
fi


# === Source any profile extensions ====================================================
bash_say &quot;&quot;
bash_say &quot;Sourcing any extensions...&quot;
n=$(( n + 2 ))  #  indent

    if [ -d /etc/profile.d ]; then

      for src_d in /etc/profile.d/*.sh; do
        if [ -r $src_d ]; then
          bash_say &quot;Sourcing  $src_d&quot;
          . $src_d
        fi
      done
      unset src_d
    fi
n=$(( n - 2 ))  #  indent
#bash_say &quot;---Done sourcing extensions&quot;


#unset bash_say
n=$(( n - 2 ))  # outdent
bash_say &quot;&quot;

And also here is my included file /etc/bash.say.shared with indenting functions used to help me debug what exactly was going on:

# /etc/bash.say.shared

# === BASH STARTUP UTILITIES ==============================================================================

Sourced by both: /etc/profile (login entry point) and /etc/bash.bashrc (terminal setup entry point)

$caller is the caller

--- Utility functions to show indented status lines ------------------------------------------------

#if test "${BASH_SAY+word}"; then # Don't reload if already defined before

was="already was ";

#else

# --- ENABLED? --------------------------------------------------------------------
# Set to 'true' to show debugging 'bash_say' messages when starting up bash
export BASH_SAY=true
export BASH_SAY=false


# --- FUNCTIONS -------------------------------------------------------------------
# Create spaces function, if not already defined
spaces() { local count=$1; while (( count-- )) ; do echo -n &quot; &quot;; done;}     # Print n spaces

bash_say() {
    # Only echo if enabled AND when there is a tty.  Don't echo status for SSH because it messes up remote scp command.
    if [ $BASH_SAY = 'true'  -a  -t 0 ]; then spaces $n; echo -e &quot;$1 &quot;; fi  #Comment in to debug    #can't get $0 to work
    : ;     #Dummy function filler for when above line is commented out
}
bash_say test

bash_sourcing() { bash_say &quot;&quot;; bash_say &quot;Sourcing $1&quot;; }    # Use at top of files

# Needed so others can use them
export -f spaces
export -f bash_say
export -f bash_sourcing

#fi

bash_sourcing "$caller"; n=$(( n + 2 )) # indent bash_sourcing "/etc/bash.say.shared"; n=$(( n + 2 )) # indent bash_say "Bash startup debugging: ${was}enabled"; unset was n=$(( n - 4 )) # outdent x2 bash_say ""; # line break

Worth noting I think is that Debian's /etc/profile by default sources (includes) /etc/bash.bashrc, (that's when /etc/bash.bashrc exists). So login scripts read both /etc files, while non-login reads only bash.bashrc.

Also of note is that /etc/bash.bashrc is set to do nothing when it's not run interactively. So these two files are only for interactive scripts.

10

The configuration logic of bash's itself is not crazy complicated and explained in other answers in this page, on serverfault and in many blogs. The problem however is what the Linux distributions make of bash, I mean the complex and various ways they configure bash by default. https://mywiki.wooledge.org/DotFiles mentions some of these quirks briefly. Here's one sample trace on Fedora 29, it shows which files source which other file(s) and in which order for a very simple scenario: remotely connecting with ssh and then starting another subshell:

ssh fedora29
 └─ -bash # login shell
      ├── /etc/profile
      |    ├─ /etc/profile.d/*.sh
      |    ├─ /etc/profile.d/sh.local
      |    └─ /etc/bashrc
      ├── ~/.bash_profile
      |    └─ ~/.bashrc
      |          └─ /etc/bashrc
      |
      |
      └─ $ bash  # non-login shell
            └─ ~/.bashrc
                 └─ /etc/bashrc
                       └─ /etc/profile.d/*.sh

Fedora's most complex logic is in /etc/bashrc. As seen above /etc/bashrc is a file bash itself doesn't know about, I mean not directly. Fedora's /etc/bashrc tests whether:

  • it's being sourced by a login shell,
  • it's being sourced by an interactive shell,
  • it has already been sourced

... and then does completely different things depending on those.

If you think can remember the graph above then too bad because it's not nearly enough: this graph merely describes just one scenario, slightly different things happen when running non-interactive scripts or starting a graphical session. I've omitted ~/.profile. I've omitted bash_completion scripts. For backward compatibility reasons, invoking bash as /bin/sh instead of /bin/bash changes its behaviour. What about zsh and other shells? And of course different Linux distributions do things differently, for instance Debian and Ubuntu come with a non-standard version of bash, it has Debian-specific customization(s). It notably looks for an unusual file: /etc/bash.bashrc. Even if you stick to a single Linux distribution it probably evolves over time. Wait: we haven't even touched macOS, FreeBSD,... Finally, let's have a thought for users stuck with the even more creative ways their admins have configured the system they have to use.

As the never-ending stream of discussions on this topic demonstrates, it's a lost cause. As long as you just want to add new values, some "trial and error" tends to be enough. The real fun begins when you want to modify in one (user) file something already defined in another (in /etc). Then be prepared to spend some time engineering a solution that will never be portable.

For a last bit of fun here's the "source graph" for the same, simple scenario on Clear Linux as of June 2019:

ssh clearlinux
 └─ -bash # login shell
      ├── /usr/share/defaults/etc/profile
      |    ├─ /usr/share/defaults/etc/profile.d/*
      |    ├─ /etc/profile.d/*
      |    └─ /etc/profile
      ├── ~/.bash_profile
      |
      |
      └─  $ bash   # non-login shell
           ├─ /usr/share/defaults/etc/bash.bashrc
           |      ├─ /usr/share/defaults/etc/profile
           |      |    ├─ /usr/share/defaults/etc/profile.d/*
           |      |    ├─ /etc/profile.d/*
           |      |    └─ /etc/profile
           |      └─ /etc/profile
           └─ ~/.bashrc
MarcH
  • 481