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
# Functions and parameters

DEFAULT=default                             # Default param value.

func2 () {
   if [ -z "$1" ]                           # Is parameter #1 zero length?
   then
     echo "-Parameter #1 is zero length.-"  # Or no parameter passed.
   else
     echo "-Param #1 is \"$1\".-"
   fi

   variable=${1-$DEFAULT}                   #  What does
   echo "variable = $variable"              #+ parameter substitution show?
                                            #  ---------------------------
                                            #  It distinguishes between
                                            #+ no param and a null param.

   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

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

Important

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

Note

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 $?.

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

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.

See also Example 10-27.

Important

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.

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

# -------------------------------------------------------------- #
# 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 3-1).

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,

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.

exit 0

[2]

The return command is a Bash builtin.