Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Cooper M.Advanced Bash scripting guide Rev1.4.2002.pdf
Скачиваний:
22
Добавлен:
23.08.2013
Размер:
4.82 Mб
Скачать

Complex Functions and Function Complexities

 

Advanced Bash-Scripting Guide:

 

Prev

Chapter 23. Functions

Next

23.1. Complex Functions and Function

Complexities

Functions may process arguments passed to them and return an exit status to the script for further processing.

function_name $arg1 $arg2

The function refers to the passed arguments by position (as if they were positional parameters), that is, $1, $2, and so forth.

Example 23-2. Function Taking Parameters

#!/bin/bash

 

func2

() {

 

if

[ -z "$1" ]

# Checks if parameter #1 is zero length.

then

echo "-Parameter #1 is zero length.-" # Also if no parameter is passed. else

echo "-Param #1 is \"$1\".-"

fi

if [ "$2" ] then

echo "-Parameter #2 is \"$2\".-"

fi

return 0

}

 

echo

 

echo "Nothing passed."

 

func2

# Called with no params

echo

 

echo "Zero-length parameter passed."

func2 ""

# Called with zero-length param

echo

 

echo "Null parameter passed."

func2 "$uninitialized_param" # Called with uninitialized param echo

echo "One parameter passed."

func2 first

# Called with one param

echo

 

http://tldp.org/LDP/abs/html/complexfunct.html (1 of 10) [7/15/2002 6:35:17 PM]

Complex Functions and Function Complexities

echo "Two parameters passed."

func2 first second

# Called with two params

echo

 

echo "\"\" \"second\"

passed."

func2 "" second

# Called with zero-length first parameter

echo

# and ASCII string as a second one.

exit 0

 

The shift command works on arguments passed to functions (see Example 34-6).

In contrast to certain other programming languages, shell scripts normally pass only value parameters to functions. [1] Variable names (which are actually pointers), if passed as parameters to functions, will be treated as string literals and cannot be dereferenced. Functions interpret their arguments literally.

Exit and Return

exit status

Functions return a value, called an exit status. The exit status may be explicitly specified by a return statement, otherwise it is the exit status of the last command in the function (0 if successful, and a non-zero error code if not). This exit status may be used in the script by referencing it as $?. This mechanism effectively permits script functions to have a "return value" similar to C functions.

return

Terminates a function. A return command [2] optionally takes an integer argument, which is returned to the calling script as the "exit status" of the function, and this exit status is assigned to the variable $?.

Example 23-3. Maximum of two numbers

#!/bin/bash

# max.sh: Maximum of two integers.

E_PARAM_ERR=-198

# If less than 2 params passed to function.

EQUAL=-199

# Return value if both params equal.

max2 ()

# Returns larger of two numbers.

{

# Note: numbers compared must be less than 257.

if [ -z "$2" ]

 

then

 

return $E_PARAM_ERR

fi

if [ "$1" -eq "$2" ] then

return $EQUAL else

if [ "$1" -gt "$2" ] then

http://tldp.org/LDP/abs/html/complexfunct.html (2 of 10) [7/15/2002 6:35:17 PM]

Complex Functions and Function Complexities

return $1 else

return $2

fi

fi

}

max2 33 34 return_val=$?

if [ "$return_val" -eq $E_PARAM_ERR ] then

echo "Need to pass two parameters to the function." elif [ "$return_val" -eq $EQUAL ]

then

echo "The two numbers are equal."

else

echo "The larger of the two numbers is $return_val."

fi

exit 0

#Exercise (easy):

#---------------

#Convert this to an interactive script,

#+ that is, have the script ask for input (two numbers).

For a function to return a string or array, use a dedicated variable.

count_lines_in_etc_passwd()

{

[[ -r /etc/passwd ]] && REPLY=$(echo $(wc -l < /etc/passwd))

#If /etc/passwd is readable, set REPLY to line count.

#Returns both a parameter value and status information.

}

if count_lines_in_etc_passwd then

echo "There are $REPLY lines in /etc/passwd." else

echo "Cannot count lines in /etc/passwd."

fi

# Thanks, S.C.

Example 23-4. Converting numbers to Roman numerals

http://tldp.org/LDP/abs/html/complexfunct.html (3 of 10) [7/15/2002 6:35:17 PM]

