guest@6admin.io:~$

Bash: customizing the .bashrc file

On our favorite systems, the interactive bash shell, its prompt and its coloring, are configured in these files:

  • /etc/profile
  • /etc/bash.bashrc
  • /root/.bashrc
  • /home/user/.bashrc

Here we only touch the last three. We start with the color of the username in the prompt, green for users, red for the root account, because a root prompt that does not announce itself is a root prompt you eventually type something stupid into:

# Set color of username in prompt
if [ "`id -u`" -eq 0 ];then prompt_color='\[\033[01;31m\]';else prompt_color='\[\033[01;32m\]';fi

Here is the list of color codes: Black 00;30, Red 00;31, Green 00;32, Brown 00;33, Blue 00;34, Purple 00;35, Cyan 00;36, Light gray 00;37, Gray 01;30, Pink red 01;31, Light green 01;32, Light brown 01;33, Light blue 01;34, Light purple 01;35, Light cyan 01;36, White 01;37.

The prompt itself shows the time (\t) rather than the current directory (\w), which took up a huge amount of space for nothing and it prefixes each line with the return code of the previous command, the small touch I no longer do without.

The .bashrc, section by section

# If not running interactively, don't do anything
[ -z "$PS1" ] && return 0;

export PATH=/sbin:/usr/sbin:$PATH;
export EDITOR=vi
export PAGER=most

# History management, timestamped and colored
CYAN=$(echo -e '\e[0;36m')
NORMAL=$(echo -e '\e[0m')
export HISTCONTROL=ignoredups;                  # no duplicates
export HISTTIMEFORMAT="${CYAN}[ %d/%m/%Y %H:%M:%S ]${NORMAL} "
export HISTFILESIZE=50000
export HISTSIZE=50000
PROMPT_COMMAND="history -a";                    # history written after each command

shopt -s checkwinsize;                          # recompute LINES/COLUMNS after each command
[ -x /usr/bin/lesspipe ] && eval "$(lesspipe)";
if [ "$TERM" != "dumb" ]; then eval "`dircolors -b`"; fi

The lines that are worth gold in this block: HISTTIMEFORMAT, the timestamped history, here colored cyan, the only way to know who typed what and when on a shared server; PROMPT_COMMAND="history -a", which writes the history after each command rather than on close, so a terminal that crashes no longer loses anything; and checkwinsize, so full-screen editors stop drawing themselves in 80 columns in a terminal that is 200 wide. The export PAGER=most is a taste of the era, most colorizes man pages and keeps several files open, where less stayed plain.

Then comes the colored prompt, the xterm window title and above all the PROMPT_COMMAND that shows the return code when it is non-zero:

# cd to a variable, and typo correction on cd
shopt -s cdable_vars;
shopt -s cdspell;

# show the previous command's return code when it is non-zero
export PROMPT_COMMAND='( x=$? ; let x!=0 && echo "sHell returned : $x" )';

A confession in passing: PROMPT_COMMAND is redefined several times in this file and the last one wins. We stack the history, the xterm title and the return code as if the three coexisted, when they should be folded into a single function. I leave it as is because it is exactly the kind of clumsiness you carry for years without seeing it.

And finally, the greeting, because a shell that does not say hello is a sad shell:

echo -e "Hello `whoami` !\tWe're in `date '+%Y:%m:%d'`\nI am Locutus of Borg. Resistance is futile."
who

The aliases, the section that never dies

First the safety rails and the ls family, which do not change from one decade to the next:

alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
alias ls='ls --color=auto'
alias ll='ls -l'
alias la='ls -A'
alias l='ls -CF'
alias d='ls -d */'
alias grep='grep --color=auto'
alias vi='vim'

Then the :q, the reflex of someone who lives inside vim and forgets they are in a shell. Detecting whether the current shell descends from a vim, in 2009, was done without a dedicated variable, with ps and awk:

[[ $(ps -ef | awk "\$2 == $(ps -ef | awk "\$2 == $$ {print \$3}") {print \$8}" | grep vi) ]] \
    && alias :q='exit' || alias :q='echo "Not in vi{m} !"';

In a :sh launched from vim, :q exits; in a normal shell, it politely answers “Not in vi{m} !”. It is convoluted, it spawns two ps, but it works and back then we thought it was very clever.

Finally the collection I am not a little proud of, the namespaced aliases, a way to get yourself a prefix pseudo-completion well before bash completion was everywhere:

alias dir.change='cd'
alias dir.new='mkdir'
alias dir.list='ls'
alias file.move='mv'
alias file.grep='grep'
alias file.stat='stat'
alias alias.edit='$EDITOR ~/.bash_aliases'
alias alias.reload='. ~/.bash_aliases'
alias apt.install='apt-get install'
alias apt.search='apt-cache search'
alias apt.moo='apt-get moo'

The idea is that you type dir. then Tab and the shell offers everything to do with directories, file. for files, apt. for package management. It is poor man’s object-oriented programming applied to aliases and it stayed one of my quirks. You also find a few private jokes in there, a nan that shouts “I am not a number !” and the apt.moo that never served any useful purpose.