Miscellaneous Programming Features





Miscellaneous Programming Features

The next sections cover some miscellaneous programming features.

The . Command

The . command reads in a complete file, then executes the commands in it as if they were typed in at the prompt. This is done in the current shell, so any variable, alias, or function settings stay in effect. It is typically used to read in and execute a profile, environment, alias, or functions file. Here the .profile file is read in and executed:

$ . .profile 

The following example illustrates the difference between executing files as Korn shell scripts and reading/executing them using the . command. The .test file sets the variable X:

$ cat .test 
X=ABC 

When the .test file is executed as a Korn shell script, variable X is not defined in the current environment, because scripts are run in a subshell:

$ ksh .test 
$ print $X 

$ 

After the .test file is read in and executed using the . command, notice that the variable X is still defined:

$ . .test 
$ print $X 
ABC 

The standard search path, PATH, is checked if the file is not in the current directory.

Functions

Functions are a form of commands like aliases, scripts, and programs. They differ from Korn shell scripts, in that they do not have to be read in from the disk each time they are referenced, so they execute faster. Functions differ from aliases, in that functions can take arguments. They provide a way to organize scripts into routines in the same way as in other high-level programming languages. Since functions can have local variables, recursion is possible. Functions are most efficient for commands with arguments that are invoked fairly often, and are defined with the following format:

function name { 
       commands 
} 

To maintain compatibility with the Bourne shell, functions can also be declared with this POSIX-style format:

function-name() { 
       commands 
} 

These types of functions have many limitations compared to Korn shell style functions, such as no support for local variables.

Here is a function called md that makes a directory and cd's to it:

