BigAdmin System Administration Portal
Community Submitted Article
BigAdmin
Print-friendly VersionPrint-friendly Version
This content is submitted by a BigAdmin user. It has not been reviewed for technical accuracy by Sun Microsystems, though it may have been lightly edited to improve readability. If you find an error or would like to comment on the article, please contact the submitter or use the comment field at the bottom of the article. Community submissions may not follow Sun trademark guidelines. For information on Sun trademarks, please see http://www.sun.com/suntrademarks/.
 
 

Converting a ksh Function to a ksh Script

William R. Seppeler, April 2006


Description

Here is a simple way to create a script that will behave both as an executable script and as a ksh function. Being an executable script means the script can be run from any shell. Being a ksh function means the script can be optimized to run faster if launched from a ksh shell. This is an attempt to get the best of both worlds.


Procedure

Start by writing a ksh function. A ksh function is just like a ksh script except the script code is enclosed within a function name { script } construct.

Take the following example:

# Example script

function fun {
  print "pid=$$ cmd=$0 args=$*" opts="$-"
}

Save the text in a file. You'll notice nothing happens if you try to execute the code as a script:

ksh ./example

In order to use a function, the file must first be sourced. Sourcing the file will create the function definition in the current shell. After the function has been sourced, it can then be executed when you call it by name:

.. ./example
fun

To make the function execute as a script, the function must be called within the file. Add the bold text to the example function.

# Example script

function fun {
  print "pid=$$ cmd=$0 args=$*" opts="$-"
}

fun $*

Now you have a file that executes like a ksh script and sources like a ksh function. One caveat is that the file now executes while it is being sourced.

There are advantages and disadvantages to how the code is executed. If the file was executed as a script, the system spawns a child ksh process, loads the function definition, and then executes the function. If the file was sourced, no child process is created, the function definition is loaded into the current shell process, and the function is then executed.

Sourcing the file will make it run faster because no extra processes are created, however, loading a function occupies environment memory space. Functions can also manipulate environment variables whereas a script only gets a copy to work with. In programming terms, a function can use call by reference parameters via shell variables. A shell script is always call by value via arguments.


Advanced Information

When working with functions, it's advantageous to use ksh autoloading. Autoloading eliminates the need to source a file before executing the function. This is accomplished by saving the file with the same name as the function. In the above example, save the example as the file name "fun". Then set the FPATH environment variable to the directory where the file fun is. Now, all that needs to be done is type "fun" on the command line to execute the function.

Notice the double output the first time fun is called. This is because the first time the function is called, the file must be sourced, and in sourcing the file, the function gets called. What we need is to only call the function when the file is executed as a script, but skip calling the function if the file is sourced. To accomplish this, notice the output of the script when executing it as opposed to sourcing it. When the file is sourced, arg0 is always -ksh. Also, note the difference in opts when the script is sourced. Test the output of arg0 to determine if the function should be called or not. Also, make the file a self-executing script. After all, no one likes having to type "ksh" before running every ksh script.

#!/bin/ksh
# Example script

function fun {
  print "pid=$$ cmd=$0 args=$*" opts="$-"
}

 [[ "${0##*/}" == "fun" ]] && fun $*

Now the file is a self-executing script as well as a self-sourcing function (when used with ksh autoloading). What becomes more interesting is that since the file can be an autoload function as well as a stand-alone script, it could be placed in a single directory and have both PATH and FPATH point to it.

# ${HOME}/.profile

FPATH=${HOME}/bin
PATH=${FPATH}:${PATH}

In this setup, fun will always be called as a function unless it's explicitly called as ${HOME}/bin/fun.


Considerations

Even though the file can be executed as a function or a script, there are minor differences in behavior between the two. When the file is sourced as a function, all local environment variables will be visible to the script. If the file is executed as a script, only exported environment variables will be visible. Also, when sourced, a function can modify all environment variables. When the file is executed, all visible environment variables are only copies. We may want to make special allowances depending on how the file is called. Take the following example.

#!/bin/ksh

# Add arg2 to the contents of arg1

function addTo {
  eval $1=$(($1 + $2))
}

if [[ "${0##*/}" == "addTo" ]]; then
  addTo $*
  eval print \$$1
fi

The script is called by naming an environment variable and a quantity to add to that variable. When sourced, the script will directly modify the environment variable with the new value. However, when executed as a script, the environment variable cannot be modified, so the result must be output instead. Here is a sample run of both situations.

# called as a function
var=5
addTo var 3
print $var

# called as a script
var=5
export var
var=$(./addTo var 3)
print $var

Note the extra steps needed when executing this example as a script. The var must be exported prior to running the script or else it won't be visible. Also, because a script can't manipulate the current environment, you must capture the new result.


Extra function-ality

It's possible to package several functions into a single file. This is nice for distribution as you only need to maintain a single file. In order to maintain autoloading functionality, all that needs to be done is create a link for each function named in the file.

#!/bin/ksh

function addTo {
  eval $1=$(($1 + $2))
}

function multiplyBy {
  eval $1=$(($1 * $2))
}

if [[ "${0##*/}" == "addTo" ]] \
|| [[ "${0##*/}" == "multiplyBy" ]]; then
  ${0##*/} $*
  eval print \$$1
fi

if [[ ! -f "${0%/*}/addTo" ]] \
|| [[ ! -f "${0%/*}/multiplyBy" ]]; then
  ln "${0}" "${0%/*}/addTo"
  ln "${0}" "${0%/*}/multiplyBy"
  chmod u+rx "${0}"
fi

Notice the extra code at the bottom. This text could be saved in a file named myDist. The first time the file is sourced or executed, the appropriate links and file permissions will be put in place, thus creating a single distribution for multiple functions. Couple that with making the file a script executable and you end up with a single distribution of multiple scripts. It's like a shar file, but nothing actually gets unpacked.

The only downside to this distribution tactic is that BigAdmin will only credit you for each file submission, not based on the actual number of executable programs...


Time to Run

Try some of the sample code in this document. Get comfortable with the usage of each snippet to understand the differences and limitations. In general, it's safest to always distribute a script, but it's nice to have a function when speed is a consideration. Do some timing tests.

export var=8
time ./addTo var 5
time addTo var 5

If this code were part of an inner-loop calculation of a larger script, that speed difference could be significant.

This document aims to provide the best of both worlds. You can have a script and retain function speed for when it's needed. I hope you have enjoyed this document and its content. Thanks to Sun and BigAdmin for the hosting and support to make contributions like this possible.

 


The information and links on this page have been provided by a BigAdmin user. The submitter is solely responsible for such information and links. Sun is not responsible for the availability of external sites or resources, and does not endorse and is not responsible or liable for any content, advertising, products, or other materials on or available from such sites or resources. Sun will not be responsible or liable, directly or indirectly, for any actual or alleged damage or loss caused by or in connection with use of or reliance on the information posted here, or goods or services available on or through any external site or resource.

Unless otherwise licensed, code in all technical manuals herein (including articles, FAQs, samples) is provided under this License.


Rate and Review
Tell us what you think of the content of this page.
Excellent   Good   Fair   Poor  
Comments:
Your email address (no reply is possible without an address):
Sun Privacy Policy

Note: We are not able to respond to all submitted comments.
  
 
 
 
Would you recommend this Sun site to a friend or colleague?
Contact About Sun News & Events Employment Site Map Privacy Terms of Use Trademarks Copyright Sun Microsystems, Inc.