The bash man page defines invocation order for login and non-login shells, and the differences are critical to best using the various scripts. I've included where to read in auxilliary files similar to /etc/profile.d/*.sh in your own dotfiles, but that's optional (unless you're an admin trying to set up default scripts for a bunch of users).
(Caveat: many Linux installations violate startup order and/or have weird interrationships between the system-provided bash startup scripts. This seems to stem partly from a lack of any /etc/bash.bashrc.d/*.bash infrastructure, so they keep putting bash-specific scripts in /etc/profile.d/*.sh . Don't follow their misexamples in your home directory.)
So here we go.
login shells
Read along in man bash under the INVOCATION header to see where this "first found" thing comes from.
/etc/profile - this is shared with sh, ksh, zsh, and other Bourne family shells
- The first found of (but we'll use all three):
~/.bash_profile - we'll read in ~.profile , . ~/.bashrc , and ~/.bash_login
~/.bash_login - bash will skip it, but we'll use it
~/.profile - bash will skip it, but we'll use it
- (the shell's interactive session, then)
~/.bash_logout
non-login shells (subshells)
/etc/bash.bashrc
~/.bashrc
non-interactive shells
$BASH_ENV (if present, or, if run as "sh", $ENV )
Now, how to use this plumbing. I'll address mostly the interactive shells.
The main point is that we want both login shells and subshells to:
- have a fully configured set of environment variables
- speed subshell startup by inheriting envvars instead of resetting them
- be compatible with Bourne shell, sh(1), when convenient
- have all the functions we want (and aliases if you like those for some reason)
- have that with no duplication in the startup files
Here's how to do this, broken down by file:
/etc/profile
You can't prevent this file from being read, but you can undo most stupid things it might do. Check any files in /etc/profile.d/*.sh ... on my system there are some envvars for Snap, Wayland, and XDG, for example, and I move all my XDG subdirs under ~/xdg/ in my ~/.profile . Most of the times settings in these files are harmless.
~/.profile
This can be shared by sh, ksh, zsh, and others, so we want to avoid anything but pure old-school Bourne shell, sh(1) syntax. Usually this just means using backticks inside of double quotes to capture command output instead of $(...) .
- Do all your environment variable set up in here
- (Optional) Read in any files matching
~/.profile.d/*.sh
If you use multiple families of shells, like zsh, es, and tcsh together, you'd probably want to make your environment settings shell-neutral, by writing a script to list all the settings, then read that in with ~/.profile (or the equivalent fofor each shell syntax) and set them (I do this).
For X window system users, the ~/.xsessionrc file can read in the ~/.profile to provide a core environment (often by starting it via ~/.xinitrc which is shared with xinit ) to the X session and window manager, passing it down to all processes started from it.
~/.bash_profile
Use this to:
- read in
~/.profile to get envvars
- set any additional variables specific to bash that can be inherited by subshells
- read in
~/.bashrc to get functions and tty setup
- read in
~/.bash_login to run any at-login things (check mail?) now that your context has been fully configured (also lets those commands use your functions, although this isn't really recommended)
The most important thing above is that ~/.bashrc is read in by your ~/.bash_profile , which means all interactive shells will run with similar envvars, functions, etc
Example:
# ~/.bash_profile -*- sh -*-
# Environment variables and sundry
[ -r ~/.profile ] && . ~/.profile
# Login shells don't read the ~/.bashrc -- this does it explicitly, but
# *AFTER* the envvar are set up, to match the sequence for subshells
[ -r ~/.bashrc ] && . ~/.bashrc
# The csh ~/.login was interactive only; this reprises that behavior
if [ -n "$PS1" ] && [ -r ~/.bash_login ] ; then
. ~/.bash_login # yes, this lets the exit status propagate.
else
true # set $! to success even if .bash_login isn't present
fi
~/.bashrc - the only thing automatically read by subshells
Speed matters a little for subshell startups, so try to avoid doing anything slow.
- DO NOT set envvars that were already set in
~/.profile !
- (Optional) Read in any files matching
~/.bashrc.d/*.bash (or .sh or .ksh) - probably before anything below this
- set any other bash-centric environment vars you missed in
~/.bash_profile
- define all your functions
- define any aliases (aside: I have
unalias -a , YMMV)
- 100+ years of shared unix experience only produced one case where an alias worked better than a function, and we were so shocked we rewrote what would have needed that corner case to not need it. It involved specifically setting the last character in the alias to a blank, and if you don't know about alias chaining, just use functions, since functions are better, anyway
- lots of
shopt commands
- terminal setup, color handling, etc
- customize your prompt
- optional, an
rm function (see notes)
~/.bash_login
Checking for mail used to be common. Some people like to log when they log in (and then when they log out from their ~/.bash_logout ).
This may not be what Bash's authors intended, but since the usage model described here overall would otherwise just leave this one file unused, we'll use it for what the C-Shell used its ~/.login for - commands to run at login session start.
~/.bash_logout
Rarely used, but sometimes pairs well with commands in ~/.bash_login .
The C-Shell also had a ~/.logout , and Bash's docs seem to agree on using the ~/.bash_logout for the same thing - commands to run at session end.
Be aware that a non-interactive subshell that calls exit explicitly will also run this, but won't have $PS1 set, see the section about this issue.
Be Careful about Output
If one's startup scripts output anything while inside a shell program, it can really screw up that program's run. Be sure to avoid outputting anything unless the main prompt string is non-empty, i.e.:
if [ -n "$PS1" ] ; then
echo .... # this is okay
fi
The syntax shown works in any Bourne family shell.
An rm function
As an admin, I used to get restore requests frequently from users whose rm commands were hidden by alias rm 'rm -i' , essentially because it:
- trained them to remove the world and then pick omissions
- they'd often hit
y a few too many times
- if the alias were absent, they'd destroy themselves
So instead, I recommend this, so that users are asked once, and will have a habit that is unlikely to delete the world if the function is missing one day:
rm () # *must* be a function, *must* require one answer for all targets
{
ls -FCsd "$@"
read -i 'remove[ny]? '
if [ "_$REPLY" = "_y" ] ; then
/bin/rm -rf "$@"
else
echo '(cancelled)'
fi
}
This must not be implemented as a script, and must not be defined for non-interactive shells.
|