Here’s a snapshot of my .bashrc, I put this together to normalise my environment; which is particularly useful when working across CentOS/RHEL 6/7, SLES11/12 & Debian/Ubuntu.

I’ll overexplain some lines and underexplain others, depending on how interesting they are.

if [[ ! "$-" =~ "i" ]]; then return; fi

Check if bash has been invoked as an interactive session or not, this tells us whether we the .bashrc is needed at all. We could check whether $PS1 is set, this sets the prompt’s behaviour, but it makes more sense to check the bash session options in $-.

bashrc_start="$( date +%s )"

Store a timestamp right at the start of .bashrc execution, later we can get the difference to determine how long .bashrc took to run.

for src in "bashrc" "bash.bashrc"; do if [ -f "/etc/$src" ]; then . "/etc/$src"; fi; done

Look for global bashrc files in /etc/ and source them if found. Here I loop over two potential names for these files, being sure to check if the target is a file.

[ "$(locale -a | grep 'en_GB.utf8')" ] && LANG="en_GB.utf8" || LANG="en_US.utf8"

Here is a compact conditional statement, by returning the output of the subshell into quotes the square brackets can test against whether something is returned. If the quotes are non-empty the first statement is run, setting LANG to en_GB.utf8, otherwise the statement after || is run.

for zopt in autocd checkjobs cdspell dirspell dotglob histappend globstar nocaseglob; do
    shopt -s $zopt 2>/dev/null
done

Now lets enable a bunch of bash features, again using a loop:

  • autocd - cd when command not in path but matches a local directory
  • checkjobs - Prompts for a second exit if jobs are running
  • cdspell - Fix, print and run command when it has transposed characters, a missing character and a character too many
  • dirspell - Correct directory names during word completion
  • dotglob - Includes filenames beginning with “.” in filename expansion
  • histappend - Prevent wiping bash history after each session
  • globstar - Match all files, directories and subdirectories
  • nocaseglob - Case-insensitive glob matching

Using 2>/dev/null here throws away any error messages; this prevents seeing errors everytime .bashrc is loaded with an older version of Bash, or if features are deprecated.

export HISTCONTROL=ignoredups:erasedups
export HISTSIZE=999999
export HISTFILESIZE=999999
export GCC_COLORS=

Setting some environment variables, specifically to garentee as many lines as possible in our .bash_history; we do this to enhance the usefulness of ^R and looking back at what you did to cause your current problem!

alias cd..="cd .."
alias ...="cd ../.."
alias l="ls -h"
alias ll="ls -lhA"
alias la="ls -Ah"
alias rm="rm -v"
alias mkdir="mkdir -pv"
alias wget="wget -c"
alias venv="source ./bin/activate"
alias quota="quota -s"

Some flags are always set, so it makes sense to make them permanent! If you need to temporarily remove the flags you can either invoke the program with a full path, i.e. /bin/ls instead of ls or run unalias ls to change it for your session.

All my ls aliases have -h in order to see mega/giga-bytes instead of only bytes, even when the alias doesn’t show those values as I could run l -l which equates to ls -h -l.

I don’t alias ls on purpose, it is such a common utility that I’d rather not influence every script I run to behave in a human-friendly way, also sometimes I need to run ls without colours so I leave it bare, I have l for that!

A couple of examples of useful defaults include wget -c for continuing interrupted downloads, why not? Also I have venv just to shorthand . bin/activate which is most useful for Python programmers.

if command -v vim >/dev/null; then alias vi=$(which vim); fi
if command -v systemctl >/dev/null && ! alias sctl &>/dev/null; then alias sctl="systemctl"; fi
if command -v journalctl >/dev/null && ! alias jctl &>/dev/null; then alias jctl="journalctl"; fi

Here we safely alias some programs where we aren’t certain they will always be installed or a binary may have already taken the alias’ name.

Running command -v gives us a return-code to work with:

  • Alias vi to vim when vim is installed
  • Alias sctl to systemctl if systemctl is installed and sctl isn’t already an alias.
alias gits="git status"
alias gitd="git diff"
alias gitc="git commit -m"
alias gita="git add"
alias gitp="git push"

We all git, so to save fingers we should alias the most command commands!

if [[ "$UID" == 0 && -f /proc/1/root/. && \
      "$(stat -c %d:%i /)" != "$(stat -c %d:%i /proc/1/root/.)" ]];
