Chapter 23. Functions

Like "real" programming languages, Bash has functions, though in a somewhat limited implementation. A function is a subroutine, a code block that implements a set of operations, a "black box" that performs a specified task. Wherever there is repetitive code, when a task repeats with only slight variations, then consider using a function.

function function_name {
command...
}

or

function_name () {
command...
}

This second form will cheer the hearts of C programmers (and is more portable).

As in C, the function's opening bracket may optionally appear on the second line.

function_name ()
{
command...
}

Functions are called, triggered, simply by invoking their names.


Example 23-1. Simple function

   1 #!/bin/bash
   2 
   3 funky ()
   4 {
   5   echo "This is a funky function."
   6   echo "Now exiting funky function."
   7 } # Function declaration must precede call.
   8 
   9   # Now, call the function.
  10 
  11 funky
  12 
  13 exit 0

The function definition must precede the first call to it. There is no method of "declaring" the function, as, for example, in C.
   1 # f1
   2 # Will give an error message, since function "f1" not yet defined.
   3 
   4 # However...
   5 
   6 	  
   7 f1 ()
   8 {
   9   echo "Calling function \"f2\" from within function \"f1\"."
  10   f2
  11 }
  12 
  13 f2 ()
  14 {
  15   echo "Function \"f2\"."
  16 }
  17 
  18 f1  # Function "f2" is not actually called until this point,
  19     # although it is referenced before its definition.
  20     # This is permissable.
  21     
  22 # Thanks, S.C.

It is even possible to nest a function within another function, although this is not very useful.
   1 f1 ()
   2 {
   3 
   4   f2 () # nested
   5   {
   6     echo "Function \"f2\", inside \"f1\"."
   7   }
   8 
   9 }  
  10 
  11 # f2
  12 # Gives an error message.
  13 
  14 f1  # Does nothing, since calling "f1" does not automatically call "f2".
  15 f2  # Now, it's all right to call "f2",
  16     # since its definition has been made visible by calling "f1".
  17 
  18 # Thanks, S.C.

Function declarations can appear in unlikely places, even where a command would otherwise go.
   1 ls -l | foo() { echo "foo"; }  # Permissable, but useless.
   2 
   3 
   4 
   5 if [ "$USER" = bozo ]
   6 then
   7   bozo_greet ()   # Function definition embedded in an if/then construct.
   8   {
   9     echo "Hello, Bozo."
  10   }
  11 fi  
  12 
  13 bozo_greet        # Works only for Bozo, and other users get an error.
  14 
  15 
  16 
  17 # Something like this might be useful in some contexts.
  18 NO_EXIT=1   # Will enable function definition below.
  19 
  20 [[ $NO_EXIT -eq 1 ]] && exit() { true; }     # Function definition in an "and-list".
  21 # If $NO_EXIT is 1, declares "exit ()".
  22 # This disables the "exit" builtin by aliasing it to "true".
  23 
  24 exit  # Invokes "exit ()" function, not "exit" builtin.
  25 
  26 # Thanks, S.C.

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.

   1 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

   1 #!/bin/bash
   2 
   3 func2 () {
   4    if [ -z "$1" ]                    # Checks if parameter #1 is zero length.
   5    then
   6      echo "-Parameter #1 is zero length.-"  # Also if no parameter is passed.
   7    else
   8      echo "-Param #1 is \"$1\".-"
   9    fi
  10 
  11    if [ "$2" ]
  12    then
  13      echo "-Parameter #2 is \"$2\".-"
  14    fi
  15 
  16    return 0
  17 }
  18 
  19 echo
  20    
  21 echo "Nothing passed."   
  22 func2                          # Called with no params
  23 echo
  24 
  25 
  26 echo "Zero-length parameter passed."
  27 func2 ""                       # Called with zero-length param
  28 echo
  29 
  30 echo "Null parameter passed."
  31 func2 "$uninitialized_param"   # Called with uninitialized param
  32 echo
  33 
  34 echo "One parameter passed."   
  35 func2 first           # Called with one param
  36 echo
  37 
  38 echo "Two parameters passed."   
  39 func2 first second    # Called with two params
  40 echo
  41 
  42 echo "\"\" \"second\" passed."
  43 func2 "" second       # Called with zero-length first parameter
  44 echo                  # and ASCII string as a second one.
  45 
  46 exit 0

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


