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).
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.
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.
~/.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.
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:
echo '# START bash completion -- do not remove this line
bash=2.05b; bmajor=; bminor=
if [ ": \u@\h; " ] && [ -eq 2 ] && [ '\''>'\'' 04 ] && [ -f /etc/bash_completion ]; then # interactive shell
# Source completion code
. /etc/bash_completion
fi
unset bash bmajor bminor
# END bash completion -- do not remove this line
' >bash_completion.safe
Now, open a new Bash shell (or run source ~/.bashrc) and try to tab-complete stuff!
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).
export REALNAME=`grep $USER /etc/passwd | awk -F: '{ print $5 }' 2>/dev/null`
to:
export REALNAME=`getent passwd $USER | awk -F: '{ print $5 }' 2>/dev/null`
Thanks to Jürgen Hötzel for suggesting this fix!
# 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 ''
from .bashrc to .bash_profile, as terminal settings
do not apply to non-interactive shells (and break scp and its ilk).
# 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 ''
# 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 [ ${TERM} = "xterm" ]; then
PROMPT_COMMAND='echo -ne "\033]0;${USER}@${HOSTNAME}: ${PWD}\007"'
export PROMPT_COMMAND
fi
to:
# 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
I found this little gem in /etc/skel/.bashrc on a
Gentoo box.
if [ ${TERM} = "xterm" ]; then
PROMPT_COMMAND='echo -ne "\033]0;${USER}@${HOSTNAME}: ${PWD}\007"'
export PROMPT_COMMAND
fi
Thanks to Joe Zarick for giving me an example from his
.bashrc.
# Put some oft-used directories in our CDPATH. The result of this is
# that typing 'cd linux' would take you to /usr/src/linux, 'cd docs'
# would take you to ~/docs (provided such a directory exists, of course),
# 'cd sys-kernel' would take you to /usr/portage/sys-kernel, and so on.
export CDPATH=.:~:~/projects:/usr/src
# Version: 1.1.0
paths="$HOME/bin /sbin /bin /usr/local/sbin /usr/local/bin /opt/sfw/bin /usr/sbin /usr/bin $ORACLE_HOME/bin /usr/ccs/bin"to
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"
as it is easier to see what is going on here. Thanks to Adrian
Filipi-Martin for this suggestion.
for i in $paths; do echo $PATH | egrep $i'(\:|$)' >/dev/null 2>&1 || if [ -d $$i ]; then PATH=$PATH:$i; fi doneto
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
as I think it makes things cleaner. Oh yeah, and it works,
whereas the old version did not. :-P
which $my_editor >/dev/null 2>&1 && export EDITOR=$my_editor which $my_pager >/dev/null 2>&1 && export PAGER=$my_pagerto
type $my_editor >/dev/null 2>&1 && export EDITOR=$my_editor type $my_pager >/dev/null 2>&1 && export PAGER=$my_pagerbecause, as Adrian Filipi-Martin pointed out, type is a shell built-in, whereas which is not. Therefore, using type avoids the chicken-and-the-egg problem caused when which itself is not in your path.
# 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
# Do *not* append the following to our history: consecutive duplicate # commands, ls, bg and fg, and exit export HISTIGNORE="&:ls:[bf]g:exit"to encompass Adrian Filipi-Martin's:
# 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
# This option mostly keeps you from needing to run "hash -r" when you # modify directories in your path shopt -s checkhash
# 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
source /etc/profile.d/bash-completionto:
bc_safe=/etc/profile.d/bash-completion if [ -f $bc_safe ]; then source $bc_safe; fito stop Bash from bitching (and thus breaking non-interactive shells) about not being able to find /etc/bash-completion on systems where it is not installed.
# Set umask umask 0023
# Set REALNAME from /etc/passwd
export REALNAME=`grep $USER /etc/passwd | awk -F: '{ print $5 }' 2>/dev/null`
which vim >/dev/null 2>&1 && export EDITOR=$my_editor which less >/dev/null 2>&1 && export EDITOR=$my_pagerto
which $my_editor >/dev/null 2>&1 && export EDITOR=$my_editor which $my_pager >/dev/null 2>&1 && export EDITOR=$my_pagerThanks to Todd Williams for noticing my snafu.
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.
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