CONTENTS

Chapter 29. Custom Commands

29.1 Creating Custom Commands

—JP and SJC

29.2 Introduction to Shell Aliases

All shells except the original Bourne shell have an "alias" facility that lets you define abbreviations for commands.

The simplest C shell aliases, which are similar to the alias facility in newer Bourne-type shells, are simply a short name for a command and, often, command options or arguments too. The C shell's aliases can get very complicated. Section 29.3 describes how a C shell alias can use arguments from its command line as it's invoked.

As we've said, aliases in Bourne-type shells (bash, zsh, and ksh) are simpler. Section 29.4 covers some of the differences between those shells and the C shells. Still, the ideas for custom C shell commands are useful in any kind of shell, and if you can't write something in a simple Bourne-type alias, you almost certainly can do it in a shell function (Section 29.11).

You can define aliases from the command line, for use in just your current shell. Any aliases you define can also be placed in your shell setup file (Section 3.3), so they'll be available whenever you're using your shell.

Note that aliases are not passed to subprocesses (Section 3.3), so putting them in a setup file that's read only by login shells or top-level shells probably isn't what you want. (One exception is an alias for a command that you want to run only in a login shell. For instance, you could define an alias named X that starts your X Window System. If that alias isn't defined in subshells, you'll get a message like X: command not found if you try to start the window system from an existing window.)

A common approach is to create separate files for each shell that store your aliases (such as .bash_aliases for bash or .aliases.csh for the C shell), so that you may source them whenever you like.

Here's one last note that applies to all shells. Anytime you want a list of the aliases currently set, just type alias.

—JP, ML, DG, and SJC

29.3 C-Shell Aliases with Command-Line Arguments

It's convenient for your aliases to use command-line arguments. For example, let's think about an alias named phone:

alias phone 'cat ~/phonelist | grep -i'

After you define that alias, you could type phone smith. The shell would find the phone alias and execute it with the argument (smith) at the end (Section 29.2) this way:

cat ~/phonelist | grep -i smith

Using cat and a pipe that way is inefficient (Section 43.2). It might be more sensible to have an alias that worked like this:

grep -i name ~/phonelist

How do we do this? The C shell's history (Section 30.8) facility lets us use the notation !$ to refer to the last word in the previous command; the notation !* refers to all the arguments of the previous command. Assuming that we only want to look up aliases one at a time, we can use !$ and write our alias like this:

alias phone grep -i \!$ ~/phonelist

When we use the phone command, its final argument will be substituted into the alias. That is, when we type phone bill, the shell executes the command grep -i bill ~/phonelist.

In this example, we needed another kind of quoting. We had to put a backslash before the exclamation point to prevent the shell from replacing !$ with the previous command's last argument. That is, we don't want the shell to expand !$ when we define the alias — that's nonsense. We want the shell to insert the previous argument when we use the alias (in which case, the previous argument is just the argument for the alias itself — clear?).

But why couldn't we just use single quotes or double quotes (Section 27.12)? This isn't the right place for a full explanation, but neither single quotes nor double quotes protect the exclamation point. The backslash does (Section 27.13). If you want to be convinced, experiment with some commands like:

% echo '!!'      Print your last command
% echo '\!!'     Print !!

The first echo command shows that the shell performs history substitution (i.e., replaces !! with your previous command) in spite of the single quotes. The second example shows that the backslash can prevent the shell from interpreting ! as a special character.

Let's look at another alias. We want to pipe the output of ls -l into more. In this case, we would want all the arguments from the command line instead of merely the last argument (or the only argument). Here's the alias:

alias lm 'ls -l \!* | more'

This time, we needed both kinds of quoting: a backslash prevents the shell from interpreting the exclamation point immediately. Single quotes protect the pipe symbol and the asterisk (*). If you don't protect them both, and protect only the pipe (with a backslash), look what happens:

% alias lm ls -l \!* | more
alias: No match.

Because the backslash temporarily stops the special meaning of the !, the shell next tries to find filenames that match the wildcard (Section 1.13) pattern !*. That fails (except in the unusual case when you have a file in the current directory whose name starts with a !).

