Functions
Overview
A Qshell function, like a Qshell script, is a group of commands that is given a name . Functions differ from scripts in that functions become part of the invoking environment, whereas scripts are stored in files. Qshell can execute a function from memory, whereas it must load a script from an external stream file.
Qshell functions are similar to subroutines, functions, and procedures in other languages, and therefore can be used to implement modularity in Qshell scripts. While functions are usually defined in scripts or profile files, at times it is useful to type a function into an interactive session to help automate ad hoc repetitive tasks .
One of the most important things to understand about functions is that they execute in the caller's process. Qshell begins a new process when it runs a script or external utility, but it does not start a new process when it executes a function. This means that functions are more efficient than scripts, and that a function can change its caller's variables directly.
Function Syntax
There are two acceptable ways to define a function. The older way, taken from the Bourne shell, works on all releases of Qshell. Here is the syntax:
function-name () { commands }
The function name is followed by empty parentheses, which serve no purpose except to tell Qshell that a function is being defined. Next is an open brace, which may be on the same line or on the following line. The commands follow. They may follow the brace on the same line or the next line. The function is terminated with a closing brace on a line by itself.
The newer way to define a shell, introduced with V5R2, is like the function definition structure of the Korn shell. Its syntax is shown here:
function function-name { commands }
This syntax differs from the older syntax in two ways: The function begins with the reserved word function , and the empty parentheses are not coded.
A function must be defined before it can be invoked. For this reason, it is customary to place function definitions at the beginning of scripts.
In Figure 13.1, the user has typed the definition of the lcname (Lowercase Name) function into an interactive session. Since the function was defined in this session, it may be invoked from the command line. Note that the entire function has been coded on one input line. Since the closing brace is required to be on a line by itself, a semicolon is necessary to separate it from the rest of the function definition.
function lcname { echo tr '[:upper:]' '[:lower:]' ; } /home/jsmith $ filename=/QSYS.LIB/MYLIB.LIB/MYFILE.FILE/MYMBR.MBR /home/jsmith $ lcname $filename /qsys.lib/mylib.lib/myfile.file/mymbr.mbr
Figure 13.1: The lcname function has one compound command, which uses the tr (translate) utility to convert uppercase letters to the corresponding lowercase ones.
Here is the same function defined with the older syntax:
lcname() { echo tr '[:upper:]' '[:lower:]' ; }
Displaying Function Definitions
Use the declare (or typeset ) utility to display the names and definitions of defined functions. Use the F option to display the names of defined functions, and the f option to display the definitions of defined functions, as shown in Figure 13.2. If you follow the option with the name of a function, declare shows information about that function only. If you do not include a function name, declare shows information about all defined functions.
declare -F ucname lcname /home/jsmith $ declare -f function ucname() { echo tr '[:lower:]' '[:upper:]' } function lcname() { echo tr '[:upper:]' '[:lower:]' } /home/jsmith $ declare -f ucname function ucname() { echo tr '[:lower:]' '[:upper:]' } /home/jsmith $
Figure 13.2: Use the declare utility to display the names and definitions of functions.
In V5R2, Qshell defines the alias functions as the equivalent of declare -f. Figure 13.3 illustrates this concept.
functions function lcname() { echo tr '[:upper:]' '[:lower:]' } /home/jsmith $
Figure 13.3: The functions alias lists all defined functions.
When you use the declare utility to display the definition of a function, do not expect to see the same syntax that was used when the function was defined. For example, in Figure 13.4, the rev (reverse) function is defined with the old, Bourne-shell syntax, but the declare utility (executed through the functions alias) displays the function definition in the new function definition syntax. Notice, too, that the $ n notation for positional parameters is used when defining the function, but the displayed definition uses the ${ n } notation.
rev() { > echo > } /home/jsmith $ rev a b b a /home/jsmith $ functions rev function rev() { echo } /home/jsmith $
Figure 13.4: The internal definition of a function might not match the original definition.
Deleting Function Definitions
To "undefine" a function, use the unset utility with the -f option. Figure 13.5 illustrates the unset command.
lcname $filename /qsys.lib/mylib.lib/myfile.file/mymbr.mbr /home/jsmith $ unset -f lcname /home/jsmith $ lcname $filename qsh: 001-0019 Error found searching for command lcname. No such path or directory.
Figure 13.5: After the unset utility runs, Qshell no longer finds the lcname function.
Parameters
You may pass arguments to a function, just as you pass them to a script. The function can reference the passed values as positional parameters, using the same $n or ${n} notation that scripts use.
Special parameters also have appropriate values within a function. For example, the special parameter $# used in the nonfunction portion of a script returns the number of arguments passed to the script. When $# is used within a function, it returns the number of arguments passed to the function. The special parameter $0 is an exception. It does not have a special value within a function. Whether it's used in a function or in the nonfunction section of a script, $0 always returns the script name . (For a full list of special parameters, see chapter 5.)
The script in Figure 13.6 illustrates the use of positional parameters in a function. In it, the stripcmt.qsh script removes comments from stdin and writes to stdout . The script contains function delcmt , which deletes the first pound sign and everything following it from the first positional parameter. Notice that function delcmt refers to the first positional parameter twice. Also notice that the stripcmt.qsh script is not foolproof; it assumes that the first pound sign in a line indicates a comment. That might not be true.
cat stripcmt.qsh function delcmt { if [[ -z "" ]] then echo "" return 0 fi echo "" cut -d'#'-f1 return 0 } # end function delcmt while read line do delcmt "$line" done /home/jsmith $ cat bu.qsh # Backup script. # # Copy the files listed in the positional parameters to # the bu directory if [[ -e bu ]] # bu exists; be sure it's a directory then if [[ ! -d bu ]] then print -u2 "bu is not a directory backup aborted." exit 1 fi else mkdir bu fi for file do cp $file bu/$file.bu done /home/jsmith $ stripcmt.qsh if [[ -e bu ]] then if [[ ! -d bu ]] then print -u2 "bu is not a directory backup aborted." exit 1 fi else mkdir bu fi for file do cp $file bu/$file.bu done /home/jsmith $
Figure 13.6: Stripcmt.qsh uses a function to remove comments from input lines.
Variables
A function may reference the variables that are defined in the script in which the function is defined. Such variables are called global variables. For example, in the following fragment of a Qshell script, the init function establishes initial values of two global variables. After init has executed, the print statement writes the string Joe Smith to stdout :
function init { firstname=Joe lastname=Smith } init print $firstname $lastname
A function may also define local variables that are known only within the function. To define a local variable, use the local utility. In V5R2, you may use the declare and typeset utilities to define local variables within functions. No special options are needed. Any variable that is declared with typeset or declare within a function is local to the function.
The following function illustrates the use of global and local variables:
function lastchar { local length=${#1} if [ $length -gt 0 ] then clast=$(echo cut -c $length) else clast='' fi }
Variable length is local to the lastchar function. Variable clast is global and may be referenced anywhere in the script, including within the lastchar function.
The script in Figure 13.7 was written for a V5R1 system, which does not have the substring parameter-expansion operators that were introduced in V5R2. It copies the characters in the first parameter to standard output, one character per output line. It includes two functions, firstchar and chopfirst . Function firstchar modifies global variable c1 . Function chopfirst uses both local and global variables.
cat func04.qsh ###### list each character of an argument # function firstchar -- # place the first character of a string # into global variable c1 firstchar() { c1=$(echo "" cut -c 1) } # function chopfirst -- # place all but the first character of # a string into global variable afterfirst chopfirst() { local length=${#1} if [ $length -gt 1 ] then afterfirst=$(echo "" cut -c 2-$length) else afterfirst= ' ' fi } # Begin main routine valu= until [ -z "$valu" ] ; do firstchar "$valu" echo $c1 chopfirst "$valu" valu= "$afterfirst" done /home/JSMITH $ func04.qsh "RPG IV" R P G I V /home/JSMITH $
Figure 13.7: In function chopfirst, both local and global variables are used.
In Figure 13.8, there are two variables named length . One is a global variable, while the other is local to function a . When length is referenced in function a , the local length variable is used. Outside of function a , the global length is used.
cat myscript.qsh function a { local length length=2 print "a "$length } length=1 print "Value of the length variable" print "============================" print "Routine Value" print "============================" print "main " $length a print "main " $length print "============================" /home/jsmith $ myscript.qsh Value of the length variable ============================ Routine Value ============================ main 1 a 2 main 1 ============================ /home/jsmith $
Figure 13.8: This example uses a local variable named length and a global variable named length .
Figure 13.9 also has both a global variable named length and, in function b , a local variable named length . Function a refers to length , but since it does not have a local variable of that name , it uses the value of the most recently activated length variable. So, if function a is called directly from the main procedure, it uses the global length variable. If it is called from function b , a uses b 's length variable.
cat myscript.qsh function a { print "a " $length } function b { local length length=3 print "b" $length a print "b " $length } length=1 print "Value of the length variable" print "============================" print "Routine Value" print "============================" a print "main " $length b print "main " $length a print "main " $length print "============================" /home/jsmith $ myscript.qsh Value of the length variable ============================ Routine Value ============================ a 1 main 1 b 3 a 3 b 3 main 1 a 1 main 1 ============================ /home/jsmith $
Figure 13.9: A function uses the definition of the most recently created instance of a variable defined outside of itself.
Starting with V5R2, you can use the declare and typeset utilities to define local variables in functions, as shown in Figure 13.10. Function init changes the value of global variable lastname , but not global variable firstname .
cat myscript.qsh function init { declare firstname firstname=Joe lastname=Smith print $firstname $lastname } firstname=Sue init print $firstname $lastname /home/jsmith $ myscript.qsh Joe Smith Sue Smith /home/jsmith $
Figure 13.10: This script was written for a V5R2 system, where you can use the declare and typeset utilities to define local variables.
Normally, any options that are changed with the set utility within a function affect the calling environment in which the function is defined. You may make the - (hyphen) special variable local to a function. However, doing so restricts the changes that can be made to options of the function itself. Figures 13.11 and 13.12 illustrate the difference. Notice the ls command in each figure. In Figure 13.11, the x option has been affected globally, causing the ls command to be echoed to stdout. In Figure 13.12, the x option is changed locally, so the ls command is not echoed.
function ucname { > set -x > echo tr '[:lower:]' '[:upper:]' > } /home/jsmith $ ucname "Joe Smith" + echo Joe Smith + tr [:lower:] [:upper:] JOE SMITH /home/jsmith $ ls t*.qsh + ls tfunctions.qsh theirscript.qsh tscriot.qsh tscript.qsh tfunctions.qsh theirscript.qsh tscriot.qsh tscript.qsh /home/jsmith $
Figure 13.11: The set command, which is executed within the function, affects the entire session.
function ucname { > local - > set -x > echo tr '[:lower:]' '[:upper:]' > } /home/jsmith $ ucname "Joe Smith" + echo Joe Smith + tr [:lower:] [:upper:] JOE SMITH /home/jsmith $ ls t*.qsh tfunctions.qsh theirscript.qsh tscriot.qsh tscript.qsh /home/jsmith $
Figure 13.12: Defining the - special variable to be local restricts the effect of the set command.
Invoking Functions
Invoke functions by name , just as you would invoke built-ins , external utilities, and aliases. In Figure 13.13, a function is invoked from more than one line of the sendfile.qsh script, which sends a file to a destination. If there is an error in the way the command is invoked, the script runs the usage function, which displays a synopsis of how the command is to be used, and aborts the script. In a true production script, the usage function would probably be called from several places, not just two.
cat sendfile.qsh # Send a file via ... function usage { local scriptname=$(basename
cat sendfile.qsh # Send a file via ... function usage { local scriptname=$(basename $0) print "$scriptname usage:" print " $scriptname [-r] [-d destination] file ..." exit 1 } # process the options resend=off while getopts rd: argname do case $argname in r) resend=on;; d) dest=$OPTARG;; ?) usage;; esac done # get rid of options shift $OPTIND-1 # make sure at least one filename # was entered if [[ -z $1 ]] then usage fi filename=$1 print "Sending $filename..." # ... more commands omitted ... exit 0 /home/jsmith $ sendfile.qsh sendfile.qsh usage: sendfile.qsh [-r] [-d destination] file ... /home/jsmith $ sendfile.qsh -x getopts: 001-0036 Option x is not valid. sendfile.qsh usage: sendfile.qsh [-r] [-d destination] file ... /home/jsmith $ sendfile.qsh -r sendfile.qsh usage: sendfile.qsh [-r] [-d destination] file ...
) print "$scriptname usage:" print " $scriptname [-r] [-d destination] file ..." exit 1 } # process the options resend=off while getopts rd: argname do case $argname in r) resend=on;; d) dest=$OPTARG;; ?) usage;; esac done # get rid of options shift $OPTIND-1 # make sure at least one filename # was entered if [[ -z ]] then usage fi filename= print "Sending $filename..." # ... more commands omitted ... exit 0 /home/jsmith $ sendfile.qsh sendfile.qsh usage: sendfile.qsh [-r] [-d destination] file ... /home/jsmith $ sendfile.qsh -x getopts: 001-0036 Option x is not valid. sendfile.qsh usage: sendfile.qsh [-r] [-d destination] file ... /home/jsmith $ sendfile.qsh -r sendfile.qsh usage: sendfile.qsh [-r] [-d destination] file ...
Figure 13.13: The usage function is called from two places in the script.
Return and the Exit Status
A function returns an exit status between zero and 255, inclusive. By convention, an exit status of zero indicates that a function completed normally. The caller is not required to do anything with the exit status.
Use the return utility if you want to assign the exit status. If you do not assign the exit status, the function returns the exit status of the last-executed command. Assignment of the exit status is illustrated here:
backup_file() { if [ -z ] then return 41 elif [ ! -e ] then return 42 elif [ ! -f ] then return 43 else cp .copy fi }
In this example, the exit status may be any of several values:
- Status 41No argument was passed to the function.
- Status 42The argument is not the name of an existing file.
- Status 43The argument is not the name of a regular file.
- Status 0The cp command completed successfully.
- Other non-zero statusThe cp command failed.
The exit and return utilities both set the exit status, but do not confuse the two. If you invoke the exit utility in a function, Qshell will shut down the entire script.
Because the function returns an exit status, you can code a function call wherever a condition is allowed. If you need to check the status code for a certain value, use the $? parameter in the caller. The following example illustrates both of these concepts:
if backup_file then : else echo "Backup of file failed; exit status was $?." >&2 fi
The backup_file function is invoked from an if command. If the function fails to make a copy of the file, the else clause is executed, sending a message to the standard error device.
Retrieving Output From Functions
Functions cannot modify the arguments that are passed to them. There are two ways to return output from a function. One way is to modify global variables , like this:
lastchar() { local length=${#1} if [ $length -gt 0 ] then clast=$(echo cut -c $length) else clast='' fi }
The lastchar function in this example copies the last character of the first argument into global variable clast . Since clast is not defined within the function, it is global and may be referenced outside of the function.
The other common way to retrieve data from a function is to write to stdout . You can use the command-substitution technique to direct the output to a variable. This is shown in Figure 13.14. The lcname function writes the lowercase equivalent of an argument to standard output. The command substitution forces the output into variable varx .
function lcname { echo tr '[:upper:]' '[:lower:]' ; } /home/jsmith $ varx= "Joe Smith" /home/jsmith $ echo $varx Joe Smith /home/jsmith $ varx=$(lcname "Joe Smith") /home/jsmith $ echo $varx joe smith /home/jsmith $
Figure 13.14: Functions can return data to calling routines by writing to stdout.
Summary
A Qshell function is like a subroutine, function, or procedure in other languages. Functions are defined within a process, so they execute more quickly than external files. Like functions and subroutines in other languages, functions promote modularity in Qshell scripts. Functions may contain local variables and receive parameters. Functions may be defined using the Bourne-shell syntax or the newer Korn-shell syntax. Like scripts, built-in utilities, and internal commands, functions are invoked by typing their names .
Chapter 14 Path Name Expansion
Категории