Talkback: Discuss this article with peers
Many tutorials and introductions to bash talk about using aliases. Unfortunately most of them don't cover functions. This is a real loss, because functions offer many values that aliases don't.
Aliases are simple string substitutions. The shell looks at the first word of a command and compares it against it's current list of aliases. Further, if the last character of an alias is a space, it looks at the next word as well. For example:
$ alias 1='echo '
$ alias 2='this is an alias'
$ 1 2
this is an alias
$
Aliases don't allow for control-flow, command line arguments, or additional trickery that makes the command line so useful. Additionally, the rules surrounding alias expansion are a bit tricky, enough so that the bash(1) manpage recommends "[t]o be safe, always put alias definitions on a separate line, and do not use alias in compound commands".
Functions are really scripts run in the current context of the shell. (This bit of techspeak means that a second shell is not forked to run the function, it is run within the current shell.) Functions really are full scripts in and of themselves, and allow all the flexibility and capability that entails.
You can create a functions a couple of different ways. You can just enter it into a file and source the file with the '.' command (either from the command line or in your start-up scripts). You can also just enter the function into at the command line. A function is only available in a session where it has been made available through one of these methods (or has inherited it from its parent shell).
To create a function from the command line you would do something like this:
$ gla() {
> ls -la | grep $1
> }
This is a pretty simple function, and could be implemented as an alias as well. (There are reasons you might not want to do this, we'll get to those later.) As written, it does a long listing of the local directory and greps for any matches for the first argument. You could make it more interesting by punching it through awk to find any matching files that are larger than 1024 bytes. This would look like:
$ gla() {
> ls -la | grep $1 | awk ' { if ( $5 > 1024 ) print $0 } '
> }
You can't do this as an alias, you're no longer just replacing gla with the 'ls -la | grep'. Since its written as a function, there is no problem using the $1 (referring to the first argument to gla) anywhere in the body of your commands.
For a larger example (well, okay it's a fair amount larger), suppose you are working on two projects with two different CVS repositories. You might want to be able to write a function that allows you to set appropriate CVSROOT and CVS_ROOT variables, or clear any values from these variables if the argument unset is given. It would also be nice if it would run 'cvs update' for you if given the argument 'update'. With aliases, you could approximate this, but only by running multiple aliases from the command line. Using functions, you could create a text file containing the following: (text version)
setcvs() {
export done="no"
if [ "$1" = "unset" ]
# we want to clear all of the variables
then
echo -n "Clearing cvs related variables: "
export CVSROOT=""
export CVS_RSH=""
export done="yes"
echo "done"
fi
if ( pwd | grep projects/reporting > /dev/null && \
[ "$done" != "yes" ] )
# if we're in the reporting area, and we're not already done
then
echo -n "Setting up cvs for reporting project: "
export CVSROOT="issdata:/usr/local/cvs/"
export CVS_RSH="ssh"
export done="yes"
echo "done"
fi
if ( pwd | grep projects/nightly > /dev/null && \
[ "$done" != "yes" ] )
# if we're in the nightly area, and we're not already done
then
echo -n "Setting up cvs for nightly project: "
export CVSROOT="/home/cvs/"
export done="yes"
echo "done"
fi
if [ "$1" = "update" ]
# we want to update the current tree from the cvs server after
# setting up the right variables
then
if [ -z "$CVSROOT" ]
# if there is a zero length $CVSROOT (it has already been
# cleared or was never set) throw an error and do nothing
then
echo "no cvs variables set ... check your cwd and try again"
elif [ -n "$CVSROOT" ]
# if there is a $CVSROOT try and do the update
then
echo "updating local tree"
cvs -q update
echo "done"
fi
fi
}
Then you could enable the function and use it like this:
$ . ~/scripts/setcvs
$ cd
$ pwd
/home/a257455
$ setcvs unset
Clearing cvs related variables: done
$ echo $CVSROOT
$ echo $CVS_RSH
$ cd projects/reporting/htdocs/
$ setcvs
Setting up cvs for reporting project: done
$ echo $CVSROOT
issdata:/usr/local/cvs/
$ echo $CVS_RSH
ssh
$ cd ../../nightly/
$ setcvs
Setting up cvs for nightly project: done
$ setcvs update
Setting up cvs for nightly project: done
updating local tree
done
$ cd
$ setcvs unset
Clearing cvs related variables: done
$ setcvs update
no cvs variables set ... check your cwd and try again
$
Functions can do a lot more than aliases, the function above shows a little bit of flow control, some error handling, and the ability to use variables. Certainly it could be improved, but it shows the point. Another big win is that functions can be re-used in scripts, while aliases can't. For example, because the function above is saved in a file called '~/scripts/setcvs' you can write a script like:
#!/bin/bash
# a sample script
# first source the functions
. ~/scripts/setcvs
# now go to the project directories and update them from cvs
cd ~/projects/reporting/htdocs
setcvs update
cd -
cd ~/projects/nightly
setcvs update
# now go back to where you were and unset any cvs variables.
cd -
setcvs unset
Aliases are very useful little things, but I hope that after this introduction, you find functions at least as interesting (and probably even more useful). A final caveat to both aliases and functions is that you should never replace a standard command with an alias or a function. It is too easy to really hurt yourself by trying to execute your alias when it doesn't exist. Imagine the difference between:
$ alias rm='rm -i'
$ cd ~/scratch
$ rm * # here the rm alias catches you and interactively
# deletes the contents of your current directory
$ su -
# cd /tmp
# rm # here the rm alias no longer exists, and you whack
# a bunch of stuff out of /tmp
Happy hacking!
-pate