Making Bash Do Your Work for You

A guide to allowing Bash to rock your world: version 1.1.5

This document owes its inspiration and indeed much of its contents to Ian Macdonald's excellent Working more productively with bash 2.x (and thanks to Curtis Smith for posting a link to it to the COLUG mailing list).

Three easy steps to work domination:

  1. Construct a powerful .bashrc
  2. Tweak your ~/.inputrc
  3. Install bash-completion

This is a living document. Check back periodically to see if a newer version is available. Also, view the changelog to see what has changed and why.

Construct a powerful .bashrc

Once you have a powerful .bashrc file sitting in your home directory, your work is your oyster. (Whatever that means.) The reason that your happiness with Unix relies primarily on the contents of your .bashrc file is that this is the file that Bash executes for all interactive shells (this is a blatant simplification and a falsehood to boot; see [1] for the truth).

The first step is to write a trivial .bash_profile that exists only to source our .bashrc (if you want to know why, see [1]) and make a few changes to our terminal emulation settings. If there is already a .bash_profile in your home directory (and this is most likely the case), don't worry. Create an empty .bash_profile: echo >~/.bash_profile and open it in your favourite editor. Enter the following, then save and exit.

# Put anything special that should happen for *only* login shells here

# Turn off TTY "start" and "stop" commands (they default to C-q and C-s,
# respectively, but Bash uses C-s to do a forward history search)
stty start ''
stty stop  ''

# Source our .bashrc
if [ -f .bashrc ]; then source .bashrc; fi
      

Now, we will create a new .bashrc: echo >~/.bashrc and open it in your favourite editor. Enter the following, then save and exit. Note that the lines coloured red may need to be edited. Conversely, any line not coloured red *should not* be changed!

# http://www.jmglov.net/unix/bash.html
#
# Version: 1.1.5

# This script *must not* generate any output, as this will break non-
# interactive shells!

# Source .bashrc for non-interactive Bash shells
export BASH_ENV=~/.bashrc

# Add some useful directories to our PATH if necessary. Add all the paths
# you want to the $paths variable
paths="${HOME}/bin"
paths="${paths} /sbin"
paths="${paths} /bin"
paths="${paths} /usr/local/sbin"
paths="${paths} /usr/local/bin"
paths="${paths} /usr/pkg/bin"
paths="${paths} /opt/sfw/bin"
paths="${paths} /usr/sbin"
paths="${paths} /usr/bin"
paths="${paths} $ORACLE_HOME/bin"
paths="${paths} /usr/ccs/bin"

for i in $paths; do
  if `echo $PATH | egrep $i'(\:|$)' >/dev/null 2>&1`; then
    continue
  fi
  if [ -d $i ]; then
    PATH=$PATH:$i 
  fi
done

PATH=`echo $PATH | sed -e 's/^\://' -e 's/\:\:/:/g'`

export PATH

# If you find yourself hampered by stoopid aliases, uncomment this
#unalias -a

# Default to the worst-case editor and pager
export EDITOR=vi
export PAGER=more

# If we have a better editor or pager, use it (change these as you see fit)
my_editor=vim
my_pager=less

type $my_editor >/dev/null 2>&1 && export EDITOR=$my_editor
type $my_pager >/dev/null 2>&1 && export PAGER=$my_pager

# Proxy settings (uncomment these and add your proxy if needed)
#export http_proxy='http://proxyhost.somewhere.com:8080'
#export ftp_proxy='http://proxyhost.somewhere.com:8080'
#export RSYNC_PROXY='proxyhost.somewhere.com:8080'

# Prompt should look like   : juser@somewhere;
export PS1=': \u@\h; '
export PS2=': ; '

# Grab our REALNAME from nsswitch
export REALNAME=`getent passwd $USER | awk -F: '{ print $5 }' 2>/dev/null`

# Standard Oracle installation directory
if [ -d /usr/local/oracle ]; then
  export ORACLE_HOME=/usr/local/oracle
fi

# If you use CVS, uncomment these and change the CVSROOT variable to
# reflect your CVS repository
#export CVS_RSH=ssh
#export CVSROOT=cvshost:/path/to/cvsroot

