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.

4
  • Read man bash, the "INVOCATION" section to start. For the rest, look at the environment(env|sort) in each of the environments. E.g. graphical terminals have $DISPLAY defined. Read man tty for more differences. bash includes IF-THEN-ELSE, so it's a SMOP. Put your code in $HOME/.bashrc.
    – waltinator
    Nov 19, 2020 at 0:34
  • ok, willdo, but as above, if I put code in .bashrc then it will run on any instance of anything (including an invocation of a one-line echo "Hello World!" script!), right? That's pretty bizarre when you think about it, because if the point of the incomprehensible maze of profile files is not to give logical separation to such things, then what is the point?? Was the goal just to build as inscrutable a maze as possible with no clear approaches to anything? Would you say there is no clear approach / no way to answer my scenarios?
    – YorSubs
    Nov 19, 2020 at 7:27
  • I'm not sure I understand the difference between your scenario 1 and 2. Bash uses the terms "login shell" and "interactive shell". How do these relate to your scenarios, and what do you mean exactly by "any new graphical terminal"? Nov 19, 2020 at 8:55
  • See above, scenario 1 says "graphical terminal shell" and 2 says "interactive shell". "any new graphical terminal" is exactly what it says: I am in GNOME or KDE or MacOS or whatever, and I click on the terminal icon, or I press the "Activities" button and type "term" and click on the terminal icon. The following long discussion proves that they are different from interactive shells. askubuntu.com/questions/121073/… in particular the discussion under geirha's answer.
    – YorSubs
    Nov 19, 2020 at 9:03

3 Answers 3

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.

9
  • Thanks. I think it is very unfortunate that this is quite ambiguous and difficult to get answers to. I'm going to try out your solution, looks very good. Out of curiosity, would you say then that in general, you personally only modify .bashrc and .bash_login and leave all files under /etc alone?
    – YorSubs
    Nov 19, 2020 at 9:39
  • For my own user profile, yes. If I had some settings that were global for all users, I could use /etc/profile. Nov 19, 2020 at 9:53
  • It gets complicated because your scenarios are a mix of the state of the shell, and the state of the terminal, which are 2 completely different things. This is also why you need to test $TERM, and not only the state of the shell. Nov 19, 2020 at 9:59
  • 1
    Agree, but you see how this is the normal state of how an averagely IT literate user might approach Linux. They will say "ok, I want to add something to my 'environment' that I can have available to me whenever I open a shell" and it is not apparently obvious that the state of an interactive shell via ssh should be somehow different from an interactive shell that they open when they are sitting at a GNOME desktop. I'm interested in how that can be communicated in an easily comprehensible way.
    – YorSubs
    Nov 19, 2020 at 10:23
  • 1
    Note that creating a ~/.bash_login file will stop ~/.bashrc from running, unless you put code to run ~/.bashrc in your ~/.bash_login file (as it is by default in ~/.profile file)
    – raj
    Nov 19, 2020 at 11:26
2

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

3
  • 2
    .bashrc can be read, even if the shell is non-interactive - for instance when connecting via sftp. That's why the first if-statement in .bashrc by default is a loop that skips reading the file if the shell is non-interactive (and this is important if you want to use sftp - I've tried). Nov 19, 2020 at 11:45
  • 1
    My solution for scenario 3 is "bulletproof", since you can then put it before the "return if non-interactive" if-statement, if you should somehow prefer that. Nov 19, 2020 at 11:56
  • 2
    I forgot about the exit condition at the beginning of .bashrc :). I edited my answer appropriately.
    – raj
    Nov 19, 2020 at 12:34
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

You must log in to answer this question.

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