4

I have seen many detailed explanations on how .bashrc, .bash_profile and the various other profile files interact, so I'm not after an explanation of how they fit together (I've read many such explanations and yet none of them answer how to approach the below). What I would really appreciate knowing is the best way or best practice or most rational way or most efficient way (clarity, logic, and efficiency are best in my mind) to achieve the following scenarios:

  1. If I want a line of bash code that only runs on opening any new graphical terminals, which file should I put that code into?

  2. If I want a line of bash code that only runs at login for any new interactive shells, where should I put that code?

  3. If I want a line of bash code that opens in both graphical terminals and interactive login shells, which file should I put that code into?

• Important point: To emphasise, I would like the above such that these lines of code only be run upon opening a shell or terminal, and not to run during invocation of any old one-line echo "Hello World!" script that I startup. i.e. I want these to only apply when I choose to open a shell/terminal and then start typing and for other scripts to not include that. Should be simple, right?

I'm really confused by this because these really should be incredibly simple to answer, and yet, I've never seen an answer. I have seen lots of inscrutable answers about how we have to dotsource .profile from .bashrc, or maybe it's the other way around (depending on the weather) and "you have to read document x, and document y, and then it will all be clear!" - the answers get more and more convoluted, and it is difficult to get an answer to the above three scenarios: simply / clearly / unambiguously. Would really appreciate knowing simply which file to put each piece of code into such that it will run in that scenario? I don't mind if there is minimal framework required in achieving that also. i.e. "For scenario 3, add the code to file-x but to facilitate that you must also put the following if-then-fi block into file-y" but it would be a massive bonus if the answer for the three scenarios can be cross-platform (i.e. a one-for-all solution that applies to debian/ubuntu, centos/red hat, MacOS, suse, etc, and also to Gnome/KDE/MacOS etc for the graphical terminal). If there is no rational/logical way to achieve this, then that is disappointing to say the least, but hopefully I'm wrong! Hopefully someone is going to show me a clear, efficient, hopefully cross-platform (but I know that might be too much to ask), repeatable, simple, and unambiguous way to achieve the above.

YorSubs
  • 343

3 Answers3

3

bash recognizes three shell states:

  • login shell - shell instance that has been invoked directly by logging in to the server via eg. ssh or text console
  • interactive shell - any shell where you can type commands, eg. shell started in graphical terminal. Login shell is also (usually) an interactive shell.
  • non-interactive shell - this is usually shell invoked from within some program to run another program or command. As the name implies, in non-interactive shell there is no interaction with the user. For example, if you use ssh to run only a single command on a remote machine (like ssh host.domain ls -l /etc) then you are implicitly invoking a non-interactive shell on the remote machine and that shell in turn runs the command, and then quits.

How the startup files work:

  • a login shell executes commands from /etc/profile (that's the global file for all users) and then looks for files .bash_profile, .bash_login or .profile - in that order - in individual user's home directory, and executes commands from whichever file is found first. In Ubuntu by default only the .profile file exists, and by default it includes commands to execute contents of the .bashrc file also. However, you can remove the code to run .bashrc from the .profile file or you can create any of the other two files with the commands you need, and then .profile will not be run.
  • an interactive (but non-login) shell executes commands from /etc/bash.bashrc (that's global for all users) and then from .bashrc file in the individual user's home directory
  • non-interactive shells started locally (ie. from within a program, or by typing bash in terminal window) do not run any of these files
  • non-interactive shells started from the network (eg. in the above example where you run a command on remote machine via ssh) execute commands from .bashrc file in user's home directory only. However, the default Ubuntu .bashrc file contains a piece of code at the beginning that stops further execution if the shell is non-interactive.

There are some more subtleties in specific cases, but basically it works as above.

So the solution depend on whether you want to keep the current default contents of .profile and .bashrc files or not.

If you want to keep them:

  • for case 1) you can use Artur's solution (but remember, your code will be run not only after login or after start of a terminal, but also whenever you start another shell instance, by eg. simply typing bash within a terminal session. If you want to avoid this, you should slightly modify the solution: [[ "$TERM" == "xterm"* ]] && [ "$SHLVL" = "1" ] && run-command
  • for case 2), put your code in .profile
  • for case 3), just put your code in .bashrc (only remember to insert it below the initial code that exits in case the shell is non-interactive). However, remember that similarly to case 1), your code will be run whenever you start another shell instance, so to avoid it it should be like [ "$SHLVL" = "1" ] && run-command

If you want to do it for all users and not just for yourself, use /etc/bash.bashrc and /etc/profile files instead of ~/.bashrc and ~/.profile.

If you want to remove the defaults:

  • rename .bashrc to something else (to be able to restore it later if you change your mind)
  • create an empty .bash_profile or .bash_login file and then:
  • for case 1), do as above
  • for case 2), put your code in .bash_profile or .bash_login (whichever file you did create)
  • for case 3), first add code to run .bashrc to your .bash_profile or .bash_login file (whichever you did create), as it is not present there by default. Then use Artur's solution - however, similarly to case 1) above, optionally modify it to the form [[ $- == *"i"* ]] && [ "$SHLVL" = "1" ] && run-command

I do not recommend removing the defaults in case of system-wide files (/etc/bash.bashrc and /etc/profile) :)

raj
  • 11,409
2

My suggestion.

Scenario 1: Put your command in ~/.bashrc like this: [[ "$TERM" == "xterm"* ]] && run-command (test if terminal is xterm)

Scenario 2: Put your command in ~/.bash_login (remember to source ~/.bashrc if you still want to run that)

Scenario 3: Put your command in ~/.bashrc like this: [[ $- == *"i"* ]] && run-command (test if shell is interactive)

I use these options in my .bashrc, for more or less the same purposes you want, from what I understand.

Artur Meinild
  • 31,035
1

I use:


Note that the ~/.profile is not bash-specific, so don't use bash-specific commands. This means

# export FOO=bar      # bad
FOO=bar; export FOO   # good

Since my GUI terminal apps are all configured to use a login shell, in my ~/.profile I put this:

# if running bash
if [ -n "$BASH_VERSION" ]; then
    # include .bashrc if it exists
    if [ -f "$HOME/.bashrc" ]; then
        . "$HOME/.bashrc"
    fi
fi

Also, I like to keep my PATH tidy, with no duplicates, so instead of using

PATH="$PATH:/some/path/1"
PATH="/some/path/2:$PATH"

I have

pathmunge () {
    case ":${PATH}:" in
        *:"$1":*)
            ;;
        *)
            [ ! -d "$1" ] && return
            if [ "$2" = "after" ] ; then
                PATH=$PATH:$1
            else
                PATH=$1:$PATH
            fi
    esac
}

then

pathmunge "/some/path/1" after pathmunge "/some/path/2" before pathmunge "/some/path/1" before # already in PATH, no changes made

glenn jackman
  • 18,218