Complex Functions and Function Complexities

#!/bin/bash

#Arabic number to Roman numeral conversion

#Range: 0 - 200

#It's crude, but it works.

#Extending the range and otherwise improving the script is left as an exercise.

#Usage: roman number-to-convert

LIMIT=200

E_ARG_ERR=65

E_OUT_OF_RANGE=66

if [ -z "$1" ] then

echo "Usage: `basename $0` number-to-convert" exit $E_ARG_ERR

fi

num=$1

if [ "$num" -gt $LIMIT ] then

echo "Out of range!" exit $E_OUT_OF_RANGE

fi

 

to_roman ()

# Must declare function before first call to it.

{

 

number=$1

 

factor=$2

 

rchar=$3

 

let "remainder = number - factor" while [ "$remainder" -ge 0 ]

do

echo -n $rchar

let "number -= factor"

let "remainder = number - factor" done

return $number

#Exercise:

#--------

#Explain how this function works.

#Hint: division by successive subtraction.

}

to_roman $num 100 C num=$?

to_roman $num 90 LXXXX num=$?

to_roman $num 50 L num=$?

to_roman $num 40 XL num=$?

http://tldp.org/LDP/abs/html/complexfunct.html (4 of 10) [7/15/2002 6:35:17 PM]

Complex Functions and Function Complexities

to_roman $num 10 X num=$?

to_roman $num 9 IX num=$?

to_roman $num 5 V num=$?

to_roman $num 4 IV num=$?

to_roman $num 1 I

echo

exit 0

See also Example 10-27.

The largest positive integer a function can return is 256. The return command is closely tied to the concept of exit status, which accounts for this particular limitation. Fortunately, there are various workarounds for those situations requiring a large integer return value from a function.

Example 23-5. Testing large return values in a function

#!/bin/bash

#return-test.sh

#The largest positive value a function can return is 256.

return_test

()

# Returns whatever passed to it.

{

 

 

return $1

 

 

}

 

 

return_test

27

# o.k.

echo $?

 

# Returns 27.

return_test

256

# Still o.k.

echo $?

 

# Returns 256.

return_test

257

# Error!

echo $?

 

# Returns 1 (return code for miscellaneous error).

return_test

-151896

# However, large negative numbers work.

echo $?

 

# Returns -151896.

exit 0

As we have seen, a function can return a large negative value. This also permits returning large positive integer, using a bit of trickery.

An alternate method of accomplishing this is to simply assign the "return value" to a global variable.

http://tldp.org/LDP/abs/html/complexfunct.html (5 of 10) [7/15/2002 6:35:17 PM]

Complex Functions and Function Complexities

Return_Val=

#

Global variable to hold oversize return value of function.

alt_return_test

()

 

{

 

 

 

fvar=$1

 

 

 

Return_Val=$fvar

 

return

# Returns 0 (success).

 

}

 

 

 

alt_return_test

1

 

echo $?

 

 

# 0

echo "return value = $Return_Val"

# 1

alt_return_test

256

 

echo "return value = $Return_Val"

# 256

alt_return_test

257

 

echo "return value = $Return_Val"

# 257

alt_return_test

25701

 

echo "return value = $Return_Val"

#25701

 

 

 

 

Example 23-6. Comparing two large integers

#!/bin/bash

#max2.sh: Maximum of two LARGE integers.

#This is the previous "max.sh" example,

#modified to permit comparing large integers.

EQUAL=0

# Return value if both params equal.

MAXRETVAL=256

# Maximum positive return value from a function.

E_PARAM_ERR=-99999

# Parameter error.

E_NPARAM_ERR=99999

# "Normalized" parameter error.

max2 ()

# Returns larger of two numbers.

{

 

if [ -z "$2" ]

 

then

 

return $E_PARAM_ERR

fi

if [ "$1" -eq "$2" ] then

return $EQUAL else

if [ "$1" -gt "$2" ] then

retval=$1 else

retval=$2

fi

fi

http://tldp.org/LDP/abs/html/complexfunct.html (6 of 10) [7/15/2002 6:35:17 PM]

Complex Functions and Function Complexities

# --------------------------------------------------------------

#

#This is a workaround to enable returning a large integer

#from this function.

if [

"$retval" -gt

"$MAXRETVAL" ]

# If out of range,

then

 

 

#

then

let "retval = ((

0 - $retval ))"

#

adjust to a negative value.

# (( 0 - $VALUE )) changes the sign of VALUE.

fi

 

 

# Large *negative* return values permitted, fortunately.

# --------------------------------------------------------------

 

#

return $retval

 

 

}

 

 

max2 33001 33997

 

 

return_val=$?

 

 

# --------------------------------------------------------------------------

 

#

if [ "$return_val" -lt 0 ]

# If "adjusted" negative number,

then

 

# then

let "return_val = (( 0 - $return_val ))"

# renormalize to positive.

fi

 

# "Absolute value" of $return_val.

# --------------------------------------------------------------------------

 

#

if [ "$return_val" -eq "$E_NPARAM_ERR" ]

 

then

# Parameter error "flag" gets sign changed, too.

echo "Error: Too few parameters." elif [ "$return_val" -eq "$EQUAL" ]

then

echo "The two numbers are equal."

else

echo "The larger of the two numbers is $return_val."

fi

exit 0

See also Example A-8.

Exercise: Using what we have just learned, extend the previous Roman numerals example to accept arbitrarily large input.

Redirection

Redirecting the stdin of a function

A function is essentially a code block, which means its stdin can be redirected (as in Example 4-1).

Example 23-7. Real name from username

http://tldp.org/LDP/abs/html/complexfunct.html (7 of 10) [7/15/2002 6:35:17 PM]

Complex Functions and Function Complexities

#!/bin/bash

# From username, gets "real name" from /etc/passwd.

ARGCOUNT=1 # Expect one arg.

E_WRONGARGS=65

file=/etc/passwd pattern=$1

if [ $# -ne "$ARGCOUNT" ] then

echo "Usage: `basename $0` USERNAME" exit $E_WRONGARGS

fi

 

 

file_excerpt ()

#

Scan file for pattern, the print relevant portion of line.

{

 

 

while read line

#

while does not necessarily need "[ condition]"

do

 

 

echo "$line" | grep $1 | awk -F":" '{ print $5 }' # Have awk use ":" delimiter. done

} <$file # Redirect into function's stdin.

file_excerpt $pattern

#Yes, this entire script could be reduced to

#grep PATTERN /etc/passwd | awk -F":" '{ print $5 }'

#or

#awk -F: '/PATTERN/ {print $5}'

#or

#awk -F: '($1 == "username") { print $5 }' # real name from username

#However, it might not be as instructive.

exit 0

There is an alternative, and perhaps less confusing method of redirecting a function's stdin. This involves redirecting the stdin to an embedded bracketed code block within the function.

#Instead of: Function ()

{

...

}< file

#Try this: Function ()

{

{

...

} < file

}

# Similarly,

http://tldp.org/LDP/abs/html/complexfunct.html (8 of 10) [7/15/2002 6:35:17 PM]

Complex Functions and Function Complexities

Function ()

# This works.

{

 

{

 

echo $*

 

} | tr a b

 

}

 

Function ()

# This doesn't work.

{

 

echo $*

 

} | tr a b

# A nested code block is mandatory here.

# Thanks, S.C.

Notes

[1]Indirect variable references (see Example 35-2) provide a clumsy sort of mechanism for passing variable pointers to functions.

#!/bin/bash

ITERATIONS=3 # How many times to get input. icount=1

my_read () {

#Called with my_read varname,

#outputs the previous value between brackets as the default value,

#then asks for a new value.

local local_var

 

echo -n "Enter a value

"

eval 'echo -n "[$'$1']

"' # Previous value.

read

local_var

 

[ -n

"$local_var" ] &&

eval $1=\$local_var

# "And-list": if "local_var" then set "$1" to its value.

}

echo

while [ "$icount" -le "$ITERATIONS" ] do

my_read var

echo "Entry #$icount = $var" let "icount += 1"

echo done

# Thanks to Stephane Chazelas for providing this instructive example.

http://tldp.org/LDP/abs/html/complexfunct.html (9 of 10) [7/15/2002 6:35:17 PM]

Complex Functions and Function Complexities

exit 0

[2]The return command is a Bash builtin.

Prev

Home

Next

Functions

Up

Local Variables

http://tldp.org/LDP/abs/html/complexfunct.html (10 of 10) [7/15/2002 6:35:17 PM]