Bash Startup Files

Table of Contents

Bash (Bourne Again SHell) is pretty much universal now, almost every unix-like distributions ship bash as the default shell. This is probably because GNU uses bash as their default shell and for those who don't know, linux is GNU/linux. And the GNU collection of utilities are designed to be used with bash as the shell.

As for the flow of the configuration files:

File Description Login shell Interactive, non login shell
/etc/profile Usually will search for /etc/profile.d or /etc/bash.bashrc depending on the distro Yes No
~/.bash_profile After etc/profile, if not found, will search for ~.bash_login and ~/.profile in that order Yes No
~/.bash_logout after exit of login shell Yes No
/etc/bash.bash_logout Depends on the -DSYS_BASH_LOGOUT="/etc/bash.bash_logout" compilation flag. After exit of a login shell. Yes No
/etc/bash.bashrc Depends on the -DSYS_BASHRC="/etc/bash.bashrc" compilation flag. Sources /usr/share/bash-completion/bash_completion. No Yes
~./bashrc Per-user, after /etc/bash.bashrc. No Yes

Usually, when using a terminal emulator, we must set the shell as bash --login to ensure the startup files is called correctly for each instance of that terminal since it is run from a graphical environment (X or wayland), this is an interactive non-login shell.

An interactive login shell is started after login (via tty, not display manager like lightdm or gdm).

As for non-interactive shell is usually present when a shell script is running, it's processing a script not waiting for user input between each commands. Only the environment invoked from the parent shell is used.

The way I set my bash startup files:

Try not to mess with the global startup files
Respect the /etc/profile and the likes, they are configured to be used by many other applications. And the distribution maintaners usually sets some variables for that purpose.
Modular
It's basically a script, separate each uses to a specific file.
Simple
Should work on many other distributions that uses bash, I use this configuration for archlinux, ubuntu, debian, even freebsd.

konsole.png

Figure 1: konsole on archlinux

~/.bashrc

do nothing if non-interactive

For when run as a script.

# If not running interactively, don't do anything
[[ $- != *i* ]] && return

  if [[ -f "/etc/bashrc" ]] ; then
      source /etc/bashrc
  elif [[ -f "/etc/bash.bashrc" ]]; then
      source /etc/bash.bashrc
  fi

bash directories

I'm separating the configuration into smaller files. XDG_CONFIG_HOME and XDG_DATA_HOME should be set by the system, I also declared them in my repository for common shells.

# setup directory for everything bash-related
BASH_DIR="${XDG_CONFIG_HOME:-~/.config}"/bash.d
BASH_DATA="${XDG_DATA_HOME:-~/.local/share}"/bash

[[ ! -d "${BASH_DIR}" ]] && mkdir -p "$BASH_DIR"
[[ ! -d "${BASH_DATA}" ]] && mkdir -p "$BASH_DATA"

shell options

There's many more shell options for bash, but I don't use them much.

# check the window size after each command and, if necessary,
# update the values of LINES and COLUMNS.
shopt -s checkwinsize

aliases

# Alias definitions.
# You may want to put all your additions into a separate file like
# $BASH_DIR/bash_aliases, instead of adding them here directly.
# See /usr/share/doc/bash-doc/examples in the bash-doc package.
if [[ -f "${BASH_DIR}/bash_aliases" ]]; then
    . "${BASH_DIR}"/bash_aliases
fi

Note that in archlinux the /usr/share/doc/bash-doc is not exists

completion

if [[ -z "${BASH_COMPLETION_VERSINFO}" ]];then
    # enable programmable completion features (you don't need to enable
    # this, if it's already enabled in /etc/bash.bashrc and /etc/profile
    # sources /etc/bash.bashrc).
    if [[ -f /etc/bash_completion ]] && ! shopt -oq posix; then
        . /etc/bash_completion
    elif [[ -f /usr/share/bash_completion/bash_completion ]] && ! shopt -oq posix; then
        . /usr/share/bash_completion/bash_completion
    elif [[ -f /usr/local/share/bash_completion/bash_completion ]] && ! shopt -oq posix; then
        . /usr/local/share/bash_completion/bash_completion
    elif [[ -f /etc/bash/bashrc.d/bash_completion.sh ]] && ! shopt -oq posix; then
        . /etc/bash/bashrc.d/bash_completion.sh
    fi
fi