$ cat md 
function md { 
      (($# < 1)) && {print "$0: dir"; exit 1;} 
      mkdir $1 && cd $1 
      pwd 
} 

To be able to execute a function, it must first be read in. This is done with the . command:

$ . md 

Now the md function can be invoked. Here, we try it with the dtmp directory:

$ md dtmp 
/home/anatole/dtmp 

Functions are executed in the current environment, so any variables and option settings are available to them.

Returning Function Exit Status

The return command is used to return from a function to the invoking Korn shell script and pass back an exit value. The syntax for the return command is:

return 

or

return n 

where n is a return value to pass back to the invoking Korn shell script or shell. If a return value is not given, the exit status of the last command is used. To exit from a function and the invoking Korn shell script, use the exit command from inside the function.

Scope and Availability

By default, functions are not available to subshells. This means that a regular function that was read in your working environment, .profile file, or environment file would not be available in a Korn shell script. To export a function, use the typeset –fx command:

typeset –fx function-name 

To make a function available across separate invocations of the Korn shell, include the typeset –fx function-name command in the environment file.

Using Functions in Korn Shell Scripts

Functions are very useful in Korn shell scripts. Not only because you can organize scripts into routines, but they also provide a way to consolidate redundant sequences of commands. For example, instead printing out an error message and exiting each time an error condition is encountered, an all-purpose error function can be created. The arguments passed to it are the message to display and the exit code:

$ cat error 
function error { 
      print ${1:?unexplained error encountered"} 
      exit ${2:?} 
} 

If function error is called without arguments, then you get the default error message "unexplained error encountered" and a default exit code of 1. Now on a non-existent file error, function error could be called like this:

error "$FILE:non-existent or not accessible" 3 

It can save quite a bit of code, and it's easier to maintain. One more thing: because functions need to be read in before they can be invoked, it's a good idea to put all function definitions at the top of Korn shell scripts.

Function Variables

All function variables, except those explicitly declared locally within the function with the typeset command, are inherited and shared by the calling Korn shell script. In this example, the X, Y, and Z variables are set within and outside of the function f:

$ cat ftest 
X=1 
function f { 
      Y=2 
      typeset Z=4 
      print "In function f, X=$X, Y=$Y, Z=$Z" 
      X=3 
} 
f 
print "Outside function f, X=$X, Y=$Y, Z=$Z" 

Notice that when executed, all the variable values are shared between the function and calling script, except for variable Z, because it is explicitly set to a local function variable using the typeset command. The value is not passed back to the calling Korn shell script:

$ ftest 
In function f, X=1, Y=2, Z=4 
Outside function f, X=3, Y=2, Z= 

The current working directory, aliases, functions, traps, and open files from the invoking script or current environment are also shared with functions.

Displaying Current Functions

The list of currently available functions are displayed using the typeset –f command:

$ typeset —f 
function _cd 
{ 
      'cd' $1 
      PS1="$PS0$PWD> " 
} 
function md 
{ 
      mkdir $1 && 'cd' $1 
} 
Autoloading Functions

To improve performance, functions can be specified to autoload. This causes the function to be read in when invoked, instead of each time a Korn shell script is invoked, and is used with functions that are not invoked frequently. To define an autoloading function, use the typeset –fu function-name command. Here, lsf is made an autoloading function:

$ typeset —fu lsf 

The autoload alias can also be used to define an autoloading function. On most systems, it is preset to typeset –fu.

The FPATH variable which contains the pathnames to search for autoloading functions must be set and have at least one directory for autoloading functions to work.

Discipline Functions

Discipline functions are a new feature in KornShell 93. They are a special type of function used to manipulate variables. They are defined but not specifically called. Rather, they are called whenever the variable associated with the function is accessed.

There are some specific rules as to how discipline functions are named and accessed. First of all, discipline functions are named using this syntax:

name.function 

Notice the function has two parts separated with a dot. The first part corresponds to the name of a variable, and the second part must be get, set, or unset. These correspond to the following operations on the variable:

get

whenever the base discipline variable is accessed

set

whenever the base discipline variable is set

unset

whenever the base discipline variable is unset

For example, the discipline function LBIN.get, LBIN.set, LBIN.unset is called whenever the variable LBIN is accessed, set, or unset.

All three discipline functions are optional, so not all need to be specified.

Within a discipline function, the following special reserved variables can be used:

.sh.name

name of current variable

.sh.value

value of the current variable

.sh.subscript

name of the subscript (if array variable)

From a practical perspective, discipline functions are often used to help debug by tracing the setting and current value of variables in running scripts. Here is a function that can be used to trace setting the value of X:

function X.set { 
         print "DEBUG: ${.sh.name} = ${.sh.value}" 
} 

Discipline functions are also a good place to centralize your variable assignment validation rules. Here is a function that checks to make sure that X it set to a number between 3 and 10:

function X.set { 
   if (( .sh.value<3 || .sh.value >10 )) 
   then 
      print "Bad value for ${.sh.name}: ${.sh.value}" 
   fi 
} 

Note that builtin functions can also be used as additional discipline functions.

FPATH

The FPATH variable contains a list of colon-separated directories to check when an autoloading function is invoked. It is analogous to PATH and CDPATH, except that the Korn shell checks for function files, instead of commands or directories. Each directory in FPATH is searched from left-to-right for a file whose name matches the name of the function. Once found, it is read in and executed in the current environment. With the following FPATH setting, if an autoloading function lsf was invoked, the Korn shell would check for a file called lsf in /home/anatole/.fdir, then /etc/.functions, and if existent, read and execute it:

$ print $FPATH 
/home/anatole/.fdir:/etc/.functions 

There is no default value for FPATH, so if not specifically set, this feature is not enabled.

Removing Function Definitions

Functions are removed by using the unset –f command. Here, the rd function is removed:

$ unset —f rd 

and when invoked, it is now undefined:

$ rd 
/bin/ksh: not found 

Multiple function names can also be given to the unset –f command.

Traps

The trap command is used to execute commands when the specified signals are received.

trap commands signals 

Trap commands are useful in controlling side effects from Korn shell scripts. For example, if you have a script that creates a number of temporary files, and you hit the <Break> or <Delete> key in the middle of execution, you may inadvertently leave the temporary files. By setting a trap command, the temporary files can be cleaned up on an error or interrupt.

The trap_test script creates some files, then removes them when an interrupt is received. Notice that the trap command is surrounded in single quotes. This is so that the FILES variable is evaluated when the signal is received, not when the trap is set.

$ cat trap_test 
trap 'print "$0 interrupted - removing temp  files" ;\ 
rm —rf $FILES; exit 1' 1 2 
FILES="a b c d e f" 
touch $FILES 
sleep 100 
$ trap_test 
Ctl-c 
trap_test interrupted - removing temp files 

If an invalid trap is set, an error is generated.

Ignoring Signals

The trap command can be used to ignore signals by specifying null as the command argument:

trap "" signals 

This could be used to make all or part of a Korn shell script uninterruptable using normal interrupt keys like Ctl-c. This trap command causes signals 2 and 3 to be ignored:

$ trap "" 2 3 

The "" argument must be in this type of trap command, otherwise the trap is reset.

Resetting Traps

The trap command can also be used to reset traps to their default action by omitting the command argument:

trap ?/span> signals 

or

trap signals 
Exit and Function Traps

A trap can be set to execute when a Korn shell script exits. This is done by using a 0 or EXIT as the signals argument to the trap command:

trap 'commands' 0 

or

trap 'commands' EXIT 

This could be used to consolidate Korn shell script cleanup functions into one place. The trap_test script contains a trap command that causes a message to be printed out when the script finishes executing:

$ cat trap_test 
trap 'print exit trap being executed' EXIT 
print "This is just a test" 
$ trap_test 
This is just a test 
exit trap being executed 

If set within a function, the commands are executed when the function returns to the invoking script. This feature is used to implement the C shell logout function in Appendix C.

Debugging with trap

The trap command can be helpful in debugging Korn shell scripts. The special signal arguments DEBUG and ERR are provided to execute trap commands after each command or only when commands in a script fail. This is discussed in the next section.

Trap Signal Precedence

If multiple traps are set, the order of precedence is:

  • DEBUG

  • ERR

  • Signal Number

  • EXIT

Trapping Keyboard Signals

The Korn shell traps KEYBD signals (sent when you type a character) and automatically assigns the following reserved variables:

.sh.edchar

contains last character of key sequence

.sh.edtext

contains current input line

.sh.edmode

contains NULL character (or escape character if user in command mode)

.sh.edcol

contains position within the current line

Korn Shell Debugging Options

set –e, set –o errexit

execute ERR trap (if set) on non-zero exit status from any commands

set –n, set –o noexec

read commands without executing them

set –v, set –o verbose

display input lines as they are read

set –x, set –o xtrace

display commands and arguments as they are executed

typeset –ft function

display the commands and arguments from function as they are executed

Debugging Korn Shell Scripts

The Korn shell provides a number of options that are useful in debugging scripts: noexec, verbose, and xtrace. The noexec option causes commands to be read without being executed. It is used to check for syntax errors in Korn shell scripts. The verbose option causes the input to be displayed as it is read. The xtrace option causes the commands in a script to be displayed as they are executed. This is the most useful, general debugging option.

Enabling Debug Options

These options are enabled in the same way other options are enabled. You can invoke the script with the option enabled:

$ ksh ?/span>option script 

invoke a subshell with the option enabled:

$ ksh ?/span>option 
$ script 

set the option globally before invoking the script:

$ set ?/span>option 
$ script 

or set the option within the script.

$ cat script 
. . . 
set ?/span>option 
. . . 
Debugging Example

Here is a Korn shell script called dbtest. It sets variable X to ABC, then checks the value and prints a message. Notice that there is an unmatched double quote on line 2:

$ cat dbtest 
X=ABC 
if [ $X" = "foo" ] 
then 
      print "X is set to ABC" 
fi 

When run with the noexec option, the syntax error is flagged:

$ ksh —n dbtest 
dbtest[2]: syntax error at line 2: `"' unmatched 

When an error is detected while executing a Korn shell script, the name of the script or function, the error message, and the line number enclosed in []'s are displayed. For functions, the line number relative to the beginning of the function is displayed. The dbtest script is fixed and run again with the noexec option:

$ ksh —n dbtest 
$ 

No error is flagged this time, but also notice that no output is generated. This is because the noexec option causes the commands to be read, but not executed. Now, the dbtest script is run with the xtrace option:

$ ksh —x dbtest 
+ alias —x echo=print ?/span> 
+ alias —x vi=SHELL=/bin/sh vi 
+ PS0=!: 
+ PS1=!:/home/anatole/bin> 
+ typeset —fx cd md 
+ typeset —x EDITOR=vi 
+ X=ABC 
+ [ X = ABC ] 
+ print X is set to ABC 
X is set to ABC 

Now there is a lot of output, most of which is execution trace output from processing of the environment file. The value of PS4 is displayed in front of each line of execution trace. If not explicitly reset, the default is the + character. The line number can also be included in the debug prompt by including LINENO in the PS4 setting.

$ typeset —x PS4='[$LINENO] ' 

Now the line number is displayed in brackets in the trace output:

$ ksh —x dbtest 
[11] alias —x echo=print ?/span> 
[12] alias —x vi=SHELL=/bin/sh vi 
[13] PS0=!: 
[14] PS1=!:/home/anatole/bin> 
[15] typeset —fx cd md 
[16] typeset —x EDITOR=vi 
[1] X=ABC 
[2] [ X = ABC ] 
[4] print X is set to ABC 
X is set to ABC 

When the dbtest script is run without any debugging options, this is the output:

$ dbtest 
X is set to ABC 
Debugging with trap

The trap command can also be helpful in debugging Korn shell scripts. The syntax for this type of trap command is:

Some Frequently-Used Signals

0

shell exit

3

quit

1

hangup

15

terminate

2

interrupt

  

trap commands DEBUG 

or

trap commands ERR 

If the trap command is set with DEBUG, then the trap commands are executed after each command in the script is executed. The following trap command causes pwd to be executed after each command if the variable DB_MODE is set to yes, otherwise a normal trap is executed.

if [[ $DB_MODE = yes ]] 
then 
      trap "pwd" DEBUG 
else 
      trap "rm —rf $TMPFILE; exit 1" 1 2 15 
fi 

If set with ERR and the errexit (–e) option is enabled, the trap is executed after commands that have a non-zero (unsuccessful) exit status. This case statement causes a different trap to be set, depending on the debug flag. If the debug flag is 0, then a normal trap is set, which removes some temporary files on normal or abnormal termination. If the debug flag is 1, then an ERR trap is set, which causes the line number to be displayed when an error occurs. If the debug flag is 2, then a DEBUG trap is set, which causes the line number and current working directory to be displayed.

case $DB_FLAG in 
      0 )     # Default trap - perform cleanup 
             trap "rm —rf $FILES; exit 1" 0 1 2 15 ;; 

      1 )     # Execute trap for failed commands only 
             set —o errexit 
             trap 'print Error at $LINENO' ERR ;; 

      2 )     # Execute trap for all commands 
             trap 'print At $LINENO; pwd' DEBUG ;; 

      * )     # Invalid debug flag 
             print "Invalid debug flag" ; exit 1 ;; 
esac 

Parsing Command-line Arguments

Here is an alternative to using case to parse command-line arguments: the getopts command. It works with the OPTARG and OPTIND variables to parse command-line arguments using this format:

getopts optstring name args 

or

getopts optstring name 

where optstring contains the list of legal options, name is the variable that will contain the given option letter, and args is the list of arguments to check. If not given, the positional parameters are checked instead. If an option begins with a +, then + is prepended to name. In all other cases, name is set to the option letter only.

There are some requirements on option format with the getopts command. Options must begin with a + or ?/span>, and option arguments can be separated from the options with or without whitespace. This getopts command specifies that a, b, and c are valid options, and OPT will be set to the given option:

getopts abc OPT 

A : after an option in optstring indicates that the option needs an argument, and OPTARG is set to the option argument. This getopts command specifies that the a, b, and c are valid options, and that options a and c have arguments:

getopts a:bc: OPT 

If optstring begins with a :, then OPTARG is set to any invalid options given, and name is set to ?. If an option argument is missing, name is set to ":". In the following Korn shell script, the getopts command is used in conjunction with the case command to process options and their arguments. The ":a:bc:" options string specifies that options a and c need arguments, and that invalid options are processed.

$ cat getopts_test 
while getopts :a:bc: OPT 
do 
      case $OPT in 
             a|+a) print "$OPT received" ;; 
             b|+b) print "$OPT received" ;; 
             c|+c) print "$OPT received" ;; 
            :) print "$OPTARG needs arg" \exit ;; 
             \?) print "$OPTARG:bad option"exit ;; 
      esac 
done 

Here the +b and –b options are given:

$ getopts_test +b —b 
+b received 
b received 

The c option needs an argument, so an error message is displayed:

$ getopts_test —c 
c needs arg 

Here, an invalid option is given:

$ getopts_test —x 
x: bad option 

The OPTIND variable is set by the getopts command to the index of the next argument. It is initialized to 1 when a new function, Korn shell, or script is invoked.

More with Here Documents

The here document feature is also used in shareware software distribution. Multiple here documents are put into one file, and when executed, generate all the modules separately. Here is an example Korn shell archive file called archive_test:

$ cat archive_test 
print "Extracting a" 
cat >a <<—END 
      This is file a. 
END 
print "Extracting b" 
cat >b <<—END 
      This is file b. 
END 
print "Extracting c" 
cat >c <<—END 
      This is file c. 
END 

When executed, it generates three files: a, b, and c.

$ ls 
archive_test 
$ archive_test 
Extracting a 
Extracting b 
Extracting c 

This feature also allows you to edit a file from within a Korn shell script. The htest script edits the file tmp and inserts one line:

$ cat htest 
ed ?tmp <<EOF 
a 
This is a new line 
. 
w 
q 
EOF 

After the htest Korn shell script is run, here is the result:

$ htest 
$ cat tmp 
This is a new line 

The <<?/span> operator is the same as <<, except that leading tab characters from each standard input line including the line with word are ignored. This is used to improve program readability by allowing the here document to be indented along with the rest of the code in Korn shell scripts.

The here document feature is also useful for generating form letters. The hmail script shows how to send a mail message to multiple users using this feature.

$ cat hmail 
for i in terry larry mary 
do 
      mail $i <<—END 
            $(date) 
            Have a good holiday $i! 
      END 
done 

Co-Processes

Co-processes are commands that are terminated with a |& character. They are executed in the background, but have their standard input and output attached to the current shell. The print –p command is used to write to the standard input of a co-process, while read –p is used to read from the standard output of a co-process. Here, the output of the date command is read into the DATE variable using the read –p command:

$ date |& 
[2] 241 
$ read —p DATE 
$ print $DATE 
Thu Jul 18 12:23:57 PST 1996 

Co-processes can be used to edit a file from within a Korn shell script. In this example, we start with file co.text:

$ cat co.text 
This is line 1 
This is line 2 
This is line 3 

It is edited using a co-process, so the job number and process id are returned:

$ ed ?co.text |& 
[3] 244 

Co-Processes

command |&

execute command in the background with the standard input and output attached to the shell.

n<&p

redirect input from co-process to file descriptor n. If n is not specified, use standard input.

n>&p

redirect output of co-process to file descriptor n. If n is not specified, use standard output.

print –p

write to the standard input of a co-process.

read –p

read from the standard output of a co-process.

The command (display line 3) is written to the co-process using print –p. The output of the ed command is then read into the LINE variable using read LINE:

$ print —p 3p 
$ read —p LINE 
$ print $LINE 
This is line 3 

The next commands delete line 2, then send the write and quit commands to ed via the co-process:

$ print —p 2d 
$ print —p w 
$ print —p q 
[3] + Done         ed ?co.text |& 

After editing from the co-process, co.text file looks like this:

$ cat co.text 
This is line 1 
This is line 3 


     Python   SQL   Java   php   Perl 
     game development   web development   internet   *nix   graphics   hardware 
     telecommunications   C++ 
     Flash   Active Directory   Windows