|  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/local). 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_time
 
Add 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_time
 
The 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_time
 
You 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 kshteam
 
Then, 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_time
 
This 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_time
 
The 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 -f
 
Use 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/pub
 
This 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_email
 
To 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="raabrr@yahoo.com 4151234567@alphapage.airtouch.com"
 EMAIL_SUBJECT="Testing"
 EMAIL_MESSAGE="This was only a test.."
 send_email
 
Notice 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: raabrr@yahoo.com. 
           |