subroutine
. All programming languages have some subroutine
mechanism whether it is known as a function, procedure, or method. The subroutine is
a means of breaking programs into smaller chunks of code. The subroutine supports a
concept called
modularity
(writing your code in modules). Through modularity, it is often
easier to design, implement, and debug your program code.
Although functions are not mandatory, or even required, the use of functions in Bash
provides a mechanism to support
reusable
code. You might for instance define a number
of functions in a file and then load that file into any script that needs to call upon any of
those functions. If you were to define a function that searched a directory for bad permis-
sions of files, you could call upon this function from many other scripts where you felt this
operation would be useful. Thus, defining the function once allows you to reuse it time and
time again.
7.9.1 Defining Bash Functions
In Bash, the function is somewhat different from the ordinary subroutine for several rea-
sons. First, since Bash is interpreted, the placement of the function definition can be any-
where whether in a file by itself, amid other instructions in a shell script, defined at the
command line, or in a file of other functions. Once the function has been defined to the
interpreter, it can be called.
Second, functions receive parameters. The typical notation in most programming lan-
guages for defining parameters is to enumerate them inside of parentheses immediately
after the function’s name. For instance, a C function which expects three parameters
might be called by
someFunction(a, b, 10);
and the function’s header might be
described as
void someFunction(int a, int b, int c)
294
◾
Linux with Operating System Concepts
But in Bash, you do not pass parameters by the parentheses. Instead, you pass them
similar to passing parameters to a script by placing the parameters after the function name
on the same line. Then, rather than receiving the parameters in a header as shown with
the C function someFunction above, the parameters are accessed using $1, $2, and so on.
Note that parameters passed in Bash all qualify as
optional
parameters. This means
that the function does not require parameters or that the number of parameters passed
to the function can vary. Most programming languages have a mechanism for optional
parameters, but it is not the default. For instance, the previous example of a C function
expects exactly three parameters. If you were to write a function call that contained only
two parameters or had four parameters, you would receive a syntax error when you com-
piled that C code because it did not meet the expectations of the function. In C, you would
have to specifically indicate if any parameters were optional. In Bash, all parameters are
optional. If, in your function, you reference $1 and no parameters were passed to the func-
tion, then $1 has the NULL value.
When you define your function, you may but do not have to precede the function’s name
with the word function. If you do use the word function, then you omit the parentheses.
For instance, we could define someFunction either as
someFunction() {
. . .
}
or as
function someFunction {
. . .
}
You can also specify the function’s body on the same line as the function header itself.
If you do so though, you must follow every instruction with a semicolon, including the last
instruction. By separating your function body instructions on separate lines, you do not
need the semicolons. Here are three versions of the same, simple function:
foo() {
echo Function foo
echo Illustrates syntax
}
function foo {
echo Function foo
echo Illustrates syntax
}
foo() {echo Function foo; echo Illustrates syntax;}
Shell Scripting
◾
295
7.9.2 Using Functions
The placement of a function is not necessarily important as long as the function is defined
before
it is called. If you place a function in a file of functions, say functions.sh, then in a
script which will use those functions, you will want to execute the functions.sh file first.
You can do this in one of two ways.
•
./functions.sh
•
source functions.sh
If your function is in the same file as a script which calls upon the function, the func-
tion
must
be defined in the file earlier than the function call. However, the script can place
that function after other script code. The following skeleton of a script file illustrates the
function’s placement.
#!/bin/bash
// script instructions
//function f1 defined
//more script instructions
//call to f1
//more script instructions
Functions can but do not have to return a value. Most functions in Bash scripts tend
to receive parameters and operate on those parameters, outputting results (if any) but
not returning values. If you return a value, it is often used to specify some error code.
Functions for the most part then are similar to ordinary scripts. They can contain input,
output, assignment, selection, and iteration instructions. Mostly, functions will be short.
Here is an example of a simple function. It expects to receive a file name as a parameter.
If the file exists and is a regular file, and it is executable, the file is executed. Otherwise, an
error message is output.
runIt() {
if [[ -f $1 && -x $1 ]]; then ./$1
else echo Error, $1 does not exist or not executable
fi
}
Now we have the code that utilizes
runIt
.
for file in $@; do
runIt $file
done
Obviously, there is no need to have the function runIt. We could have just as easily (or
more easily) placed the if statement inside of the for loop and replaced
$1
with
$file
.
296
◾
Linux with Operating System Concepts
The function
runIt
might be useful though if we feel that other shell scripts might
utilize it.
7.9.3 Functions and Variables
Local
variables in a function are declared, unlike the variables of a normal script. This is
done through
local var1 var2 var3 . . .
The reason to define local variables is to ensure that the variables no longer exist outside
of the function once the function ends. Otherwise, if a variable used in a function happens
to be the same name as a variable outside of the function, the value of the outer variable
may be altered. Consider the following.
X
=
0
foo() {
X
=
$1
X
=
$((X
+
1))
echo $X
}
echo $X
foo 5
echo $X
In this code, X starts at 0. The function foo is defined but not yet invoked, so any code in
foo does not yet impact the script. The value of X is output, which is 0. Now, foo is called,
passing the parameter 5. In foo, X stores the parameter, so X now stores 5. Next, X is
incremented to 6. The value of X is output, 6. After foo terminates, we resume in the script
immediately after the instruction
foo 5
. This leads to the last instruction,
echo $X
. This
outputs 6 because X had changed in foo. So we see that X has changed from 0 to 5 to 6.
Now consider changing foo by adding the following line as the first line.
local X
=
$1
The script behaves somewhat differently now because the variable X exists in two places.
First, X in the script (but not the function) is set to 0 and then output. Next, foo is called,
being passed the parameter 5. The second X is now created
local
to foo. This variable is
initialized to 1 and then changed to 5 in the instruction
X
=
$1
. This version of X is incre-
mented to 6 and output. The
echo $X
in the function outputs 6. When the function ter-
minates, the local version of X is removed from memory. The
echo $X
at the bottom of
the script outputs 0 because this version of X was never affected by the function.
Shell Scripting
◾
297
The above example illustrates that a local variable exists only within the confines of the
function in which it was declared. The above example then uses two variables. They both
happen to be named X, but they are accessible in different locations.
Without the local statement in foo, the X in foo is the same as the X outside of foo. With
local, there are two X’s. What about a variable that is defined in a function which was not
first initialized prior to the function’s call? This leads to yet another situation. In this case,
what we will see is that a variable used in a function that did not exist prior to the function’s
call will continue to persist after the function terminates.
Let us consider a simple swap function. You might use this to sort an array of numbers.
In our case, we will simply define swap to exchange two variable values, X and Y. In this
example, prior to calling the function, we define X and Y. The value TEMP is first defined
in the function and so did not exist prior to calling the function.
#!/bin/bash
X
=
5
Y
=
10
swap() {
TEMP
=
$X
X
=
$Y
Y
=
$TEMP
}
swap
echo $X $Y
The script first initializes X to 5 and Y to 10. It then defines the swap function. Now,
swap is called. The swap function knows of X and Y since they were defined in the code
prior to the function call. In swap, we use TEMP to store X, we move Y into X and then
TEMP into Y. Now X has 10 and Y has 5. TEMP also has 5. After the function terminates,
we output $X and $Y giving us 10 5. Interestingly at this point, TEMP is still 5. Had we
changed the echo statement to be
echo $X $Y $TEMP
we would see 10 5 5. Had we made TEMP a local variable in swap by declaring
local
TEMP
, then TEMP would not have a value outside of swap and this latter echo statement
would output a blank (null) for TEMP.
It should be noted that the initialization of X and Y could be moved to after the func-
tion’s definition. By moving them after the } in the function and before swap, the function
still receives the values of X and Y since they were defined before the function call. Thus,
the script would still provide the same output.
298
◾
Linux with Operating System Concepts
7.9.4 Exit and Return Statements
All functions return an exit status. If the function executes without error, the exit status
by default is 0. We can modify the exit status through the command
exit
Do'stlaringiz bilan baham: |