then
    IN_CHROOT=true
    if [ -f /etc/debian_chroot ]; then CHROOT_NAME="$(cat /etc/debian_chroot)"; fi
fi

Here’s some arcane magic to determine if the shell is running inside a chroot. I don’t rely on this, I’m not entirely sure it’s useful either as a chrooting doesn’t typically take your environment with you.

## Detect virtual screens
if [ "$STY" ]; then   IS_GNUSCREEN=true; fi
if [ "$TMUX" ]; then  IS_TMUX=true; fi

Detect terminal emulators, I prefer to set an $IS_* instead of testing for the terminal emulators directly, mostly in-case I later update the detection code to be more complex; it is also easier to remember when using these variables later.

for path in "$HOME/bin" "$HOME/local/bin" "$HOME/.local/bin"; do
    if [[ -d "$path" ]]; then
        PATH="${PATH}:${path}"
    fi
done

Append home bin directories to your PATH when the directories exist.

for path in "$HOME/lib" "$HOME/lib64" "$HOME/.local/lib" "$HOME/.local/lib64"; do
    if [[ -d "$path" ]]; then LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${path}"; fi
done

Append home lib directories to your LD_LIBRARY_PATH to be used in compilations when the directories exist.

for path in "$HOME/.rbenv/shims"; do
    if [ -d "$path" ]; then PATH="${path}:${PATH}"; fi
done

for path in "$HOME/.cargo/bin"; do
    if [ -d "$path" ]; then PATH="${PATH}:${path}"; fi
done

