| Advanced Bash-Scripting Guide: A complete guide to shell scripting, using Bash | ||
|---|---|---|
| Prev | Chapter 3. Tutorial / Reference | Next |
3.21. 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...
}
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 3-137. Simple function
1 #!/bin/bash
2
3 funky ()
4 {
5 echo This is a funky function.
6 echo Now exiting funky function.
7 }
8
9 # Note: function must precede call.
10
11 # Now, call the function.
12
13 funky
14
15 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. |
3.21.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 3-138. Function Taking Parameters
1 #!/bin/bash
2
3 func2 () {
4 if [ -z "$1" ]
5 # Checks if parameter #1 is zero length.
6 then
7 echo "-Parameter #1 is zero length.-" # Also if no parameter is passed.
8 else
9 echo "-Param #1 is \"$1\".-"
10 fi
11
12 if [ "$2" ]
13 then
14 echo "-Parameter #2 is \"$2\".-"
15 fi
16
17 return 0
18 }
19
20 echo
21
22 echo "Nothing passed."
23 func2 # Called with no params
24 echo
25
26
27 echo "Zero-length parameter passed."
28 func2 "" # Called with zero-length param
29 echo
30
31 echo "Null parameter passed."
32 func2 "$uninitialized_param" # Called with uninitialized param
33 echo
34
35 echo "One parameter passed."
36 func2 first # Called with one param
37 echo
38
39 echo "Two parameter passed."
40 func2 first second # Called with two params
41 echo
42
43 echo "\"\" \"second\" passed."
44 func2 "" second # Called with zero-length first parameter
45 echo # and ASCII string as a second one.
46
47 exit 0 |
![]() | 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 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 3-139. Maximum of two numbers
1 #!/bin/bash 2 3 PARAM_ERR=-198 # If less than 2 params passed to function. 4 EQUAL=-199 # Return value if both params equal. 5 6 max2 () # Returns larger of two numbers. 7 { 8 if [ -z "$2" ] 9 then 10 return $PARAM_ERR 11 fi 12 13 if [ "$1" -eq "$2" ] 14 then 15 return $EQUAL 16 else 17 if [ "$1" -gt "$2" ] 18 then 19 return $1 20 else 21 return $2 22 fi 23 fi 24 } 25 26 max2 33 34 27 return_val=$? 28 29 if [ "$return_val" -eq $PARAM_ERR ] 30 then 31 echo "Need to pass two parameters to the function." 32 elif [ "$return_val" -eq $EQUAL ] 33 then 34 echo "The two numbers are equal." 35 else 36 echo "The larger of the two numbers is $return_val." 37 fi 38 39 40 exit 0 41 42 # Exercise for the reader (easy): 43 # Convert this to an interactive script, 44 # that is, have the script ask for input (two numbers).
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 3-140. 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 ARG_ERR=65 13 LIMIT=200 14 15 if [ -z "$1" ] 16 then 17 echo "Usage: `basename $0` number-to-convert" 18 exit $ARG_ERR 19 fi 20 21 num=$1 22 if [ "$num" -gt $LIMIT ] 23 then 24 echo "Out of range!" 25 exit $OUT_OF_RANGE 26 fi 27 28 to_roman () 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 } 43 44 # Note: must declare function 45 # before first call to it. 46 47 to_roman $num 100 C 48 num=$? 49 to_roman $num 90 LXXXX 50 num=$? 51 to_roman $num 50 L 52 num=$? 53 to_roman $num 40 XL 54 num=$? 55 to_roman $num 10 X 56 num=$? 57 to_roman $num 9 IX 58 num=$? 59 to_roman $num 5 V 60 num=$? 61 to_roman $num 4 IV 62 num=$? 63 to_roman $num 1 I 64 65 echo 66 67 exit 0See also Example 3-66.
- redirecting the stdin of a function
A function is essentially a code block, which means its stdin can be redirected (as in Example 3-3).
Example 3-141. 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 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 $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 0There 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.
3.21.2. Local Variables and Recursion
- local variables
A variable declared as local is one that is visible only within the block of code in which it appears. In a function, a local variable has meaning only within that function block.
Example 3-142. Local variable visibility
1 #!/bin/bash 2 3 func () 4 { 5 local a=23 6 echo 7 echo "a in function is $a" 8 echo 9 } 10 11 func 12 13 # Now, see if local 'a' 14 # exists outside function. 15 16 echo "a outside function is $a" 17 echo 18 # Nope, 'a' not visible globally. 19 20 exit 0Local variables permit recursion (a recursive function is one that calls itself), but this practice generally involves much computational overhead and is definitely not recommended in a shell script. [3]
Example 3-143. Recursion, using a local variable
1 #!/bin/bash 2 3 # factorial 4 # --------- 5 6 7 # Does bash permit recursion? 8 # Well, yes, but... 9 # You gotta have rocks in your head to try it. 10 11 12 MAX_ARG=5 13 WRONG_ARGS=65 14 RANGE_ERR=66 15 16 17 if [ -z "$1" ] 18 then 19 echo "Usage: `basename $0` number" 20 exit $WRONG_ARGS 21 fi 22 23 if [ "$1" -gt $MAX_ARG ] 24 then 25 echo "Out of range (5 is maximum)." 26 # Let's get real now... 27 # If you want greater range than this, rewrite it in a real programming language. 28 exit $RANGE_ERR 29 fi 30 31 fact () 32 { 33 local number=$1 34 # Variable "number" must be declared as local otherwise this doesn't work. 35 if [ "$number" -eq 0 ] 36 then 37 factorial=1 38 else 39 let "decrnum = number - 1" 40 fact $decrnum # Recursive function call. 41 let "factorial = $number * $?" 42 fi 43 44 return $factorial 45 } 46 47 fact $1 48 echo "Factorial of $1 is $?." 49 50 exit 0See also Example A-10 for an example of recursion in a script. Be aware that recursion is resource-intensive and executes slowly, especially in a script.
Notes
| [1] | Indirect variable references (see Example 3-167) provide a clumsy sort of mechanism for passing variable pointers to functions.
| |
| [2] | The return command is a Bash builtin. | |
| [3] | Too many levels of recursion may crash a script with a segfault.
|

