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 in procedure, then consider using a function.
function function_name {
command...
}
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...
}
A function may be "compacted" into a single line.
In this case, however, a semicolon must follow the final command in the function.
|
Functions are called, triggered, simply by invoking their names. A function call is equivalent to a command.
Example 23-1. Simple functions
1 #!/bin/bash 2 3 JUST_A_SECOND=1 4 5 funky () 6 { # This is about as simple as functions get. 7 echo "This is a funky function." 8 echo "Now exiting funky function." 9 } # Function declaration must precede call. 10 11 12 fun () 13 { # A somewhat more complex function. 14 i=0 15 REPEATS=30 16 17 echo 18 echo "And now the fun really begins." 19 echo 20 21 sleep $JUST_A_SECOND # Hey, wait a second! 22 while [ $i -lt $REPEATS ] 23 do 24 echo "----------FUNCTIONS---------->" 25 echo "<------------ARE-------------" 26 echo "<------------FUN------------>" 27 echo 28 let "i+=1" 29 done 30 } 31 32 # Now, call the functions. 33 34 funky 35 fun 36 37 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 declare -f f1 # This doesn't help either. 5 f1 # Still an error message. 6 7 # However... 8 9 10 f1 () 11 { 12 echo "Calling function \"f2\" from within function \"f1\"." 13 f2 14 } 15 16 f2 () 17 { 18 echo "Function \"f2\"." 19 } 20 21 f1 # Function "f2" is not actually called until this point, 22 #+ although it is referenced before its definition. 23 # This is permissible. 24 25 # 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 # Gives an error message. 12 # Even a preceding "declare -f f2" wouldn't help. 13 14 echo 15 16 f1 # Does nothing, since calling "f1" does not automatically call "f2". 17 f2 # Now, it's all right to call "f2", 18 #+ since its definition has been made visible by calling "f1". 19 20 # Thanks, S.C. |
Function declarations can appear in unlikely places, even where a command would otherwise go.
1 ls -l | foo() { echo "foo"; } # Permissible, 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 27 28 # Or, similarly: 29 filename=file1 30 31 [ -f "$filename" ] && 32 foo () { rm -f "$filename"; echo "File "$filename" deleted."; } || 33 foo () { echo "File "$filename" not found."; touch bar; } 34 35 foo 36 37 # Thanks, S.C. and Christopher Head |
What happens when different versions of the same function appear in a script?
|
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 # Functions and parameters 3 4 DEFAULT=default # Default param value. 5 6 func2 () { 7 if [ -z "$1" ] # Is parameter #1 zero length? 8 then 9 echo "-Parameter #1 is zero length.-" # Or no parameter passed. 10 else 11 echo "-Param #1 is \"$1\".-" 12 fi 13 14 variable=${1-$DEFAULT} # What does 15 echo "variable = $variable" #+ parameter substitution show? 16 # --------------------------- 17 # It distinguishes between 18 #+ no param and a null param. 19 20 if [ "$2" ] 21 then 22 echo "-Parameter #2 is \"$2\".-" 23 fi 24 25 return 0 26 } 27 28 echo 29 30 echo "Nothing passed." 31 func2 # Called with no params 32 echo 33 34 35 echo "Zero-length parameter passed." 36 func2 "" # Called with zero-length param 37 echo 38 39 echo "Null parameter passed." 40 func2 "$uninitialized_param" # Called with uninitialized param 41 echo 42 43 echo "One parameter passed." 44 func2 first # Called with one param 45 echo 46 47 echo "Two parameters passed." 48 func2 first second # Called with two params 49 echo 50 51 echo "\"\" \"second\" passed." 52 func2 "" second # Called with zero-length first parameter 53 echo # and ASCII string as a second one. 54 55 exit 0 |
The shift command works on arguments passed to functions (see Example 33-16). |
But, what about command-line arguments passed to the script? Does a function see them? Well, let's clear up the confusion.
Example 23-3. Functions and command-line args passed to the script
1 #!/bin/bash 2 # func-cmdlinearg.sh 3 # Call this script with a command-line argument, 4 #+ something like $0 arg1. 5 6 7 func () 8 9 { 10 echo "$1" 11 } 12 13 echo "First call to function: no arg passed." 14 echo "See if command-line arg is seen." 15 func 16 # No! Command-line arg not seen. 17 18 echo "============================================================" 19 echo 20 echo "Second call to function: command-line arg passed explicitly." 21 func $1 22 # Now it's seen! 23 24 exit 0 |
In contrast to certain other programming languages, shell scripts normally pass only value parameters to functions. Variable names (which are actually pointers), if passed as parameters to functions, will be treated as string literals. Functions interpret their arguments literally.
Indirect variable references (see Example 34-2) provide a clumsy sort of mechanism for passing variable pointers to functions.
Example 23-4. Passing an indirect reference to a function
1 #!/bin/bash 2 # ind-func.sh: Passing an indirect reference to a function. 3 4 echo_var () 5 { 6 echo "$1" 7 } 8 9 message=Hello 10 Hello=Goodbye 11 12 echo_var "$message" # Hello 13 # Now, let's pass an indirect reference to the function. 14 echo_var "${!message}" # Goodbye 15 16 echo "-------------" 17 18 # What happens if we change the contents of "hello" variable? 19 Hello="Hello, again!" 20 echo_var "$message" # Hello 21 echo_var "${!message}" # Hello, again! 22 23 exit 0 |
The next logical question is whether parameters can be dereferenced after being passed to a function.
Example 23-5. Dereferencing a parameter passed to a function
1 #!/bin/bash 2 # dereference.sh 3 # Dereferencing parameter passed to a function. 4 # Script by Bruce W. Clare. 5 6 dereference () 7 { 8 y=\$"$1" # Name of variable. 9 echo $y # $Junk 10 11 x=`eval "expr \"$y\" "` 12 echo $1=$x 13 eval "$1=\"Some Different Text \"" # Assign new value. 14 } 15 16 Junk="Some Text" 17 echo $Junk "before" # Some Text before 18 19 dereference Junk 20 echo $Junk "after" # Some Different Text after 21 22 exit 0 |
Example 23-6. Again, dereferencing a parameter passed to a function
1 #!/bin/bash 2 # ref-params.sh: Dereferencing a parameter passed to a function. 3 # (Complex Example) 4 5 ITERATIONS=3 # How many times to get input. 6 icount=1 7 8 my_read () { 9 # Called with my_read varname, 10 #+ outputs the previous value between brackets as the default value, 11 #+ then asks for a new value. 12 13 local local_var 14 15 echo -n "Enter a value " 16 eval 'echo -n "[$'$1'] "' # Previous value. 17 # eval echo -n "[\$$1] " # Easier to understand, 18 #+ but loses trailing space in user prompt. 19 read local_var 20 [ -n "$local_var" ] && eval $1=\$local_var 21 22 # "And-list": if "local_var" then set "$1" to its value. 23 } 24 25 echo 26 27 while [ "$icount" -le "$ITERATIONS" ] 28 do 29 my_read var 30 echo "Entry #$icount = $var" 31 let "icount += 1" 32 echo 33 done 34 35 36 # Thanks to Stephane Chazelas for providing this instructive example. 37 38 exit 0 |
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.
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 23-7. Maximum of two numbers
1 #!/bin/bash 2 # max.sh: Maximum of two integers. 3 4 E_PARAM_ERR=250 # If less than 2 params passed to function. 5 EQUAL=251 # Return value if both params equal. 6 # Error values out of range of any 7 #+ params that might be fed to the function. 8 9 max2 () # Returns larger of two numbers. 10 { # Note: numbers compared must be less than 257. 11 if [ -z "$2" ] 12 then 13 return $E_PARAM_ERR 14 fi 15 16 if [ "$1" -eq "$2" ] 17 then 18 return $EQUAL 19 else 20 if [ "$1" -gt "$2" ] 21 then 22 return $1 23 else 24 return $2 25 fi 26 fi 27 } 28 29 max2 33 34 30 return_val=$? 31 32 if [ "$return_val" -eq $E_PARAM_ERR ] 33 then 34 echo "Need to pass two parameters to the function." 35 elif [ "$return_val" -eq $EQUAL ] 36 then 37 echo "The two numbers are equal." 38 else 39 echo "The larger of the two numbers is $return_val." 40 fi 41 42 43 exit 0 44 45 # Exercise (easy): 46 # --------------- 47 # Convert this to an interactive script, 48 #+ that is, have the script ask for input (two numbers). |
For a function to return a string or array, use a dedicated variable.
|
Example 23-8. 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 is left as an exercise. 8 9 # Usage: roman number-to-convert 10 11 LIMIT=200 12 E_ARG_ERR=65 13 E_OUT_OF_RANGE=66 14 15 if [ -z "$1" ] 16 then 17 echo "Usage: `basename $0` number-to-convert" 18 exit $E_ARG_ERR 19 fi 20 21 num=$1 22 if [ "$num" -gt $LIMIT ] 23 then 24 echo "Out of range!" 25 exit $E_OUT_OF_RANGE 26 fi 27 28 to_roman () # Must declare function before first call to it. 29 { 30 number=$1 31 factor=$2 32 rchar=$3 33 let "remainder = number - factor" 34 while [ "$remainder" -ge 0 ] 35 do 36 echo -n $rchar 37 let "number -= factor" 38 let "remainder = number - factor" 39 done 40 41 return $number 42 # Exercises: 43 # --------- 44 # 1) Explain how this function works. 45 # Hint: division by successive subtraction. 46 # 2) Extend to range of the function. 47 # Hint: use "echo" and command-substitution capture. 48 } 49 50 51 to_roman $num 100 C 52 num=$? 53 to_roman $num 90 LXXXX 54 num=$? 55 to_roman $num 50 L 56 num=$? 57 to_roman $num 40 XL 58 num=$? 59 to_roman $num 10 X 60 num=$? 61 to_roman $num 9 IX 62 num=$? 63 to_roman $num 5 V 64 num=$? 65 to_roman $num 4 IV 66 num=$? 67 to_roman $num 1 I 68 # Successive calls to conversion function! 69 # Is this really necessary??? Can it be simplified? 70 71 echo 72 73 exit |
See also Example 10-28.
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 23-9. Testing large return values in a function
A workaround for obtaining large integer "return values" is to simply assign the "return value" to a global variable.
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 33.8. Example 23-10. Comparing two large integers
Here is another example of capturing a function "return value." Understanding it requires some knowledge of awk.
See also Example A-7 and Example A-39. Exercise: Using what we have just learned, extend the previous Roman numerals example to accept arbitrarily large input. |
A function is essentially a code block, which means its stdin can be redirected (as in Example 3-1).
Example 23-11. Real name from username
1 #!/bin/bash 2 # realname.sh 3 # 4 # From username, gets "real name" from /etc/passwd. 5 6 7 ARGCOUNT=1 # Expect one arg. 8 E_WRONGARGS=65 9 10 file=/etc/passwd 11 pattern=$1 12 13 if [ $# -ne "$ARGCOUNT" ] 14 then 15 echo "Usage: `basename $0` USERNAME" 16 exit $E_WRONGARGS 17 fi 18 19 file_excerpt () # Scan file for pattern, 20 { #+ then print relevant portion of line. 21 while read line # "while" does not necessarily need [ condition ] 22 do 23 echo "$line" | grep $1 | awk -F":" '{ print $5 }' 24 # Have awk use ":" delimiter. 25 done 26 } <$file # Redirect into function's stdin. 27 28 file_excerpt $pattern 29 30 # Yes, this entire script could be reduced to 31 # grep PATTERN /etc/passwd | awk -F":" '{ print $5 }' 32 # or 33 # awk -F: '/PATTERN/ {print $5}' 34 # or 35 # awk -F: '($1 == "username") { print $5 }' # real name from username 36 # However, it might not be as instructive. 37 38 exit 0 |
There is an alternate, 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. |
[1] | The return command is a Bash builtin. |