The next sections cover some miscellaneous programming features.
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 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.
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.
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.
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:—1} }
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.
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.
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 }
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 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.
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.
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.
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.
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.
The trap command can also be used to reset traps to their default action by omitting the command argument:
trap – signals
or
trap signals
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.
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.
If multiple traps are set, the order of precedence is:
DEBUG
ERR
Signal Number
EXIT
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 |
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 |
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.
These options are enabled in the same way other options are enabled. You can invoke the script with the option enabled:
$ ksh —option script
invoke a subshell with the option enabled:
$ ksh —option $ script
set the option globally before invoking the script:
$ set —option $ script
or set the option within the script.
$ cat script . . . set —option . . .
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 — + 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 — [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
The trap command can also be helpful in debugging Korn shell scripts. The syntax for this type of trap command is:
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
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 –, 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.
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 <<– 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 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
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
3.15.137.213