Example 23-3. Maximum of two numbers

   1 #!/bin/bash
   2 # max.sh: Maximum of two integers.
   3 
   4 E_PARAM_ERR=-198    # If less than 2 params passed to function.
   5 EQUAL=-199          # Return value if both params equal.
   6 
   7 max2 ()             # Returns larger of two numbers.
   8 {                   # Note: numbers compared must be less than 257.
   9 if [ -z "$2" ]
  10 then
  11   return $E_PARAM_ERR
  12 fi
  13 
  14 if [ "$1" -eq "$2" ]
  15 then
  16   return $EQUAL
  17 else
  18   if [ "$1" -gt "$2" ]
  19   then
  20     return $1
  21   else
  22     return $2
  23   fi
  24 fi
  25 }
  26 
  27 max2 33 34
  28 return_val=$?
  29 
  30 if [ "$return_val" -eq $E_PARAM_ERR ]
  31 then
  32   echo "Need to pass two parameters to the function."
  33 elif [ "$return_val" -eq $EQUAL ]
  34   then
  35     echo "The two numbers are equal."
  36 else
  37     echo "The larger of the two numbers is $return_val."
  38 fi  
  39 
  40   
  41 exit 0
  42 
  43 # Exercise for the reader (easy):
  44 # Convert this to an interactive script,
  45 # that is, have the script ask for input (two numbers).

Tip

For a function to return a string or array, use a dedicated variable.
   1 count_lines_in_etc_passwd()
   2 {
   3   [[ -r /etc/passwd ]] && REPLY=$(echo $(wc -l < /etc/passwd))
   4   # If /etc/passwd is readable, set REPLY to line count.
   5   # Returns both a parameter value and status information.
   6 }
   7 
   8 if count_lines_in_etc_passwd
   9 then
  10   echo "There are $REPLY lines in /etc/passwd."
  11 else
  12   echo "Cannot count lines in /etc/passwd."
  13 fi  
  14 
  15 # Thanks, S.C.


Example 23-4. Converting numbers to Roman numerals

   1 #!/bin/bash
   2 
   3 # Arabic number to Roman numeral conversion
   4 # Range: 0 - 200
   5 # It's crude, but it works.
   6 
   7 # Extending the range and otherwise improving the script
   8 # is left as an exercise for the reader.
   9 
  10 # Usage: roman number-to-convert
  11 
  12 LIMIT=200
  13 E_ARG_ERR=65
  14 E_OUT_OF_RANGE=66
  15 
  16 if [ -z "$1" ]
  17 then
  18   echo "Usage: `basename $0` number-to-convert"
  19   exit $E_ARG_ERR
  20 fi  
  21 
  22 num=$1
  23 if [ "$num" -gt $LIMIT ]
  24 then
  25   echo "Out of range!"
  26   exit $E_OUT_OF_RANGE
  27 fi  
  28 
  29 to_roman ()   # Must declare function before first call to it.
  30 {
  31 number=$1
  32 factor=$2
  33 rchar=$3
  34 let "remainder = number - factor"
  35 while [ "$remainder" -ge 0 ]
  36 do
  37   echo -n $rchar
  38   let "number -= factor"
  39   let "remainder = number - factor"
  40 done  
  41 
  42 return $number
  43        # Exercise for the reader:
  44        # Explain how this function works.
  45        # Hint: division by successive subtraction.
  46 }
  47    
  48 
  49 to_roman $num 100 C
  50 num=$?
  51 to_roman $num 90 LXXXX
  52 num=$?
  53 to_roman $num 50 L
  54 num=$?
  55 to_roman $num 40 XL
  56 num=$?
  57 to_roman $num 10 X
  58 num=$?
  59 to_roman $num 9 IX
  60 num=$?
  61 to_roman $num 5 V
  62 num=$?
  63 to_roman $num 4 IV
  64 num=$?
  65 to_roman $num 1 I
  66 
  67 echo
  68 
  69 exit 0

See also Example 10-26.

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 workarounds for those situations requiring a large integer return value from a function.


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

   1 #!/bin/bash
   2 # return-test.sh
   3 
   4 # The largest positive value a function can return is 256.
   5 
   6 return_test ()         # Returns whatever passed to it.
   7 {
   8   return $1
   9 }
  10 
  11 return_test 27         # o.k.
  12 echo $?                # Returns 27.
  13   
  14 return_test 256        # Still o.k.
  15 echo $?                # Returns 256.
  16 
  17 return_test 257        # Error!
  18 echo $?                # Returns 1 (return code for miscellaneous error).
  19 
  20 return_test -151896    # However, large negative numbers work.
  21 echo $?                # Returns -151896.
  22 
  23 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.


Example 23-6. Comparing two large integers

   1 #!/bin/bash
   2 # max2.sh: Maximum of two LARGE integers.
   3 
   4 # This is the previous "max.sh" example,
   5 # modified to permit comparing large integers.
   6 
   7 EQUAL=0             # Return value if both params equal.
   8 MAXRETVAL=256       # Maximum positive return value from a function.
   9 E_PARAM_ERR=-99999  # Parameter error.
  10 E_NPARAM_ERR=99999  # "Normalized" parameter error.
  11 
  12 max2 ()             # Returns larger of two numbers.
  13 {
  14 if [ -z "$2" ]
  15 then
  16   return $E_PARAM_ERR
  17 fi
  18 
  19 if [ "$1" -eq "$2" ]
  20 then
  21   return $EQUAL
  22 else
  23   if [ "$1" -gt "$2" ]
  24   then
  25     retval=$1
  26   else
  27     retval=$2
  28   fi
  29 fi
  30 
  31 # -------------------------------------------------------------- #
  32 # This is a workaround to enable returning a large integer
  33 # from this function.
  34 if [ "$retval" -gt "$MAXRETVAL" ]    # If out of range,
  35 then                                 # then
  36   let "retval = (( 0 - $retval ))"   # adjust to a negative value.
  37   # (( 0 - $VALUE )) changes the sign of VALUE.
  38 fi
  39 # Large *negative* return values permitted, fortunately.
  40 # -------------------------------------------------------------- #
  41 
  42 return $retval
  43 }
  44 
  45 max2 33001 33997
  46 return_val=$?
  47 
  48 # -------------------------------------------------------------------------- #
  49 if [ "$return_val" -lt 0 ]                  # If "adjusted" negative number,
  50 then                                        # then
  51   let "return_val = (( 0 - $return_val ))"  # renormalize to positive.
  52 fi                                          # "Absolute value" of $return_val.  
  53 # -------------------------------------------------------------------------- #
  54 
  55 
  56 if [ "$return_val" -eq "$E_NPARAM_ERR" ]
  57 then                   # Parameter error "flag" gets sign changed, too.
  58   echo "Error: Too few parameters."
  59 elif [ "$return_val" -eq "$EQUAL" ]
  60   then
  61     echo "The two numbers are equal."
  62 else
  63     echo "The larger of the two numbers is $return_val."
  64 fi  
  65   
  66 exit 0

See also Example A-6.

Exercise for the reader: 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

   1 #!/bin/bash
   2 
   3 # From username, gets "real name" from /etc/passwd.
   4 
   5 ARGCOUNT=1  # Expect one arg.
   6 E_WRONGARGS=65
   7 
   8 file=/etc/passwd
   9 pattern=$1
  10 
  11 if [ $# -ne "$ARGCOUNT" ]
  12 then
  13   echo "Usage: `basename $0` USERNAME"
  14   exit $E_WRONGARGS
  15 fi  
  16 
  17 file_excerpt ()  # Scan file for pattern, the print relevant portion of line.
  18 {
  19 while read line  # while does not necessarily need "[ condition]"
  20 do
  21   echo "$line" | grep $1 | awk -F":" '{ print $5 }'  # Have awk use ":" delimiter.
  22 done
  23 } <$file  # Redirect into function's stdin.
  24 
  25 file_excerpt $pattern
  26 
  27 # Yes, this entire script could be reduced to
  28 #       grep PATTERN /etc/passwd | awk -F":" '{ print $5 }'
  29 # or
  30 #       awk -F: '/PATTERN/ {print $5}'
  31 # or
  32 #       awk -F: '($1 == "username") { print $5 }' # real name from username
  33 # However, it might not be as instructive.
  34 
  35 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.
   1 # Instead of:
   2 Function ()
   3 {
   4  ...
   5  } < file
   6 
   7 # Try this:
   8 Function ()
   9 {
  10   {
  11     ...
  12    } < file
  13 }
  14 
  15 # Similarly,
  16 
  17 Function ()  # This works.
  18 {
  19   {
  20    echo $*
  21   } | tr a b
  22 }
  23 
  24 Function ()  # This doesn't work.
  25 {
  26   echo $*
  27 } | tr a b   # A nested code block is mandatory here.
  28 
  29 
  30 # Thanks, S.C.

Notes

[1]

Indirect variable references (see Example 35-2) provide a clumsy sort of mechanism for passing variable pointers to functions.
   1 #!/bin/bash
   2 
   3 ITERATIONS=3  # How many times to get input.
   4 icount=1
   5 
   6 my_read () {
   7   # Called with my_read varname,
   8   # outputs the previous value between brackets as the default value,
   9   # then asks for a new value.
  10 
  11   local local_var
  12 
  13   echo -n "Enter a value "
  14   eval 'echo -n "[$'$1'] "'  # Previous value.
  15   read local_var
  16   [ -n "$local_var" ] && eval $1=\$local_var
  17 
  18   # "And-list": if "local_var" then set "$1" to its value.
  19 }
  20 
  21 echo
  22 
  23 while [ "$icount" -le "$ITERATIONS" ]
  24 do
  25   my_read var
  26   echo "Entry #$icount = $var"
  27   let "icount += 1"
  28   echo
  29 done  
  30 
  31 
  32 # Thanks to Stephane Chazelas for providing this instructive example.
  33 
  34 exit 0

[2]

The return command is a Bash builtin.