8.7.10

Bash Function Exit and Return

 

Exit and Return

exit status

Functions return a value, called an exit status. This is analogous to the exit status returned by a command. 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 [1] 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 24-7. Maximum of two numbers

#!/bin/bash
# max.sh: Maximum of two integers.

E_PARAM_ERR=250 # If less than 2 params passed to function.
EQUAL=251 # Return value if both params equal.
# Error values out of range of any
#+ params that might be fed to the function.

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
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).

Tip

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.
# The 'echo' seems unnecessary, but . . .
#+ it removes excess whitespace from the output.
}

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 24-8. Converting numbers to Roman numerals

#!/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
# Exercises:
# ---------
# 1) Explain how this function works.
# Hint: division by successive subtraction.
# 2) Extend to range of the function.
# Hint: use "echo" and command-substitution capture.
}


to_roman $num 100 C
num=$?
to_roman $num 90 LXXXX
num=$?
to_roman $num 50 L
num=$?
to_roman $num 40 XL
num=$?
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
# Successive calls to conversion function!
# Is this really necessary??? Can it be simplified?

echo

exit

See also Example 11-28.

Important

The largest positive integer a function can return is 255. 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 24-9. Testing large return values in a function

#!/bin/bash
# return-test.sh

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

return_test () # Returns whatever passed to it.
{
return $1
}

return_test 27 # o.k.
echo $? # Returns 27.

return_test 255 # Still o.k.
echo $? # Returns 255.

return_test 257 # Error!
echo $? # Returns 1 (return code for miscellaneous error).

# ======================================================
return_test -151896 # Do large negative numbers work?
echo $? # Will this return -151896?
# No! It returns 168.
# Version of Bash before 2.05b permitted
#+ large negative integer return values.
# Newer versions of Bash plug this loophole.
# This may break older scripts.
# Caution!
# ======================================================

exit 0

A workaround for obtaining large integer "return values" is to simply assign the "return value" to a global variable.

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


A more elegant method is to have the function echo its "return value to stdout," and then capture it by command substitution. See the discussion of this in Section 35.7.


Example 24-10. 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.
E_PARAM_ERR=-99999 # Not enough params passed to function.
# ^^^^^^ Out of range of any params that might be passed.

max2 () # "Returns" larger of two numbers.
{
if [ -z "$2" ]
then
echo $E_PARAM_ERR
return
fi

if [ "$1" -eq "$2" ]
then
echo $EQUAL
return
else
if [ "$1" -gt "$2" ]
then
retval=$1
else
retval=$2
fi
fi

echo $retval # Echoes (to stdout), rather than returning value.
# Why?
}


return_val=$(max2 33001 33997)
# ^^^^ Function name
# ^^^^^ ^^^^^ Params passed
# This is actually a form of command substitution:
#+ treating a function as if it were a command,
#+ and assigning the stdout of the function to the variable "return_val."


# ========================= OUTPUT ========================
if [ "$return_val" -eq "$E_PARAM_ERR" ]
then
echo "Error in parameters passed to comparison 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

# Exercises:
# ---------
# 1) Find a more elegant way of testing
#+ the parameters passed to the function.
# 2) Simplify the if/then structure at "OUTPUT."
# 3) Rewrite the script to take input from command-line parameters.

Here is another example of capturing a function "return value." Understanding it requires some knowledge of awk.

month_length ()  # Takes month number as an argument.
{ # Returns number of days in month.
monthD="31 28 31 30 31 30 31 31 30 31 30 31" # Declare as local?
echo "$monthD" | awk '{ print $'"${1}"' }' # Tricky.
# ^^^^^^^^^
# Parameter passed to function ($1 -- month number), then to awk.
# Awk sees this as "print $1 . . . print $12" (depending on month number)
# Template for passing a parameter to embedded awk script:
# $'"${script_parameter}"'

# Needs error checking for correct parameter range (1-12)
#+ and for February in leap year.
}

# ----------------------------------------------
# Usage example:
month=4 # April, for example (4th month).
days_in=$(month_length $month)
echo $days_in # 30
# ----------------------------------------------

See also Example A-7 and Example A-37.

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



 


http://tldp.org/LDP/abs/html/complexfunct.html

No comments: