Ksh Functions

Functions are the key to writing just about ANY program that is longer than a page or so of text. Other languages may call functions something else. But essentially, its all a matter of breaking up a large program, into smaller, managable chunks. Ideally, functions are sort of like 'objects' for program flow. You pick a part of your program that is pretty much self-contained, and make it into its own 'function'

Why are functions critical?

Properly written functions can exist by themselves, and affect only small things external to themselves. You should DOCUMENT what things it changes external to itself. Then you can look very carefully just at the function, and determine whether it actually does what you think it does :-)

When your program isn't working properly(WHEN, not if), you can then put in little debug notes to yourself in the approximate section you think is broken. If you suspect a function is not working, then all you have to verify is

Once you have done that, you then know the entire function is correct, for that particular set of input(s), and you can look for errors elsewhere.

A trivial function

printmessage() {
	echo "Hello, this is the printmessage function"


The first part, from the first "printmessage()" all the way through the final '}', is the function definition. It only defines what the function does, when you decide to call it. It does not DO anything, until you actually say "I want to call this function now".

You call a function in ksh, by pretending it is a regular command, as shown above. Just have the function name as the first part of your line. Or any other place commands go. For example,

echo The message is: `printmessage`

Remember: Just like its own separate shellscript. Which means if you access "$1" in a function, it is the first argument passed in to the function, not the shellscript.

Debugging your functions

If you are really really having difficulties, it should be easy to copy the entire function into another file, and test it separately from the main program.

This same type of modularity can be achived by making separate script files, instead of functions. In some ways, that is almost preferable, because it is then easier to test each part by itself. But functions run much faster than separate shellscripts.

A nice way to start a large project is to start with multiple, separate shellscripts, but then encapsulate them into functions in your main script, once you are happy with how they work.

CRITICAL ISSUE: exit vs return

THE main difference when changing between shellscripts and functions, is the use of "exit".

'exit' will exit the entire script, whether it is in a function or not.
'return' will just quit the function. Like 'exit', however, it can return the default "sucess" value of 0, or any number from 1-255 that you specify. You can then check the return value of a function, just in the same way you can check the return value of an external program, with the $? variable.

# This is just a dummy script. It does not DO anything

	# This will quit the 'fatal' function, and the entire script that
	# it is in!

	if [[ "$1" = "" ]] ; then echo "hey, give me an argument" ; return 1; fi

	# we should use 'else' here, but this is just a demonstration
	if [[ $1 -lt 4 ]] ; then
		echo Argument is less than 4
		# We are DONE with this function. Dont do anything else in
		# here. But the shellscript will continue at the caller

	echo Argument is equal to or GREATER than 4
	echo We could do other stuff if we wanted to now

echo note that the above functions are not even called. They are just
echo defined

A bare "return" in a shellscript is an error. It can only be used inside a function.

CRITICAL ISSUE: "scope" for function variables!

Be warned: Functions act almost just like external scripts... except that by default, all variables are SHARED between the same ksh process! If you change a variable name inside a function.... that variable's value will still be changed after you have left the function!! Run this script to see what I mean.
# Acts the same with /bin/sh, or /bin/ksh, or /bin/bash
        echo sub: var starts as $var
        echo sub: var is now $var
echo var starts as $var, before calling function '"subfunc"'
subfunc  # calls the function
echo var after function is now $var
To avoid this behaviour, and give what is known as "local scope" to a variable, you can use the typeset command, to define a variable as local to the function.
# You must use a modern sh like /bin/ksh, or /bin/bash for this
	typeset var
        echo sub: var starts as $var '(empty)'
        echo sub: var is now $var
echo var starts as $var, before calling function '"subfunc"'
subfunc  # calls the function
echo var after function is now $var
Another exception to this is if you call a function in the 'background', or as part of a pipe (like echo val | function )
This makes the function be called in a separate ksh process, which cannot dynamically share variables back to the parent shell. Another way that this happens, is if you use backticks to call the function. This treats the function like an external call, and forks a new shell. This means the variable from the parent will not be updated. Eg:
func() { newval=$(($1 + 1)) echo $newval echo in func: newval ends as $newval } newval=1 echo newval in main is $newval output=`func $newval` func $newval echo output is : $output echo newval finishes in main as $newval

Write Comments!

Lastly, as mentioned in the good practices chapter, dont forget to comment your functions! While shellscripts are generally easier to read than most programming languages, you really can't beat actual human language to explain what a function is doing!

TOP of tutorial
Next: Built-in functions Prev: POSIX.1 utilities
This material is copyrighted by Philip Brown