# If you use Subversion, uncomment these and change them to reflect your
# Subversion protocol and root. The example below assumes that your
# Subversion root is a local directory, /var/svnroot. For a remote root,
# you might set SVNPROTO to "svn+ssh://" and SVNROOT to
# "svn.mydomain.tld/var/svnroot".
#export SVNPROTO=file://
#export SVNROOT=/var/svnroot

# If you are a developer, uncomment the following to make sure core dumps
# will be generated when they should be
#ulimit -c unlimited

# Let me know about new mail in these folders
export MAILPATH=/var/spool/mail/$USER"?You've got mail"'!'

# xterm title settings
case $TERM in

  xterm*|rxvt|Eterm|eterm)

    PROMPT_COMMAND='echo -ne "\033]0;${USER}@${HOSTNAME}: ${PWD}\007"'
    export PROMPT_COMMAND
    ;;

  screen)

    PROMPT_COMMAND='echo -ne "\033_${USER}@${HOSTNAME%%.*}:${PWD/$HOME/~}\033\\"'
    export PROMPT_COMMAND
    ;;

esac

# Correct minor spelling errors in cd commands
shopt -s cdspell

# Do *not* append the following to our history: consecutive duplicate
# commands, ls, bg and fg, and exit
# Don't keep useless history commands. Note the last pattern is to not
# keep dangerous commands in the history file.  Who really needs to
# repeat the shutdown(8) command accidentally from your command
# history?
HISTIGNORE='\&:fg:bg:ls:pwd:cd ..:cd ~-:cd -:cd:jobs:set -x:ls -l:ls -l'
HISTIGNORE=${HISTIGNORE}':%1:%2:popd:top:pine:mutt:shutdown*'
export HISTIGNORE

# Save multi-line commands in history as single line
shopt -s cmdhist

# Disk is cheap. Memory is cheap. My memory isn't! Keep a lot of
# history by default. 10K lines seems to go back about 6 months, and
# captures all of the wacky one-off shell scripts that I might want
# again later.
export HISTSIZE=10000
export HISTFILESIZE=${HISTSIZE}

# Reduce redundancy in the history file
export HISTCONTROL=ignoredups

# Do not delete your precious history file before writing the new one
shopt -s histappend

# This is useful for embedded newlines in commands and quoted arguments
shopt -s lithist

# Enable egrep-style pattern matching
shopt -s extglob

# This option mostly keeps you from needing to run "hash -r" when you
# modify directories in your path
shopt -s checkhash

# Set umask
umask 0023

# Uncomment this if you are a vi user and hate Bash's Emacs keybindings
#set -o vi

# Enable programmable completion. Set $bc_safe to the full path of the
# wrapper script distributed with the bash-completion package
# that checks to make sure your version of Bash is new enough before
# enabling programmable completion (the default will probably suffice).
bc_safe=/etc/profile.d/bash-completion
if [ -f $bc_safe ]; then source $bc_safe; fi

# If you want group- and host-specific profiles, uncomment this
#if [ -f .bashrc_net ]; then source .bashrc_net; fi
      

If you want to have group- and/or host-specific .bashrc files (useful if you are on a network with a network-mounted home directory), uncomment the

#if [ -f .bashrc_net ]; then source .bashrc_net; fi
      
line above and see [2], below.

Tweak your ~/.inputrc

~/.inputrc is the user-specific configuration file for the GNU Readline library, which is used by Bash (and many other programs) to allow command-line editing and history browsing and so on. To tell Readline to show all possibilities when tab-completion is ambiguous (e.g. you type ls /usr/<TAB>) (the normal behaviour is to require another press of the Tab key to display all possibilities), run this command: echo 'set show-all-if-ambiguous on' >>~/.inputrc

If you are a vi user and would rather have Bash act like vi instead of Emacs, try this: echo -e 'set editing-mode vi\nset keymap vi' >>~/.inputrc And make sure that you uncommented the

#set -o vi
      
line in your .bashrc, above.

Install bash-completion