# user completion files
if [[ -d "{$BASH_DIR}/bash_completion.d" ]]; then
    for i in "${BASH_DIR}"/bash_completion.d/*.bash; do
        if [[ -r ${i} ]]; then
            . "${i}"
        fi
    done
    unset i
fi

This should cover every location for bash completion.

color prompt

# color prompt, this is gentoo bashrc file
[[ -f "${BASH_DIR}/colors.bash" ]] && . "${BASH_DIR}"/colors.bash

Taken from gentoo i guess…

history

# history
[[ -f "${BASH_DIR}/history.bash" ]] && . "${BASH_DIR}"/history.bash

setup history

custom functions

# functions
[[ -f "${BASH_DIR}/functions.bash" ]] && . "${BASH_DIR}"/functions.bash

Put custom functions in a file.

freebsd specifics

if [[ "${DISTRO}" != "freebsd" ]]; then
    iatest=$(expr index "$-" i)
    if [[ ${iatest} -gt 0 ]]; then
        # Disable the bell
        bind "set bell-style visible";

        # Ignore case on auto-completion
        # Note: bind used instead of sticking these in .inputrc
        bind "set completion-ignore-case on";

        # Show auto-completion list automatically, without double tab
        bind "set show-all-if-ambiguous On";
    fi
fi

Some settings specific to FreeBSD.

history navigation with ctrl-r

# Allow ctrl-S for history navigation (with ctrl-R)
stty -ixon

Enable CTRL-s and CTRL-r

local override

# local bashrc
if [[ -f "${BASH_DIR}/bashrc.local" ]];then
   . "${BASH_DIR}"/bashrc.local
fi
   # End ~/.bashrc

In case I need to override all or some settings above, I'll put them in this file.

~/.bash_profile

source ~/etc/profile

[[ -f /etc/profile ]] && source /etc/profile

Making sure that /etc/profile is also sourced, the only downside is some variables, such as PATH, would be declared multiple times. Depending on how the PATH is set, the variable will have repeated paths. But in my common shell repository I make sure that each item is unique.

This differs in several Linux distribution.

# in the actual startup sequences, "~/.bash_profile" is invoked
# before "~/.profile" so we need to source "~/.profile" first.
[[ -f ${HOME}/.profile ]] && source "${HOME}"/.profile
[[ -f ${HOME}/.bashrc ]] && source "${HOME}"/.bashrc

local override

# local bash_profile
# ${BASH_DIR} should already set by now
[[ -f ${BASH_DIR}/bash_profile.local ]] && source "${BASH_DIR}"/bash_profile.local

# End ~/.bash_profile

Again, in case I need to override all or some settings above…

~/.bash_logout

clear console on logout

# when leaving the console clear the screen to increase privacy

  if [ "$SHLVL" = 1 ]; then
      #[ -x /usr/bin/clear_console ] && /usr/bin/clear_console -q
      [ -x /usr/bin/clear ] && /usr/bin/clear
  fi

# End ~/.bash_logout

~/.config/bash.d

bash_aliases

Most of the coreutils programs have a syntax for color.

color support of most of the coreutils

# enable color support of ls and also add handy aliases
if [ -x /usr/bin/dircolors ]; then
    test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
    alias ls='ls --color=auto'
    #alias dir='dir --color=auto'
    #alias vdir='vdir --color=auto'

    #BSD#@export CLICOLOR=1
    #GNU#@alias ls='ls --color=auto'
    export CLICOLOR=1
    alias grep='grep --color=auto'
    alias fgrep='fgrep --color=auto'
    alias egrep='egrep --color=auto'
    alias diff='diff --color=auto'
    alias ip='ip -color=auto'
fi

ls aliases

# some more ls aliases
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'

Some useful ls aliases.

alert

# Add an "alert" alias for long running commands. Use like so:
# sleep 10; alert
alias alert='notify-send --expire-time=2000 --urgency=low --icon="$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"'

alias xp='xprop | grep "WM_WINDOW_ROLE\|WM_CLASS" && echo "WM_CLASS(STRING) = \"NAME\", \"CLASS\""'

I don't think I've ever use this anymore, but it still nice to have

colors.bash

PS1

Set PS1 when the terminal support it.

#!/usr/bin/env bash
# Set colorful PS1 only on colorful terminals.
# dircolors --print-database uses its own built-in database
# instead of using /etc/DIR_COLORS.  Try to use the external file
# first to take advantage of user additions.
# We run dircolors directly due to its changes in file syntax and
# terminal name patching.

for sh in /etc/bash/bashrc.d/* ; do
    [[ -r ${sh} ]] && source "${sh}"
done

use_color=false
if type -P dircolors >/dev/null ; then
    # Enable colors for ls, etc.  Prefer ~/.dir_colors #64489
    LS_COLORS=
    if [[ -f ~/.dir_colors ]] ; then
        eval "$(dircolors -b ~/.dir_colors)"
    elif [[ -f /etc/DIR_COLORS ]] ; then
        eval "$(dircolors -b /etc/DIR_COLORS)"
    else
        eval "$(dircolors -b)"
    fi
    # Note: We always evaluate the LS_COLORS setting even when it's the
    # default.  If it isn't set, then `ls` will only colorize by default
    # based on file attributes and ignore extensions (even the compiled
    # in defaults of dircolors). #583814
    if [[ -n ${LS_COLORS:+set} ]] ; then
        use_color=true
    else
        # Delete it if it's empty as it's useless in that case.
        unset LS_COLORS
    fi
else
    # Some systems (e.g. BSD & embedded) don't typically come with
    # dircolors so we need to hardcode some terminals in here.
    case ${TERM} in
        [aEkx]term*|rxvt*|gnome*|konsole*|screen*|cons25|*color|linux) use_color=true;;
    esac
fi

# bash prompt variables
#
# \a
#     A bell character.
# \d
#     The date, in "Weekday Month Date" format (e.g., "Tue May 26").
# \D{format}
#     The format is passed to strftime(3) and the result is inserted
#     into the prompt string; an empty format results in a locale-specific
#     time representation. The braces are required.
# \e
#     An escape character.
# \h
#     The hostname, up to the first ‘.’.
# \H
#     The hostname.
# \j
#     The number of jobs currently managed by the shell.
# \l
#     The basename of the shell’s terminal device name.
# \n
#     A newline.
# \r
#     A carriage return.
# \s
#     The name of the shell, the basename of $0 (the portion
#     following the final slash).
# \t
#     The time, in 24-hour HH:MM:SS format.
# \T
#     The time, in 12-hour HH:MM:SS format.
# \@
#     The time, in 12-hour am/pm format.
# \A
#     The time, in 24-hour HH:MM format.
# \u
#     The username of the current user.
# \v
#     The version of Bash (e.g., 2.00)
# \V
#     The release of Bash, version + patchlevel (e.g., 2.00.0)
# \w
#     The current working directory, with $HOME abbreviated with a
#     tilde (uses the $PROMPT_DIRTRIM variable).
# \W
#     The basename of $PWD, with $HOME abbreviated with a tilde.
# \!
#     The history number of this command.
# \#
#     The command number of this command.
# \$
#     If the effective uid is 0, #, otherwise $.
# \nnn
#     The character whose ASCII code is the octal value nnn.
# \\
#     A backslash.
# \[
#     Begin a sequence of non-printing characters. This could be used to
#     embed a terminal control sequence into the prompt.
# \]
#     End a sequence of non-printing characters.

# ANSI color codes
# RS="\[\033[0m\]"    # reset
# HC="\[\033[1m\]"    # hicolor
# UL="\[\033[4m\]"    # underline
# INV="\[\033[7m\]"   # inverse background and foreground
# FBLK="\[\033[30m\]" # foreground black
# FRED="\[\033[31m\]" # foreground red
# FGRN="\[\033[32m\]" # foreground green
# FYEL="\[\033[33m\]" # foreground yellow
# FBLE="\[\033[34m\]" # foreground blue
# FMAG="\[\033[35m\]" # foreground magenta
# FCYN="\[\033[36m\]" # foreground cyan
# FWHT="\[\033[37m\]" # foreground white
# BBLK="\[\033[40m\]" # background black
# BRED="\[\033[41m\]" # background red
# BGRN="\[\033[42m\]" # background green
# BYEL="\[\033[43m\]" # background yellow
# BBLE="\[\033[44m\]" # background blue
# BMAG="\[\033[45m\]" # background magenta
# BCYN="\[\033[46m\]" # background cyan
# BWHT="\[\033[47m\]" # background white
# txtblk='\e[0;30m' # Black - Regular
# txtred='\e[0;31m' # Red
# txtgrn='\e[0;32m' # Green
# txtylw='\e[0;33m' # Yellow
# txtblu='\e[0;34m' # Blue
# txtpur='\e[0;35m' # Purple
# txtcyn='\e[0;36m' # Cyan
# txtwht='\e[0;37m' # White
# bldblk='\e[1;30m' # Black - Bold
# bldred='\e[1;31m' # Red
# bldgrn='\e[1;32m' # Green
# bldylw='\e[1;33m' # Yellow
# bldblu='\e[1;34m' # Blue
# bldpur='\e[1;35m' # Purple
# bldcyn='\e[1;36m' # Cyan
# bldwht='\e[1;37m' # White
# unkblk='\e[4;30m' # Black - Underline
# undred='\e[4;31m' # Red
# undgrn='\e[4;32m' # Green
# undylw='\e[4;33m' # Yellow
# undblu='\e[4;34m' # Blue
# undpur='\e[4;35m' # Purple
# undcyn='\e[4;36m' # Cyan
# undwht='\e[4;37m' # White
# bakblk='\e[40m'   # Black - Background
# bakred='\e[41m'   # Red
# bakgrn='\e[42m'   # Green
# bakylw='\e[43m'   # Yellow
# bakblu='\e[44m'   # Blue
# bakpur='\e[45m'   # Purple
# bakcyn='\e[46m'   # Cyan
# bakwht='\e[47m'   # White
# txtrst='\e[0m'    # Text Resetreset='\033[0m'


if ${use_color} ; then
    if [[ ${EUID} == 0 ]] ; then
        PS1='\[\033[01;31m\]\h\[\033[01;34m\] \w \$\[\033[00m\] '
    else
        #PS1='\[\033[01;32m\]\u@\h\[\033[01;34m\] \w \$\[\033[00m\] '
        case "${DISTRO}" in
            # nothing fancy, just different colors for hostname
            # blue for arch derivatives
            "artix"|"arch")
                PS1="\[\033[0;32m\]\u\[\033[00m\]\e[2m\[\033[1m\]@\[\033[00m\]\[\033[00m\]\[\033[0;34m\]\h\[\033[00m\]\[\033[01;34m\] \w \$\[\033[00m\] "
                ;;
            "debian"|"devuan"|"ubuntu")
                # purple for debian and such
                PS1='\[\033[0;32m\]\u\[\033[00m\]\e[2m\[\033[1m\]@\[\033[00m\]\[\033[00m\]\[\033[0;35m\]\h\[\033[00m\]\[\033[01;34m\] \w \$\[\033[00m\] '
                ;;
            "freebsd")
                # red
                PS1='\[\033[0;32m\]\u\[\033[00m\]\e[2m\[\033[1m\]@\[\033[00m\]\[\033[00m\]\[\033[1;31m\]\h\[\033[00m\]\[\033[01;34m\] \w \$\[\033[00m\] '
                ;;
            "gentoo")
                # bold blue
                PS1='\[\033[0;32m\]\u\[\033[00m\]\e[2m\[\033[1m\]@\[\033[00m\]\[\033[00m\]\[\033[1;34m\]\h\[\033[00m\]\[\033[01;34m\] \w \$\[\033[00m\] '
        esac
    fi
else
    # show root@ when we don't have colors
    PS1+='\u@\h \w \$ '
fi

# Try to keep environment pollution down, EPA loves us.
unset use_color sh

functions.bash

pathremove

if [ ! "$(type pathremove >/dev/null 2>&1)" ] ; then
    pathremove () {
        local IFS=':'
        local NEWPATH
        local DIR
        local PATHVARIABLE=${2:-PATH}
        for DIR in ${!PATHVARIABLE} ; do
            if [ "${DIR}" != "${1}" ] ; then
                NEWPATH=${NEWPATH:+$NEWPATH:}$DIR
            fi
        done
        export "$PATHVARIABLE"="${NEWPATH}"
    }
    export -f pathremove
fi

This function is for removing an entry from PATH

pathprepend

if [ ! "$(type pathprepend >/dev/null 2>&1)" ] ; then
    pathprepend () {
        pathremove "${1}" "${2}"
        local PATHVARIABLE=${2:-PATH}
        export "$PATHVARIABLE"="$1${!PATHVARIABLE:+:${!PATHVARIABLE}}"
    }
    export -f pathprepend
fi

For prepending an item into PATH

pathappend

if [ ! "$(type pathappend >/dev/null 2>&1)" ] ; then
    pathappend () {
        pathremove "${1}" "${2}"
        local PATHVARIABLE=${2:-PATH}
        export "$PATHVARIABLE"="${!PATHVARIABLE:+${!PATHVARIABLE}:}$1"
    }
    export -f pathappend
fi
# end ~/.config/bash.d/functions.bash

Appending an item to PATH

history.bash

Here I set some variable that is use by the bash history. Most of these are self explainatory.

set the location of history file

export HISTFILE="${BASH_DATA}"/bash_history

move previous history to current file (if any)

if [ -f "${HOME}/.bash_history " ]; then
    if [ -f "${HISTFILE}" ]; then
        cat "${HOME}"/.bash_history >> "${HISTFILE}"
    fi
    rm "${HOME}"/.bash_history
fi

Lines of history in memory

HISTSIZE=10000

Save 2,000,000 lines of history to disk (will have to grep ~/.bash_history for full listing)

HISTFILESIZE=2000000

Append to history instead of overwrite

shopt -s histappend

Ignore redundant or space commands

HISTCONTROL=ignoreboth

Ignore more

HISTIGNORE='ls:ll:ls -alh:pwd:clear:history'

Set time format

HISTTIMEFORMAT='%F %T '

Multiple commands on one line show up as a single line

shopt -s cmdhist

Append new history lines, clear the history list, re-read the history list, print prompt.

Currently unused

export PROMPT_COMMAND="history -a; history -c; history -r; ${PROMPT_COMMAND}"

Date: 2022-06-11 Sat 00:00

Author: Kristian Alexander P

Created: 2024-06-11 Tue 08:10