Creating Global Functions with the Korn Shell
Rainer Raab
Scripting and UNIX systems administration go hand in hand. Writing scripts to automate repetitive tasks is necessary if you want to find the time to work on other things, such as tightening security on the systems you manage, migrating applications to promote server utilization, capacity planning, and system tuning. These types of tasks often fall at the bottom of the food chain if you are in a typical shop, constantly putting out the fires started by buggy applications, compounded by poor vendor support, and unreliable hardware. They also lose priority if you are supporting a development team with a time to market of 3-6 months for their application and the developers expect you to manage the test, staging, and production environments, along with application deployment and installation scripts. Fortunately, most UNIX variants provide an ideal tool for developing and deploying scripts -- the Korn shell.
The Korn shell is not just a command interpreter; it provides a powerful and versatile programming language for the rapid development of automation scripts and the capability to write globally accessible functions. In its basic form, a Korn shell script is a series of shell commands, strung together in an executable file. In its most advanced form, a Korn shell script may utilize built-in shell commands that provide support for arrays, integer arithmetic and arithmetic conditions, advanced UNIX I/O redirectors, and functions. It is the support for functions that allows the rapid development of scripts that provide solutions to the tasks of routine UNIX systems administration.
Defining Global Functions
Korn shell functions, known as procedures or subroutines in other programming languages, allow for the organization of related shell commands by name. By organizing shell commands into functions, long shell scripts are easier to read and follow, and thus maintain. More importantly, functions provide a mechanism allowing shell scripts to be quickly developed, because Korn shell functions can be made available for use by other Korn shell scripts.
Korn shell functions are normally defined within the script from which they are called. However, in order for them to be made available to other Korn shell scripts (i.e., made global), they must be defined separately and saved in their own files, with only one function per file. To define a global function in Korn shell, use the same syntax for defining all functions. Either one of the following forms will work:
function function_name { shell commands... }Or the Bourne shell form:
function_name () { shell commands... }I always use the first form since it is the one that I am more familiar with and seems to be the predominantly used one. A global function is one that returns the system date and time in a format that is suitable for timestamps in log files:
function get_time { TIME=$(date '+%m/%d/%y-%H:%M:%S') printf "$TIME\n" }To make the
get_time
function global and invokable from Korn shell
scripts, create a special directory (e.g., ksh_functions
) in a
system-wide accessible directory (e.g., /usr/loca
l). Save
the get_time
function in that directory, using the function name
as the filename. I prefer to use the /usr/local/ksh_functions
directory
as the global Korn shell function directory:
[rainer@fostercity]/usr/local/ksh_functions>ls -l total 2 -rw-r--r-- 1 rainer gurus 93 Nov 28 10:00 get_timeAdd the Korn shell built-in variable
FPATH=/usr/local/ksh_functions
and autoload get_time
to your .kshrc
file,
or to the file you have defined as the environmental file for your login shell.
If you have not previously done so, adding export ENV=~/.kshrc
to your .profile
initialize file will set your environmental
file to .kshrc
:
[oksana@fostercity]/export/home/oksana>more .kshrc export FPATH=/usr/local/ksh_functions autoload get_timeThe use of the built-in variable
FPATH
is similar to that of the
PATH
environment variable. FPATH
contains the list
of directories that have function definitions, and is used by the autoload function_name
command to locate functions. The autoload
command not only instructs
the shell to load the function only when invoked, but also enables us to store
functions in locations other than in our local .profile
or .kshrc
files, providing the entire framework for creation
of global functions. Actually, the autoload
command is an alias
for the typeset -fu
command and can be used instead. I prefer
to use the autoload
command because it is easier to remember.
To use get_time
from a script, simply invoke the function name
from a shell script:
[oksana@fostercity]/export/home/oksana/scripts>more test_time1 #!/bin/ksh PATH=/usr/bin:/usr/ucb get_timeYou can even assign a variable to hold the results from
get_time
:
[oksana@fostercity]/export/home/oksana/scripts>more test_time2 #!/bin/ksh PATH=/usr/bin:/usr/ucb SYSTIME=$(get_time)Securing Global Functions You may have noticed that in the
get_time
function I did not specify
the path for the system commands date
and printf
.
This is because the PATH environment variable is set explicitly, from the shell
scripts test_time1
and test_time2
, to include the
standard system command directories /usr/bin
and /usr/ucb
.
This is my preferred method for writing functions and scripts. If you view this
as a security concern, simply include the full path for system commands in all
functions and set the PATH
to null (PATH
="")
in your shell scripts.
For added security, create a special group, such as kshteam
,
and add supplementary group membership to users who want to create and access
global functions in the ksh_functions
directory:
[rainer@fostercity]/usr/local/ksh_functions>groups rainer gurus kshteamThen, assign the user bin as the owner, with group name
kshteam
,
to the ksh_functions
directory with permissions of read, write,
and execute for the owner/group and no permissions for world. Next, set the
sticky bit on the ksh_functions
directory to only allow the owner
of a global function to delete, rename, or modify the function:
[rainer@fostercity]/usr/local/ksh_functions>ls -la total 6 drwxrwx--T 2 bin kshteam 512 Nov 28 12:41 . drwxr-xr-x 40 root root 1024 Oct 22 14:50 .. -rw-r--r-- 1 rainer gurus 93 Nov 28 14:50 get_timeThis scheme will prevent unauthorized access to global functions, as well as prevent authorized users from modifying functions that they do not own. However, be sure to inform authorized users that their functions must be world readable to allow other authorized users access (unless the other authorized users all belong to the same primary group and the appropriate permissions are set to allow group access to the functions). Setting the global functions to world readable is not a security concern in this scheme because the permissions on the
ksh_functions
directory are set to none for world, making the
directory inaccessible by anyone other than root or members of the kshteam
group.
Managing Global Functions
There are several useful commands for managing functions. To verify which functions
are available to your shell, use the functions
command. The functions
command displays a list of all defined functions, listed in alphabetical order
by function name, defined in your login session:
[oksana@fostercity]/export/home/oksana/scripts>functions function get_timeThe
functions
command, like autoload
, is yet another
Korn shell alias. The typeset -f
command can be used instead. There
are a number of predefined Korn shell aliases that might be of interest. Use
the alias
command to display a list of all aliases defined for
your login session:
[oksana@fostercity]/export/home/oksana/scripts>alias autoload='typeset -fu' command='command ' functions='typeset -f' history='fc -l' integer='typeset -i' local=typeset nohup='nohup ' r='fc -e -' stop='kill -STOP' suspend='kill -STOP $$'The
functions
command does not just list functions available to
your current login session, but all of your shells, because the FPATH=/usr/local/ksh_functions
and the autoload get_time
statements have been added to your .kshrc
environment file. The get_time
function, as well as other functions
defined by the autoload command in your .kshrc
file, are
available to all subshells and Korn shell scripts run under your login.
whence
is another useful built-in command. It identifies the
source of a command. When used with the -v
option (verbose), whence
produces useful information as to what type of command it is (e.g., is it a
shell function, a built-in command, a reserved shell keyword, an alias, etc.):
[oksana@fostercity]/export/home/oksana/scripts>whence -v get_time get_time is a function [oksana@fostercity]/export/home/oksana/scripts>whence -v whence whence is a shell builtin [oksana@fostercity]/export/home/oksana/scripts>whence -v function function is a reserved shell keyword [oksana@fostercity]/export/home/oksana/scripts>whence -v functions functions is an exported alias for typeset -fUse
whence
for clarification when there is some uncertainty as
the output of a command. Strange results are often not so strange when you realize
that the command you have been running is not the command you thought you were
running. A good example of this is the pwd
command, which is also
a built-in shell command, and takes precedence over /usr/bin/pwd
when
typed without the full path. This conundrum becomes apparent when you cd
to a directory via a symbolic link, and find yourself wondering where you really
are:
[oksana@fostercity]/export/home/oksana/scripts>cd /usr/pub \ [oksana@fostercity]/usr/pub>pwd /usr/pub [oksana@fostercity]/usr/pub>/usr/bin/pwd /usr/share/lib/pubThis occurs because
pwd
is a built-in shell command that displays
the contents of the present working directory set by the cd
command
and stored in the PWD shell environment variable. PWD stores the relative path,
while /usr/bin/pwd
displays the absolute path name of the current
working directory.
Practical Global Functions
The get_time
function was a simple example of a function, made
global, that may be useful for incorporation into other Korn shell scripts.
However, it is hardly worth the effort to make get_time
global
just to save 3-4 lines of future typing. A more useful global function would
be one to send out email alerts (see Listing
1):
The first thing you might notice that is different with this function, than
with the previous get_time
function, is that it contains a documentation
header. When writing functions for system-wide usage, it is helpful to include
a small description of the function that describes (at a minimum) what the function
does and its usage. Develop a standard documentation header and stick to it.
As the system-wide function library grows, you will be glad to see the documentation
header there when you want to use someone else's function or need to use one
of your older functions.
To make the send_email
function globally available and invokable
from Korn shell scripts, save the send_email
function to our system-wide
accessible /usr/local/ksh_functions
directory and add the autoload
send_email
command to your .kshrc
file:
[rainer@fostercity]/usr/local/ksh_functions>ls -l total 4 -rw-r--r-- 1 rainer gurus 93 Nov 28 10:00 get_time -rw-r--r-- 1 rainer gurus 290 Nov 28 10:05 send_email [oksana@fostercity]/export/home/oksana>more .kshrc export FPATH=/usr/local/ksh_functions autoload get_time autoload send_emailTo use the
send_email
function from a script, simply assign values
to the variables EMAIL_LIST
, EMAIL_SUBJECT
, and EMAIL_MESSAGE
,
then invoke the function name from a script:
[oksana@fostercity]/export/home/oksana/scripts>more test_email #!/bin/ksh PATH=/usr/bin:/usr/ucb EMAIL_LIST="[email protected] [email protected]" EMAIL_SUBJECT="Testing" EMAIL_MESSAGE="This was only a test.." send_emailNotice that you can pass the value of variables to functions. In the
test_email
script, we are passing a space-separated list of the users to whom we want to
send email using the EMAIL_LIST
variable, and the subject of the
email message with the EMAIL_SUBJECT
variable, and the email message
itself with the EMAIL_MESSAGE
variable. This little bit of magic
works because functions do not run in separate subshells, as scripts normally
do (the exception is if you invoke a script with the "." command). Utilizing
functions in this manner allows you to use functions within your scripts as
if they were present locally!
Another useful function is one to read a configuration file (see Listing
2. When developing complex scripts, I find it useful to be able to change
the values of variables from a configuration file, rather than hardcoding the
values within the script itself. This not only eases script maintenance, but
also allows for user-adjustable settings. Since the configuration file is a
text file, you can make appropriate comments to assist the individuals who may
be responsible for managing the process that your script performs.
Listing 3 contains a configuration
file that could be used with the read_config
function. To make
the read_config
function globally available and invokable from
Korn shell scripts, save it to our system-wide accessible /usr/local/ksh_functions
directory and add autoload
read_config
to your .kshrc
file. Then to use it from a shell script, simply invoke the function name from
a script:
[oksana@fostercity]/export/home/oksana/scripts>more test_read_cfg #!/bin/ksh PATH=/usr/bin:/usr/ucb CONFIG_FILE=/export/home/oksana/scripts/test_read_cfg.conf read_config printf "Email list: $EMAIL_LIST\n" printf "Log file: $LOG_FILE\n" printf "Debugging is: $DEBUG\n"
Notice how the values of the variables DEBUG
, LOG_FILE
,
and EMAIL_LIST
are magically available to the test_read_cfg
script. This is accomplished by exporting the variables found in the configuration
file to the current shell for use. I use the read_config
function
for all of my shell scripts that require regular updates or are maintained by
someone else. For example, to add someone to the email list, just edit the test_read_cfg.conf
file with your favorite text editor and then append an additional email address
to the EMAIL_LIST
variable. Someone with no Korn shell scripting
experience can do this, since the configuration file is easily readable and
in plain text.
Conclusion
The Korn shell provides a powerful and versatile programming language and is my scripting language of choice for automating routine UNIX systems administration tasks. The Korn shell function facility not only provides the mechanism for allowing shell scripts to be quickly developed, but it promotes the sharing of code through the use of global functions. There is no point in reinventing the wheel every time a new script has to be developed. With some proper management, the use of global Korn shell functions can become an invaluable tool for everyone on a system, not just the systems administrator.
References
Learning the Korn Shell, Bill Rosenblatt, O'Reilly and Associates, Inc., 1993
Rainer Raab is a senior UNIX Systems Administrator, consulting for Wells Fargo Bank in San Francisco, California. When not scripting, he spends as much time as possible with his lovely wife, Oksana. He can be contacted at: [email protected].