Tab-completion is, in my opinion, one of the most useful features of Bash. Luckily for me and others like me (probably you, if you care enough to read this far), there exists a package called bash-completion which provides programmable completion. You should think of this as completion on steroids. Emacs-style everything-and-the-kitchen-sink completion. For example, wouldn't it be great if tab-completion worked on hostnames in ssh or ping or traceroute (or whatever network-related command you can dream up)? Or on those pesky cvs commands? Or process IDs or names in kill and killall commands? Well, it does now!

To install bash-completion, follow these steps:

Now, open a new Bash shell (or run source ~/.bashrc) and try to tab-complete stuff!

Help!

Everything in this document has been tested on a Gentoo Linux system with GNU bash, version 2.05b.0 and a Solaris 8 Intel box running GNU bash, version 2.03.0(1)-release. If you find something deficient, broken, or just plain confusing, I want to fix it. Please email me at bash THE_@_SYMBOL jmglov.net (make sure to type @ instead of THE_@_SYMBOL).

Changelog

1.1.5

1.1.4

1.1.3

1.1.2

1.1.1

1.1.0

1.0.2

1.0.1

Footnotes

[1] Interactive shells

Actually, .bashrc is only executed by Bash when it is invoked as an interactive, non-login shell (e.g. when you su someuser). Another file, .bash_profile, is executed for interactive login shells (e.g. when you login, open a new xterm or rxvt, su - someuser, etc). Which is why we will construct a simple .bash_profile that just sources .bashrc.

Bash can also be invoked non-interactively (e.g. when you run ssh somehost ls /some/path or the like). In this case, Bash consults the $BASH_ENV environment variable, and if it contains a filename, sources that file. Which is why we will add a export BASH_ENV=~/.bashrc line to our .bashrc file.

[2] Group- and host-specific .bashrc

If you are on a heterogeneous network where you regularly have to login to different machines to do different things, you may find it useful and desirable to setup your Bash environment differently for each host, or for groups of similar machines. As long as you have a network-mounted homespace, this is easy. Simply uncomment the

#if [ -f .bashrc_net ]; then source .bashrc_net; fi
      
line in the .bashrc section, above, and then create a new .bashrc_net file: echo >~/.bashrc_net and open it in your favourite editor. Enter the following, then save and exit.

# List your groups, delimited by whitespace
groups=""

for group in $groups; do
  if [ -e ~/.bashrc.$group.$HOSTNAME ]; then
    source ~/.bashrc.$group
  fi
  if [ -e ~/.bashrc.$HOSTNAME ]; then
    source ~/.bashrc.$HOSTNAME
  fi
done
      

Now, for each group, create the file ~/.bashrc.GROUP, where GROUP is the name of the group. For example, you might want to create ~/.bashrc.solaris, ~/.bashrc.redhat, and ~/.bashrc.gentoo so that machines running each OS / distribution could do things a little differently. Remember, the group-specific .bashrc is sourced at the end of the standard .bashrc, so anything you put in it will override your standard .bashrc An example .bashrc.solaris might look like this:

export PATH=/opt/sfw/bin:/usr/local/bin:$PATH
alias awk=gawk grep=ggrep sed=gsed tar=gtar
      

Now, touch an empty file for each host that you want in the group. You might think that symlinks are a better way of doing this. You would be right, were it not for the hideous machinations of NFS. Don't trust symlinks to work right on NFS-mounted filesystems! Note that the group line in the .bashrc_net (above) checks for the existence of a .bashrc.group.host file (where group and host are the name of the group and host, respectively, but sources the .bashrc.group file.

For example, if you had three hosts running Solaris, opie, andy, and auntbee, you would want to do this:

for i in "andy antbee opie"; do
  touch ~/.bashrc.solaris.$i
done

Likewise, for each host for which you want a specific Bash environment, create a ~/.bashrc.HOST, where HOST is the name of the group. For example, if acroread was installed only on the opie box, you might create a .bashrc.opie file with the following contents:

export PATH=/opt/bin:$PATH
export ACROREAD_QUICKLOAD=1
      

Last modified: Sun Mar 6 14:22:20 EST 2005