Here's a good general rule for quoting aliases. Unless you're trying to do something special with an alias and you understand quoting well, put single quotes (') around the whole definition and put a backslash before every exclamation point (\!).

If you want to pick one argument from the command line, use \!:n, where n is the number of the argument. Here's a sample alias. It uses cat (Section 12.2) to add a header file to the file named in the first argument, then writes them both into the file named in the second argument:

~ Section 31.11

alias addhead 'cat ~/txt/header \!:1 > \!:2'

This alias has two arguments: the file to which you want to add a header and the output file. When you type:

% addhead foo bar

the C shell substitutes the filename foo for \!:1, and the filename bar for \!:2, executing the command:

cat ~/txt/header foo > bar

Finally, if you need to append fixed text strings to these arguments, you need to separate the argument text from the fixed text. For instance, here's an alias that tells the Netscape browser to go to a URL http://info/proj23/xxx1.html, where xxx is a word like report, summary, etc., that you're typing on the command line (as an argument to the alias). For instance, to go to the page http://info/proj23/report1.html, you'd type:

% proj report

The first alias below shows the wrong way to do this. The second one shows how to quote the argument in curly braces ({}) so the shell doesn't think the 1 after the argument is part of the number (giving you argument 11 instead of what you want: argument 1 with the digit 1 after it):

alias proj 'netscape -remote "openURL(http://info/proj23/\!:11.html)"'   ...wrong
alias proj 'netscape -remote "openURL(http://info/proj23/\!{:1}1.html)"' ...right

If you haven't seen this netscape -remote technique, by the way, it's very handy. It sends a message to an already-open Netscape browser. You can use it from a command line (shell prompt) or by defining a button or menu item on your window system desktop. Recent Unix versions of Mozilla have also begun to support this API, as well. On the Macintosh, remote control is supported via Apple Events, but not from the command line as of this writing.

—ML, JP, and SJC

29.4 Setting and Unsetting Bourne-Type Aliases

A lot of what we said about aliases in Section 29.2 applies to the Korn shell (ksh), zsh, and bash. This article, along with Section 29.5 and Section 29.6, have an overview of what's different.

One thing that's different from C shells is the syntax of the alias command, which is:

$ alias  name = definition 

That is, you need an equal sign (no spaces) between the name and the definition. A good guideline is to use single quotes (') around the definition unless you're doing something specialized and you understand how quoting (Section 27.12) works in aliases.

You can't put arguments inside an alias as the C shell's \! operator (Section 29.3) does. To do that, use a shell function (Section 29.11).

As in the C shells, unalias removes an alias. To remove all aliases, use unalias -a in ksh and bash or unhash -a in zsh. alias with no arguments lists aliases that are currently defined.

bash aliases are pretty basic; this section covers them. Korn shell and zsh aliases do more.

—JP and SC

29.5 Korn-Shell Aliases

pdksh (the public domain ksh) has three types of aliases. First is the regular command alias covered in Section 29.4.

Tracked aliases keep track of the locations of external (Section 1.9) executables. The shell has a default list of commands to track (see the ksh manpage). The first time ksh searches the PATH for an executable command that's marked as a tracked alias, it saves the full path of that command. This saves the shell the trouble of performing the path search each time a command is invoked. The tracked aliases aren't reset unless one becomes invalid or you change the PATH. The command alias -t lists and creates tracked aliases. Here's an example with a newly invoked Korn shell:

$ alias -t
$ cat somefile > somewhere
$ alias -t
cat=/bin/cat
$ alias -t less
$ alias -t
cat=/bin/cat
less=/usr/bin/less

At first, there are no tracked aliases. But the cat command is marked for tracking; as soon as I use it, the shell saves its location, as the next alias -t shows. Next, I add a tracked alias for less (Section 12.3) (which isn't one of the default commands to track). The Korn shell won't track a command unless it's one of the defaults or you mark it for tracking.

The third kind of alias, directory aliases, set with alias -d, let you use a tilde abbreviation like ~dir for any directory.

— JP

29.6 zsh Aliases

zsh has the regular command alias covered in Section 29.4. zsh is compatible with the C shell in many ways, but it doesn't accept csh alias syntax without an equal sign (=) between the name and value. That's probably because, as in other Bourne-type shells, zsh allows you to set multiple aliases with one command, like this:

zsh$ alias ri='rm -i' mi='mv -i'    ...and so on

In zsh, alias -g defines a zsh global alias: a word that's expanded anywhere (as long as it isn't quoted). These are like a shell variable (Section 35.9) that doesn't need a dollar sign ($) to be expanded. Maybe you have a log file you read and edit often. You could make a global alias named log:

zsh$ alias -g log=/work/beta/p2/worklog

zsh$ less log
zsh$ cp log logtemp

Global aliases are expanded only when they stand alone and aren't quoted. So if there's a global alias dir for a directory, you cannot use emacs dir/file to refer to a file in that directory. Also, if you defined the global alias fserv for the hostname fserv.bkk.ac.uk, you could type telnet fserv — but if you type mail ed@fserv, the shell wouldn't expand it into a hostname. Named directories and shell variables work better in cases like those.

alias -m lists aliases that match a wildcard-type pattern; alias -m 'hi*' shows all alias names that start with hi (like hi, hist, and so on). This matches regular command aliases as well as global aliases. You can use -m with unalias, too, to remove all aliases matching a pattern.

— JP

29.7 Sourceable Scripts

Aliases are a powerful concept in csh. Another great capability is shell scripts (Section 1.8). Each has its strengths. An alias is just right for common sequences of commands, calling a command by a different name, and so on. Scripts are great for more flexible processing and batch processing. There are limitations to both, and I will show a way around them.

The limitation to aliases is that you are working pretty much with one command line. Consider this example, which manages various stages of changing directories, updating the prompt, and so forth:

alias pp 'set o2=$cwd; popd; set old=$o2; dir_number; record_dir pp; \\
  prompt_set; set cd_attempt=(\!*); if ($#cd_attempt > 0) cd $cd_attempt'

Now this works fine for me, and it served me well for a few years and thousands of invocations, but it's at the point where I start thinking that a script is more suited to the job. This brings me to the limitation of scripts.

Shell scripts are great for accomplishing some task that might change a file, start a program, etc. They are limited by the fact that any changes they make to shell or environment variables are not visible (Section 24.3) to the parent shell that started them. In other words, you can write some really cool script that will change directories for you if you don't touch the keyboard for five seconds, but once the script exits, you are still in the same place you started.

The answer is to combine the best of both worlds. Consider this:

alias pp 'set cd_attempt=(\!*); source ~/bin/pp_csh'

We set up a variable and source a script. The concept is this: put your command-line arguments into a variable and then source (Section 35.29) a script to accomplish something. The difference here is that because you are not starting a subshell (Section 24.4) for the script, it can do everything an alias can and more. This is much like Bourne shell functions (Section 29.11).

Some hints on using this technique:

Naming

I like to name the script that is doing all of the work after the alias, with _csh or .csh at the end of its name. I put all of the scripts in my ~/bin (Section 7.4). [Instead of names ending in .csh, I put mine in a directory named ~/.lib/csh. — JP]

Feedback

You don't want to execute the script directly. You want to source it. Here's a good first line that detects this:

#! /bin/echo sorry,try:source
Usage statement

Check the variable that you expect to see from the alias. If it isn't there, you can show a usage statement and do a goto to the end of the script:

<< Section 27.16

if ($#lg_args == 0) then
    cat << +++
usage: lg [-a][-p] pattern [command]
    -a  lists all (.dot files)
    -p  pipe resulting list into command
+++
    goto lg_end
endif
   ...
lg_end:
Alias options

You aren't limited to what an alias can do, since you are sourcing a script. You gain some flexibility here. Here's one way of handling options:

set Section 35.9

unset ls_arg
while (! $?ls_arg)
    switch ("$lg_args[1]")
        case "-a":
            set ls_arg="-a"
            shift lg_args
        case "-p":
            set use_pipe
            shift lg_args
        default:
            set ls_arg
            breaksw
    endsw
end

Have fun with this! You may find yourself tossing some old aliases and rewriting them as sourceable scripts. They're also easier to maintain.

— DS

29.8 Avoiding C-Shell Alias Loops

Section 27.9 has similar information for bash.

Here's a situation that came up on the Net a while ago. Someone wanted an exit (Section 24.4) alias that would run a ~/.exit file (Section 31.13) before leaving the shell. The obvious solution is:

alias exit "source ~/.exit; exit"

This doesn't work; when you use the exit alias, the C shell thinks that the alias is trying to execute itself. Recursive aliases aren't allowed on many shells, so the C shell prints an error message (Alias loop) and gives up.

There are many ways to break the loop. Here's the best (in my opinion):

alias exit 'source ~/.exit; ""exit'

Section 27.10 has the hairy details of what works and why. To summarize, if you need to use the alias's name within a C shell alias, you can use:

""name

Where name is the name of a built-in (Section 1.9) command or any "regular" command.

\name

Where name is the name of any "regular" command, but not a built-in command.

Tempting as this all may sound (and I have to admit, if it didn't sound a bit tempting, I wouldn't be writing this article), I can't really recommend the practice of "redefining" commands with aliases. You should leave the original commands as they are. The original author could have avoided all these problems by calling his alias quit rather than exit.

If you redefine commands with aliases and then use another account where your alias isn't defined, it's easy for things to go wrong. That's especially true for commands that do something permanent — overwriting or removing files, for example. It also can cause problems if you let someone type a command on your account and the person isn't expecting an aliased version.

Let me give one more example to show you what problems you can have. Let's say you've aliased the exit command to source a .exit file before quitting. Fair enough. But now, let's say that you're not in your login shell, that you've set ignoreeof, and that, for no apparent reason, your .exit file disappears (maybe it develops a bad block, so the system can't read it; such things happen).

Now you're stuck. If you type exit, the source command will fail, and the "real" exit command will never be executed. You can't leave the shell. Of course, if you remember what you did, you can always type unalias exit and get the original command back. Or you can type " "exit. Or finally, you could simply write the alias such that it tests for the existence of the file before trying to read it. But if you've foisted this alias on a beginner, he or she might not know that. All of a sudden, you're stuck in some shell that you apparently can't get out of.

The biggest virtue of Unix is that it's infinitely extendable. However, you aren't helping if your extensions hide the basic operations that make everything work. So — extend all you want. But when you write your extensions, give them new names. End of sermon.

— ML

29.9 How to Put if-then-else in a C-Shell Alias

The C shell's brain damage keeps you from using an if with an else in an alias. You have to use a sourceable script (Section 29.7). Or that's what I thought until I saw an article by Lloyd Zusman on comp.unix.questions in December 1987. He'd saved an earlier posting on that group (but without its author's name) that showed how. The trick: use enough backslashes (\) and the eval (Section 27.8) command.

As an example, here's an alias named C for compiling C programs. It needs the executable filename (like C prog), not the source filename (like C prog.c). If you type a filename ending in .c, it complains and quits. Else, it does the following:

Your alias doesn't need to be as complicated. But this one shows some tricks, such as putting an if inside the if, that you might want to use. Watch your quoting — remember that the shell strips off one level of quoting when you set the alias (Section 29.3) and another during the first pass of the eval. Follow this example and you'll probably be fine:

figs/www.gif Go to http://examples.oreilly.com/upt3 for more information on: if-else-alias.cs

# COMPILE AND chmod C PROGRAMS; DON'T USE .c ON END OF FILENAME.
alias C 'eval "if (\!* =~ *.c) then \\
   echo "C quitting: no .c on end of \!* please." \\
else \\
   if (-e \!*) mv \!* \!*.old \\
   echo \!*.c SENT TO cc \\
   cc -s \!*.c -o \!* \\
   if (-e \!*) chmod 311 \!* \\
endif"'

— JP

29.10 Fix Quoting in csh Aliases with makealias and quote

Getting quoting right in C shell aliases can be a real problem. Dan Bernstein wrote two aliases called makealias and quote that take care of this for you.

For example, here I use makealias to avoid having to quote ! and *:

% makealias mycat
cat `ls | sed '1,/!*/d'` | less
CTRL-d
alias mycat 'cat `ls | sed '\''1,/\!*/d'\''` | less'

I typed the makealias mycat command and the line starting with cat, then pressed CTRL-d and got back an alias definition with all the quoting done correctly.

The properly quoted alias definition is sent to the standard output. That line is what you would use to define the alias.[1]

Here are the quote and makealias aliases themselves:

figs/www.gif Go to http://examples.oreilly.com/upt3 for more information on: makealias.csh

alias quote     "/bin/sed -e 's/\\!/\\\\\!/g' \\
   -e 's/'\\\''/'\\\'\\\\\\\'\\\''/g' \\
   -e 's/^/'\''/' -e 's/"\$"/'\''/'"
alias makealias "quote | /bin/sed 's/^/alias \!:1 /' \!:2*"

Pretty gross, but they do the job. On Darwin, as on many BSD-derived systems, sed is in /usr/bin, not /bin. You may wish simply to use the command name without the explicit path, or use the explicit (but correct) path. On Linux, the script above does not work with tcsh, which handles multi-line aliases anyway.

—JIK and SJC

29.11 Shell Function Basics

Most shells have aliases (Section 29.2). Almost all Bourne-type shells have functions, which are like aliases, but richer and more flexible. Here are four examples.

29.11.1 Simple Functions: ls with Options

Let's start with two aliases from Section 29.2, changed into shell functions: The la function includes "hidden" files in ls listings. The lf function labels the names as directories, executable files, and so on.

function la ( ) { ls -a "$@"; }
function lf ( ) { ls -F "$@"; }

The spaces and the semicolon (;) are important. You don't need them on some shells, but writing functions this way (or in the multiline format in later examples) is more portable.[2] The function keyword is not needed in the original Bourne shell but is required in later versions of bash. The "$@" (Section 35.20) is replaced by any arguments (other options, or directory and filenames) you pass to the function:

$ la -l somedir              ...runs ls -a -l somedir

29.11.2 Functions with Loops: Internet Lookup

figs/www.gif Go to http://examples.oreilly.com/upt3 for more information on: mx.sh

The mx function uses dig to look up the DNS MX (mail exchanger) record for a host, then sed (Section 34.1) to pull out the "ANSWER SECTION", which has the hostname or hostnames:

for Section 35.21

function mx( ) {
# Look up mail exchanger for host(s)
for host
do
    echo "==== $host ===="
    dig "$host" mx in |
    sed -n '/^;; ANSWER SECTION:/,/^$/{
            s/^[^;].* //p
    }'
done
}

mx takes one or more hostname arguments; it runs dig and sed on each hostname. For example, the mail exchangers for oreilly.com are smtp2.oreilly.com and smtp.oreilly.com. The mail exchanger for hesketh.com is mail.hesketh.com:

$ mx oreilly.com hesketh.com
==== oreilly.com ====
smtp2.oreilly.com.
smtp.oreilly.com.
==== hesketh.com ====
mail.hesketh.com.

This example shows how to write a function with more than one line. In that style, with the ending curly brace on its own line, you don't need a semicolon after the last command. (The curly braces in the middle of the function are inside quotes, so they're passed to sed as part of its script.)

The mx function looks like a little shell program (Section 35.2). Shell functions have the same syntax as a shell script, except for the enclosing function name and curly braces. In fact, a shell function can be defined and used within a shell script (Section 35.30). But, as we've seen, it's also handy for interactive use.

29.11.3 Setting Current Shell Environment: The work Function

Like aliases, functions run in the current shell process — not in a subprocess as shell scripts do. So they can change your shell's current directory, reset shell and environment variables, and do basically anything you could do at a shell prompt. (Section 24.3 has details.)

This next function is for a group of people who are all working on a project. A directory named /work has symbolic links (Section 10.4) named for each worker — /work/ann, /work/joe, etc. — and each link points to the directory where that person is working. Each worker makes a function named work that, by default, cds to her directory and summarizes it. If the person gives an argument to the function — like work todo, for instance — the script edits the file named .todo in that directory. This setup also lets people quickly find out where others in the group are working.

figs/www.gif Go to http://examples.oreilly.com/upt3 for more information on: work.sh

Okay, I admit that I made this up as a demonstration for this article, as a way to show a lot of features in a small amount of space. Anyway, here's the function:

if Section 35.13, '...' Section 28.14, wc Section 16.6

function work ( ) {
    local status=0
    if [ $# -eq 1 -a "$1" = todo ]
    then
        ${VISUAL-vi} /work/$USER/.todo
        status=$?  # return status from editor
    elif [ $# -ne 0 ]
    then
        echo "Usage: work [todo]" 1>&2
        status=1
    else
        cd /work/$USER
        echo "You're in your work directory `pwd`."
        echo "`ls | wc -w` files to edit."
        status=0
    fi
    return $status
}

There are three points I should make about this example. First, the local command defines a shell variable named status that's local to the function — which means its value isn't available outside the function, so it's guaranteed not to conflict with variables set other places in the shell. I've also set the value to 0, but this isn't required. (In the original Korn shell, use the typeset command to set a local variable.) Second, when you run a function, the first argument you pass it is stored in $1, the second in $2, and so on (Section 35.20). Shell and environment variables set outside of the function, and nonlocal variables set within the function, are passed to and from the function. Finally, the return command returns a status (Section 35.12) to the calling shell. (Without return, the function returns the status from the last command in the function.) For a function you use interactively, like this one, you may not care about the status. But you also can use return in the middle of a function to end execution and return to the calling shell immediately.

29.11.4 Functions Calling Functions: Factorials

Okay, students, this example is "extra credit" ;-)...You can ignore this ramble unless you want some esoterica. (I'm actually not trying to waste your time. There are some useful bits of info in here about the internal workings of the shells.) Functions can call each other recursively, and local variables are passed to functions they call, but changes in a called function are not passed back to the calling function. When I say "recursion," I've gotta show the classic demonstration: a factorial function.[3]

The fac function calculates the factorial of the number passed in $1. It writes the result to standard output, for two reasons. First, doing so lets you type fac n at the command line (why you'd need to calculate a factorial very often, though, I'm not sure!). Second, if the shells' return command works like the Unix exit statuses (and I haven't checked all versions of all shells), the values are only eight bits — so it's better to return a string, which lets us handle bigger integers. I could put in more error checking, but since this is all theoretical anyway, here's the simple version of fac:

figs/www.gif Go to http://examples.oreilly.com/upt3 for more information on: fac.sh

function fac ( ) {
    if [ "$1" -gt 0 ]
    then echo $(($1 * `fac $(($1 - 1))`))
    else echo 1
    fi
}

Then you can play:

$ fac 0
1
$ fac 15
2004310016
$ fac 18
-898433024

Oops: overflow. Try zsh instead of bash or ksh; zsh built-in arithmetic seems to have more capacity:

zsh$ fac 18
6402373705728000

You can do some simple tracing by typing set -x (Section 27.15) at a shell prompt. Then the shell will display the commands it executes. (This works best in bash because it puts one + character at the left edge of each line to show each level of recursion.) You also can add some tracing code that uses a local variable, level, to store the depth of recursion. The code echoes debugging messages that show the depth of recursion of each call. Note that because the "returned value" of each function is written to its standard output, these debugging messages have to be on the standard error! (To see what happens otherwise, remove the 1>&2 operator (Section 36.16).) Here's fac with debugging code:

${..-..} Section 36.7

fac ( ) {
local level=${level-0}
echo "debug: recursion level is $((level += 1)).  Doing fac of $1" 1>&2
if [ "$1" -gt 0 ]
then echo $(($1 * `fac $(($1 - 1))`))
else echo 1
fi
echo "debug: leaving level $level." 1>&2
}

Let's run the code with tracing. Note that changes to the value of level at deeper levels doesn't affect the value at higher levels — and that level isn't set at all in the top-level shell:

$ fac 3
debug: recursion level is 1.  Doing fac of 3
debug: recursion level is 2.  Doing fac of 2
debug: recursion level is 3.  Doing fac of 1
debug: recursion level is 4.  Doing fac of 0
debug: leaving level 4.
debug: leaving level 3.
debug: leaving level 2.
6
debug: leaving level 1.
$ echo $level
$

29.11.5 Conclusion

The next two articles cover specifics about functions in particular shells, and Section 29.14 shows how to simulate functions in shells that don't have them.

Here's another overall note. Each shell has its own commands for working with functions, but in general, the typeset -f command lists the functions you've defined, and unset -f funcname deletes the definition of the function named funcname.

—JP and SJC

29.12 Shell Function Specifics

Section 27.11 introduces shell functions for all Bourne-type shells. This article covers details of functions in specific shells.

Read-only functions

A bash and ksh function can be made read-only. In ksh, that means the function can't be changed. In bash, it can't be changed or removed. To make a function read-only, use the ksh command typeset -r funcname or use read-only -f funcname in bash, where funcname is the name of the function.

A system administrator might want to set read-only functions from a system-wide setup file (Section 3.3) like /etc/profile. bash users can't unset read-only functions, though. So once a function foo has been defined, how can you define your own foo? As Section 27.9 explains, you can type command foo to use a command named foo from your search path. Or define an alias named foo; aliases are used before functions. Finally, if you'd like to redefine the function, make an alias with the same name, then make the alias invoke a function with a (usually similar) name. For instance, to override a read-only function named foo:

alias foo=_foo
function _foo( ) {
    ...your foo function...
}
Changing function environment

If a function uses an environment variable — like VISUAL or EDITOR (Section 35.5), your standard text editor — you can set the value temporarily while the function executes. The syntax is the same for functions, but only in bash and zsh. For instance, if you usually use vi, but you want to use emacs as you run the work function (Section 29.11):

$ VISUAL=emacs work todo
Resetting zsh options

There are lots of zsh options. You may want to set some of them temporarily during a shell function without needing to reset them to their previous values when the function returns to the calling shell. To make that happen, set the LOCAL_OPTIONS option (run setopt local_options) in the function body.

For instance, maybe you use setopt nounset to make your interactive shell complain if you try to expand an unset shell variable. During your func function, though, you want to use the unset option to allow unset variables. Define the function like this:

function mullog( ) {
    setopt unset local_options
        ...do whatever...
}

— JP and SJC

29.13 Propagating Shell Functions

One easy way to define shell functions that you'll have every time you start a new shell is by defining them in your shell setup files (Section 3.3). Here are two other ways.

29.13.1 Exporting bash Functions

In bash , you can export functions to other bash subshells (Section 24.4). (The original Korn shell, but not the public-domain version, supposedly does this too, but I haven't had much luck with it.) Just use the command typeset -fx funcname, where funcname is the name of the function.

How does this work? It stores the function in an environment variable (Section 35.3) whose value starts with ( ). You can see this with printenv or env (Section 35.3). For example, let's define a simple function named dir, export it, start a subshell, run the function, and look for it in the environment:

bash$ function dir( ) { ls -F "$@"; } 
bash$ typeset -fx dir           ...export the function
bash$ bash                      ...start subshell
bash$ dir                       ...the function still works
,ptbk.last            ch14.sgm    ch36.ps.gz          fmt/
,xrefs.list           ch15.ps.gz  ch36.sgm            gmatlogs/
bash$ printenv 
    ...lots of environment variables...
dir=( ) {  ls -F "$@"
}

29.13.2 FPATH Search Path

Both ksh and zsh will automatically search for functions in the PATH variable (Section 35.6). So you can put a function in a file with the same name as the function (for instance, put the function foo in a file named foo), and make the file executable (with chmod +x foo (Section 35.1)), and then the shell can find the function.

I don't like to use PATH for function-searching, though. One reason is that PATH is passed to all Unix processes — but if the process isn't a shell and it tries to execute a function file, it'll probably fail in an ugly way.[4] Also, making a file executable if you don't tell the kernel how to execute it seems to me a recipe for trouble. A better way to help the shell find functions is to set a function search path in the FPATH environment variable; it has the same syntax as PATH. (In zsh, you can also set the fpath array — with the same syntax as path.) In FPATH, list directories that hold function files. In ksh, those files don't even need execute permission! Then ksh and zsh will search the FPATH directories if they can't find an executable file in the PATH.

Would you like the shells to search FPATH before PATH, so that a function will be executed before a standard command with the same name? (I would. After all, if I define a function from a shell prompt or shell setup file like .zshrc, that function will be run instead of a standard executable.) Here's how to set that up. Tell the shell to autoload the function. Autoloading happens automatically if there's no match found in PATH — because, as I said above, the shell falls back to FPATH if it doesn't find a match in PATH. But if you want the shell to look for a particular name in FPATH before it tries PATH, you have to autoload the function. Autoloading a function doesn't actually define the function (read the function body into the shell); it simply declares that the function exists — so the shell will remember that when you eventually want to execute the function.

This has a few twists, so let's look at each shell separately. You might want to do this yourself and follow along: When I first played with FPATH, I made two subdirectories of /tmp named a and b. Each directory had three simple function files named func1, func2, and foo. The functions func1 and func2 simply echo a message with their name and location. foo invokes a shell script of the same name, but first uses set -xv (Section 37.1) for debugging. func1 was a single-line function and func2 was multiline. The files in /tmp/a weren't executable, and the ones in /tmp/b were executable. I set the FPATH environment variable (set the shell variable and exported it) to /tmp/a:/tmp/b — so the shells should try the nonexecutable function files before falling back to the executables. After setting that up, I started a ksh subshell and played around. Then I exited the ksh and started a zsh.

29.13.2.1 Korn shell

Here's what happened in pdksh. The standard ksh is similar but not as verbose:

$ echo $FPATH
/tmp/a:/tmp/b
$ type func1
func1 is a undefined (autoload from /tmp/a/func1) function
$ func1
This is func1 from /tmp/a, a single-line unexecutable function
$ type func1
func1 is a function

$ typeset -f func2
$ type func2
func2 is a undefined (autoload from /tmp/a/func2) function
$ func2
This is func2 from /tmp/a, a multi-line unexecutable function
$ typeset -f func2
func2( ) {
    echo "This is func2 from /tmp/a, a multi-line unexecutable function"
}

$ type foo
foo is /home/jpeek/.bin/foo
$ autoload foo
$ type foo
foo is a undefined (autoload from /tmp/a/foo) function
$ cat /tmp/a/foo
foo( ) { sh -xv $HOME/.bin/foo "$@"; }
$ foo
#!/bin/sh
echo "Welcome to the exciting $0 program..."
+ echo Welcome to the exciting /home/jpeek/.bin/foo program...
Welcome to the exciting /home/jpeek/.bin/foo program...
$ type foo
foo is a function

Here's what happened with func1, func2, and foo:

If you'd like to be sure that all the functions in your FPATH are autoloaded — especially if you add new ones pretty often — here's a way to do it. Put code like this in your ENV setup file (Section 3.3):

IFS Section 36.23, for Section 28.9

# Autoload all functions in FPATH directories.
# Temporarily add a colon (:) to IFS to parse FPATH:
old_ifs="$IFS"; IFS=":$IFS"
for d in $FPATH
do autoload `ls $d`
done
IFS="$oldifs"; unset old_ifs

If a directory in FPATH is empty, autoload gets no arguments and, in that case, shows the function definitions it has already autoloaded. I only put a directory in my FPATH if it has functions to load. If you might have an empty directory in yours, you can avoid seeing the autoload output by editing that code to store the output of ls in a shell variable and running autoload only if the variable isn't empty.

29.13.2.2 zsh

The zsh system is mostly like ksh. The difference is that zsh doesn't automatically search FPATH. You have to manually autoload any function that you want zsh to search for in FPATH.

zsh$ echo $FPATH
/tmp/a:/tmp/b
zsh$ type func1
func1 not found
zsh$ func1
zsh: command not found: func1
zsh$ autoload func1
zsh$ type func1
func1 is a shell function
zsh$ func1
This is func1 from /tmp/a, a single-line unexecutable function
zsh$ type func1
func1 is a shell function

zsh$ autoload func2
zsh$ typeset -f func2
undefined func2 ( ) { }
zsh$ func2
This is func2 from /tmp/a, a multi-line unexecutable function
zsh$ typeset -f func2
func2 ( ) {
   echo "This is func2 from /tmp/a, a multi-line unexecutable function"
}

zsh$ type foo
foo is /home/jpeek/.bin/foo
zsh$ autoload foo
zsh$ foo
#!/bin/sh
echo "Welcome to the exciting $0 program..."
+ echo Welcome to the exciting /home/jpeek/.bin/foo program...
Welcome to the exciting /home/jpeek/.bin/foo program...
zsh$ type foo
foo is a shell function

I won't repeat all of the explanation from the ksh section. Instead, let's just look at the differences:

If you'd like to be sure that all the functions in your FPATH are autoloaded — especially if you add new ones pretty often — here's how to do it in zsh. Put code like this in a per-shell setup file (Section 3.3) — typically .zshrc:

# Autoload all functions in fpath directories:
for d in $fpath
do autoload `ls $d`
done

The code is simpler than in ksh because we can step through the fpath array without parsing it at colon (:) characters. As in ksh, though, you'll want to tweak the code if a directory in fpath might be empty: store the output of ls in an array and run autoload only if the array has members.

— JP

29.14 Simulated Bourne Shell Functions and Aliases

Until System V Release 2 (circa 1984), the Bourne shell had no way for users to set up their own built-in commands. If you have a Bourne shell with no functions (Section 29.11) or aliases (Section 29.2) and haven't yet turned the host machine into a wet bar, CD/DVD storage case, or some other pragmatic but fun use for a 30-year-old computer, you can do a lot of the same things with shell variables and the eval (Section 27.8) command.

Let's look at an example. First, here's a shell function named cps (copy safely). If the destination file exists and isn't empty, the function prints an error message instead of copying:

test Section 35.26

cps( )
{
   if test ! -s "$2"
   then cp "$1" "$2"
   else echo "cps: cannot copy $1: $2 exists"
   fi
}

If you use the same cps twice, the first time you'll make bfile. The second time you try, you see the error:

$ cps afile bfile
   ...
$ cps afile bfile
cps: cannot copy afile: bfile exists

Here's the same cps — stored in a shell variable instead of a function:

cps='
if test ! -s "$2"
then cp "$1" "$2"
else echo "cps: cannot copy $1: $2 exists"
fi
'

Because this fake function uses shell parameters, you have to add an extra step: setting the parameters. Simpler functions are easier to use:

set Section 35.25

$ set afile bfile
$ eval "$cps"
   ...
$ eval "$cps"
cps: cannot copy afile: bfile exists

— JP

[1]  [The mycat alias runs cat on all files with names later in the alphabet than the argument you type. The output of cat is piped to the less (Section 12.3) pager. For example, let's say your current directory has the files afile, count, jim, and report. Typing mycat count would display the files jim and report. — JP]

[2]  A function is a Bourne shell list construct.

[3]  Factorial is the product of all integers from some nonnegative number through one. So the factorial of 6, written 6!, is 6 x 5 x 4 x 3 x 2 x 1 or 720. Also, zero factorial (0!) is defined as 1. In recursion, a function typically calls itself to get "the next value," then waits for that value to be returned and returns its answer to the function that called it. If you ask a function to calculate 6!, it will call itself and ask for 5!, then call itself and ask for 4!, and so on. This can be confusing if you haven't seen it before, but there's information about it in almost every computer science textbook on basic programming techniques. It is also worth mentioning that recursion is a pretty poor way to calculate factorials in most languages, namely, those that lack support for tail recursion.

[4]  zsh lets you define a function in a function file without the enclosing funcname( ) { and } syntax. Then the file could be directly executed in a subshell by some shell that doesn't understand functions. I'm not sure I'd ever use this because running a function this way — as an external command instead of an internal command (Section 1.9) — means the function can't access or modify the environment of the shell that's running it, which is one of the reasons for writing a shell function in the first place! But, like everything in zsh, I'm sure someone had a good reason for making this work.

CONTENTS