for path in ${HOME}/bin/* ${HOME}/.local/bin/*; do
    if [ -d "${path}/bin" ] && [[ "$( ls --color=never ${path}/bin )" ]]; then
        export PATH="${PATH}:${path}/bin"
    fi
done

Prepend some PATHs for common environment managers and subdirectories in your bin directory. Specifically Ruby rbenv, Rust cargo and paths such as ~/bin/bitcoin/bin.

if [[ "$( which python2.6 2>/dev/null )" ]] && [[ "$( which python2.7 2>/dev/null )" ]]; then
    alias python="python2.7"
fi

Set python alias to python2.7 when python2.6 is installed, to using python2.6.

if [ -f "$HOME/.hosts" ]; then export HOSTALIASES="$HOME/.hosts"; fi

I haven’t used this but is a nice idea, a local hosts file which compliments /etc/hosts

for cmd_orig in ls ll la l dir vdir grep fgrep egrep; do
    cmd_alias="$( alias $cmd_orig 2>/dev/null | awk -F\' '{ print $2 }' )"

    if [ "$cmd_alias" ]; then cmd="$cmd_alias"; else cmd="$cmd_orig"; fi

    if [ ! "$( alias $cmd_orig 2>/dev/null | grep '\-\-color=auto' )" ]; then
        alias "$cmd_orig"="$cmd --color=auto"
    fi
done

This loop adds the color flag to supported programs, it checks whether there is an alias already set and appends to it.

HexTo256() {
    # https://unix.stackexchange.com/a/269085/264635
    local hex=${1#"#"}
    local r=$( printf '0x%0.2s' "$hex" )
    local g=$( printf '0x%0.2s' ${hex#??} )
    local b=$( printf '0x%0.2s' ${hex#????} )
    printf '%03d' "$(( (r<75?0:(r-35)/40)*6*6 +
                       (g<75?0:(g-35)/40)*6   +
                       (b<75?0:(b-35)/40)     + 16 ))"
}

bashColor() {
    case "$1" in
        # f=foreground, b=background, *b=*bold
        f) g="38";;
        fb) g="38"; b="1";;
        b) g="48";;
        bb) g="48"; b="1";;
        *) b="";;
    esac
    echo "\[\e[${b};${g};5;$( HexTo256 ${2} )m\]"
}

The HexTo256() function is wizardry; taking a hex colour value it returns the bash-compatible 256-colors code to use for text highlighting. I abstracted the function behind bashColor() which properly encapsulates the colors in terminal escape characters and sets the colour to either the foreground or background with bold or regular text depending on the arguments.

Example: bashColor(fb ffffff) -> foreground color, bold text, white

fgRe="\[\e[0m\]\[\e[22m\]\[\e[25m\]\[\e[27m\]\[\e[28m\]"

fgDim="\[\e[2m\]"     # Set Dim text
fgBlink="\[\e[5m\]"   # Set Blink
fgInv="\[\e[7m\]"     # Set Invert colours
fgHidden="\[\e[8m\]"  # Set Hide
reCol="\[\e[0m\]"     # Reset colour
reDim="\[\e[22m\]"    # Reset dim
reBlink="\[\e[25m\]"  # Reset blink
reInv="\[\e[27m\]"    # Reset invert
reHidden="\[\e[28m\]" # Reset hidden

As you can see, terminal escape codes are a pain to visual parse and any mistakes cause very strange problems with your terminal. Here are the non-colour escape codes for text maniplation as-well as a reset variable which includes the reset for every type of text manipulation, to keep things simple.

usrcolour="$(bashColor f b5db3b)"
syscolor="$(bashColor f a43cff)"

Some default colours with the functions from earlier!

src_bashrc=true
bashrc_end=$(( $(date +%s) - ${bashrc_start} ))

It’s important to leave behind an indicator that the bashrc has run, it avoids cyclical sourcing in the event you also have a .bash_profile, et al.

if [ -f "${HOME}/.my_profile" ]; then . "${HOME}/.my_profile"; fi

This is where I break with convention a little, I prefer to have my environment personalisations in a non-standard file so I know it won’t overwrite anything when I copy it over, or be overwritten later.

if [ -f "$HOME/.motd" ] && [ ! -f "$HOME/.hushlogin" ] \
&& [ -z "$MOTD_ENABLE" ] || [ "$MOTD_ENABLE" == true ]; then
    . $HOME/.motd
fi

Another place I go a bit off-trail is with .motd, again I prefer the customisations to be outside of .bashrc as to not clutter it and keep it minimial enough that start-up isn’t excessively slowed. I made my .motd abortable by catching the ^C signal to skip execution if it is hanging.

PS1_body="\${PREPROMPT_ENV}${fgRe}${syscolor}\h\${PROMPT_ENV}${fgRe}${OPER_PROMPT}${fgRe}:\W"
PS1_tail=">${fgRe} "
PS1="${PS1_body}${PS1_tail}"

Here we assemble the list of variables which build our prompt, this gets combined into a single $PS1 variable later but is separated now so extra variables can be slotted inbetween if required. Alternatively I could put escaped variables into $PS1 then reassign it later once the variables are set, but this is harder to follow as order of operations becomes more important.

BoincPrompt() {
    exitc="$1"

    [ "$exitc" == "1" ] && local exitcolor="$(bashColor f ffa200)" || \
    [ "$exitc" != "0" ] && local exitcolor="$(bashColor f 1fe100)" || \
    local exitcolor=""
 
    [ "$exitc" != "0" ] && exitc="<${exitc}" || exitc=""

    [ "$UID" == "0" ] && usrcolor="$(bashColor f ff2d00)" || usrcolor="$usrcolour"

    [ "$VIRTUAL_ENV" ] && PROMPT_ENV="($( basename ${VIRTUAL_ENV} ))" || PROMPT_ENV=""
    [ "$CHROOT_NAME" ] && PREPROMPT_ENV="(${CHROOT_NAME})" || PREPROMPT_ENV=""

    command -v __git_ps1 >/dev/null && git_PS1="$( __git_ps1 '(%s)' )"

    export GIT_PS1_SHOWUPSTREAM="auto"

    PS1="${usrcolor}${PS1_body}${git_PS1}${exitcolor}${exitc}${PS1_tail}"
}
export PROMPT_COMMAND="BoincPrompt \$? 2>/dev/null || echo"

Lastly, modifying the prompt!

The BoincPrompt() function is run everytime your press Enter in your terminal and a new prompt is printed.

BoincPrompt() is invoked via the $PROMPT_COMMAND variable, where I pass the return-code of the previous command to the function as-well as redirect stderr to null for when the function is undefined or unavailable but is still being called by $PROMPT_COMMAND - this happens in some chroots.

The function takes the return code and colours it depending on whether it is non-zero or 1 - The coloured value is appended toe the prompt later.

A check exists to color the username red if bash is running as root, I don’t customize root’s bash environment typically, so this is there to catch edge cases.

Using some of the variables set earlier I also print the name of the VirtualEnv or chroot so I can’t forget the context for a given prompt.

I capture the __git_ps1 output to show the current git branch.

Lastly all these variables are concatenated to for the complete prompt!