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 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 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 permissable.
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"; } # 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. |
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 34-10). |
![]() | 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. |
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 [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 (easy):
44 # ---------------
45 # Convert this to an interactive script,
46 #+ 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-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 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 # Exercise:
43 # --------
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-28.
![]() | 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. Example 23-5. Testing large return values in 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.
Example 23-6. Comparing two large integers
See also Example A-8. 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-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. |
| [1] | Indirect variable references (see Example 35-2) provide a clumsy sort of mechanism for passing variable pointers to functions.
| |
| [2] | The return command is a Bash builtin. |