565

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

4

6 Answers 6

658

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.

22
  • 21
    +1 for good post. ALSO thank you for adding section about "login graphical vs login shell"... I had the problem where I thought ~/.profile would ALWAYS execute for graphical/shell... but it doesn't execute for when the user logs in via graphical login. Thank you for solving that mystery. Sep 17, 2011 at 21:41
  • 6
    @Gilles: Could you explain in more detail, with examples, of why running a login shell in every terminal is a bad idea? Is this only an issue with desktop Linux? (I gather that on OS X Terminal runs a login shell every time, and I've never noticed any side effects (though I usually use iTerm). But then I can't think of many environment variables I'd care about outside of a terminal. (Maybe HTTP_PROXY?))
    – iconoclast
    Jun 12, 2012 at 16:38
  • 2
    @Brandon If you run a login shell in every terminal, that will override environment variables provided by the environment. In everyday situations, you can get away with it, but it'll come and bite you sooner or later, when you want to set up different variables in a terminal (say, to try out a different version of a program): running a login shell would override your local settings. Jun 12, 2012 at 17:57
  • 5
    The statement ~/.bash_profile can be used instead of ~/.profile, but you also need to include ~/.bashrc if the shell is interactive. is misleading as these are orthogonal issues. No matter if you use ~/.bash_profile or ~/.profile you have to include ~/.bashrc in the one you use if you want settings from there to have effect in the login shell. Jun 15, 2013 at 11:20
  • 3
    @Gilles Sure, but the way the sentence is formulated in the answer suggests that the need to include ~/.bashrc has something to do with choosing ~/.bash_profile instead of ~/.profile which is not true. If someone includes ~/.bashrc in any kind of script being sourced at login time (here it's either ~/.bash_profile or ~/.profile) is because he wants settings from ~/.bashrc to be applied to the login shell the same way they are being applied to non-login shell. Jun 15, 2013 at 12:29
63

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.

2
  • 16
    Slight updates: 'Executed' is probably a slightly misleading term, they're both sourced. Executed sounds like it's run as a script, fork/exec yadda yadda. It's run in the context of the current shell More importantly, .bashrc is run much more often. It is run on every bash script run, and also if you don't have a .bash_profile. Also, depending how you set up your xterms, you may create a shell that sources .bash_profile Sep 2, 2010 at 17:57
  • 1
    @RichHomolka ` It (~/.bashrc) is run on every bash script run` is untrue. By default, ~/.bashrc is invoked only for interactive non-login shells, and therefore, will not be sourced in a script. Jun 14, 2021 at 10:05
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

9
  • 7
    This is a bad idea, see my answer. In particular, 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. Sep 2, 2010 at 19:24
  • 6
    @Gilles I don't understand why you claim this. With .$HOME/.bashrc as Rich showed above, settings in .bashrc will be available in login shells, and thus the desktop environment as well. For example, on my Fedora system, gnome-session is started as -$SHELL -c gnome-session, so .profile is read.
    – Mikel
    Jun 2, 2012 at 16:25
  • 2
    @PiotrDobrogost Oh, yes, there's another problem with Rich's answer. Including .bashrc in .profile typically doesn't work, because .profile may be executed by /bin/sh and not bash (e.g. on Ubuntu for a graphical login by default), and that shell may not be interactive (e.g. for a graphical login). Jun 15, 2013 at 11:24
  • 3
    @Gilles re: "including .bashrc in .profile" is not at all what was recommended (quite the contrary, in fact). Either the answer was edited (it doesn't appear so), or your comments don't align with what is being said.
    – michael
    Jul 29, 2013 at 6:17
  • 2
    In general, +1, but I would add to the recommendation to "short circuit...for non-interactive shells" ("near the top of .bashrc: [[ $- != *i* ]] && return"); I do like some of my .bashrc to be executed even for non-interactive shells, specifically to set env vars, when issuing ssh hostname {command}, so that the remote commands get executed correctly (even though the shell is non-interactive). But other settings later in .bashrc should be ignored. I usually check for TERM=dumb and/or unset, and then bail out early.
    – michael
    Jul 29, 2013 at 6:30
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      |           |      |
+----------------+-----------+-----------+------+
3
  • Rather than posting the same answer on multiple questions it is preferred if you can tailor your answer to the specific needs of the asker. If the answer is exactly the same for both questions then you should be posting a single answer and voting to close the other questions as duplicates of the original.
    – Mokubai
    Jul 13, 2016 at 18:13
  • 1
    @Mokubai The other question has already been marked as a duplicate of this one.
    – Flimm
    Jul 13, 2016 at 20:34
  • @ElipticalView: by set to do nothing, you're referring to the line: [ -z "$PS1" ] && return? The table in my answer is giving the list of scripts run by Bash regardless of the contents of the scripts, if the script itself has the line [ -z "$PS1" ] && return, of course that would take effect, but I don't think that should mean I should change the table.
    – Flimm
    Oct 18, 2016 at 10:51
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 ""
    bash_say "Sourcing any extensions..."
    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 "Sourcing  $src_d"
              . $src_d
            fi
          done
          unset src_d
        fi
    n=$(( n - 2 ))  #  indent
    #bash_say "---Done sourcing extensions"
    
    
    #unset bash_say
    n=$(( n - 2 ))  # outdent
    bash_say ""

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 " "; 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 "$1 "; 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 ""; bash_say "Sourcing $1"; }    # 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.

1
  • Thanks for this answer - BTW - Does it apply for Ubuntu? Sep 17, 2021 at 2:10
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. http://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
1
  • 1
    Linux shell and gdm startup initialization case is epic collection of anti-patterns, malfunction and misbehavior. I work in these environments for many years and has always been surprised how careless, ill-considered, not adapted to the most profound use case of reliable and reproducible customization of environment this framework is. Still, I am very grateful to all contributors, on a large scale Linux remains the best environment for many, if not majority of IT professionals
    – maoizm
    Jan 24, 2021 at 17:08

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .