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
tovim
whenvim
is installed - Alias
sctl
tosystemctl
ifsystemctl
is installed andsctl
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!