BASH: using functions, with examples
BASH — Bourne Again Shell: Functions — arguments, variables, local variables, recursive functions, and function exports. Examples.
In fact, a function in bash
is a regular variable, but with more features.
The main use is when the same code needs to be used several times and/or in different related scripts.
Declaring and calling a function
The function is declared like this:
function function_name ()
{
function body
}
Or:
function one {
echo "One"
}
two () {
echo "Two"
}
function three () {
echo "Three"
}
However, the most correct option, in order to make the script compatible with different shell
versions, would be the second one:
two () {
echo "Two"
}
And try to never use the third option:
function three () {
echo "Three"
}
You can call a function simply by specifying its name in the body of the script:
#!/bin/bash
function one {
echo "One"
}
one
$ ./example.sh
One
It is important that the function declaration be exist before it is called, otherwise, an error will be received:
#!/bin/bash
function one {
echo "One"
}
one
two
function two {
echo "Two"
}
$ ./example.sh
One
$ ./example.sh: line 7: two: command not found
Calling a function with arguments
Let’s move on to a more complex function, and consider calling a function with arguments.
For example, let’s take a function that is called at the place in the code where you need to get a response from the user:
#!/bin/bash
answer () {
while read response; do
echo
case $response in
[yY][eE][sS]|[yY])
printf "$1"
$2
break
;;
[nN][oO]|[nN])
printf "$3"
$4
break
;;
*)
printf "Please, enter Y(yes) or N(no)! "
esac
done
}
echo "Run application? (Yes/No) "
answer "Run" "" "Not run" ""
In this case, the function answer()
expects a response from the user in the style Yes
or No
(or any variation given in the expression [yY][eE][sS]|[yY]
or [nN][oO]|[nN]
), and depending on the response, performs a certain action.
In case of a response Yes
, the action specified in the first argument $1
with which the function was called will be performed.
Let’s check:
$ bash test.sh
Run application? (Yes/No)
y
Run
With answer No:
$ ./example.sh
Run application? (Yes/No)
no
Not run
Calling commands directly from arguments, and even more so from variables, is considered not the best solution, so let’s rewrite it and call it with the operators &&
(that is in case of success, when receiving code 0) and ||
- in case of an error and receiving response code 1:
#!/bin/bash
answer () {
while read response; do
echo
case $response in
[yY][eE][sS]|[yY])
printf "$1\n"
return 0
break
;;
[nN][oO]|[nN])
printf "$2\n"
return 1
break
;;
*)
printf "Please, enter Y(yes) or N(no)! "
esac
done
}
echo -e "\nRun application? (Yes/No) "
answer "Run" "Will not run" && echo "I'm script" || echo "Doing nothing"
Now we pass the answer “Run” as the first argument to the function, and in the case of the user’s answer “Yes”, we’ll execute the printf "Run"
and echo "I'm script"
. If the answer No
is selected, then we print the second argument Will not run
, and perform the action echo "Doing nothing"
:
$ bash test.sh
Run application? (Yes/No)
y
Run
I’m script
$ bash test.sh
Run application? (Yes/No)
no
Will not run
Doing nothing
Accordingly, instead of the echo
you can run any other command:
#!/bin/bash
answer () {
while read response; do
echo
case $response in
[yY][eE][sS]|[yY])
printf "$1\n"
return 0
break
;;
[nN][oO]|[nN])
printf "$2\n"
return 1
break
;;
*)
printf "Please, enter Y(yes) or N(no)! "
esac
done
}
echo -e "\nKill TOP application? (Yes/No) "
answer "Killing TOP" "Left it alive" && pkill top || echo "Doing nothing"
$ ./example.sh
Kill TOP application? (Yes/No)
y
Killing TOP
It is important to keep in mind that if the first command fails (in this example, pkill
does not find the specified process), then the function will return code 1, and the second part will be executed:
$ ./example.sh
Kill TOP application? (Yes/No)
y
Killing TOP
Doing nothing
Variables in functions
Variables can also be used in arguments.
For example, you can define several answers in different variables, and use the right one in different cases:
#!/bin/bash
answer () {
while read response; do
echo
case $response in
[yY][eE][sS]|[yY])
printf "$1\n"
return 0
break
;;
[nN][oO]|[nN])
printf "$2\n"
return 1
break
;;
*)
printf "Please, enter Y(yes) or N(no)! "
esac
done
}
replay1="Killing TOP"
replay2="Left it alive"
echo -e "\nKill TOP application? (Yes/No) "
answer "$replay1" "$replay2" && echo "I'm script" || echo "Doing nothing"
$ ./example.sh
Kill TOP application? (Yes/No)
y
Killing TOP
I’m script
$ ./example.sh
Kill TOP application? (Yes/No)
n
Left it alive
Doing nothing
As with regular variables, functions use “positional arguments”, i.e.:
$#
- display the number of passed arguments$*
- display a list of all passed arguments$@
- the same as$*
- but each argument is considered as a simple word (string)$1 - $9
- are numbered arguments, depending on the position in the list
For example, let’s create a script with a function that should display the number of arguments passed:
#!/bin/bash
example () {
echo $#
shift
}
example $*
$ ./example.sh 1 2 3 4
4
Or just display all the arguments passed to it:
#!/bin/bash
example () {
echo $*
shift
}
example $*
$ ./example.sh 1 2 3 4
1 2 3 4
Or you can pass arguments directly when calling a function, and not when calling a script, as in the example above:
#!/bin/bash
example () {
echo $*
shift
}
example 1 2 3 4
$ ./example.sh
1 2 3 4
Local variables
By default, all given variables in bash
scripts are considered global within the script itself, but in a function, you can declare a local variable that will be available only during its (function) execution.
Example:
#!/bin/bash
ex0=0
example () {
local ex1=1
echo "$ex1"
}
example
[[ $ex0 ]] && echo "Variable found" || echo "Can't find variable!"
[[ $ex1 ]] && echo "Variable found" || echo "Can't find variable!"
Check it:
$ bash test.sh
Variable found
Can’t find variable!
Math operations in functions
As with variables, functions can use mathematical operations.
For example this function:
#!/bin/bash
mat () {
a=1
(( a++ ))
echo $a
}
mat
As a result, we get the value of the variable $a
+ 1:
$ ./mat.sh
2
A more complex example — using several variables and calculating their value:
#!/bin/bash
mat () {
a=1
b=2
c=$(( a + b ))
echo $c
}
mat
Result:
$ ./mat.sh
3
Another option is to use arguments:
#!/bin/bash
mat () {
a=$1
b=$2
c=$(( a + b ))
echo $c
}
mat $1 $2
Run it:
$ ./mat.sh 1 1
2
Recursive functions
A recursive function is a function that, when called, calls itself.
For example:
#!/bin/bash
recursion () {
count=$(( $count + 1 ))
echo $count
recursion
}
recursion
Such a function will call itself endlessly until its execution is manually interrupted:
$ ./example.sh
…
913
914
915
For better clarity, let’s add a loop that checks the condition: if the variable $count
exceeds the value of the variable $recursions
, then the function will stop its execution:
#!/bin/bash
count=0
recursions=4
recursion () {
count=$(( $count + 1 ))
echo $count
while [ $count -le $recursions ]; do
recursion
done
}
recursion
Run the script:
$ ./example.sh
1
2
3
4
5
To simplify the script, you can replace the expression count=$(( $count + 1 ))
with (( count++ ))
:
#!/bin/bash
count=0
recursions=4
recursion () {
(( count++ ))
echo $count
while [ $count -le $recursions ]; do
recursion
done
}
recursion
Check it:
$ ./example.sh
1
2
3
4
5
Export functions
To pass a function to the next script called in a new (child) instance of shell
, it must be exported.
For example, let’s take two files — in the file 1.sh
we will declare a function and call the script 2.sh
:
#!/bin/bash
one () {
echo "one"
}
bash 2.sh
And in the file 2.sh
will try to use this function:
#!/bin/bash
one
Run it:
$ ./1.sh
2.sh: line 3: one: command not found
Now, export the function using the option export
with the key -f
:
#!/bin/bash
one () {
echo "one"
}
export -f one
bash 2.sh
Run:
$ ./1.sh
one
Another option is to call the second script in the same instance of the shell by using the source
:
#!/bin/bash
one () {
echo "one"
}
source 2.sh
Or so:
#!/bin/bash
one () {
echo "one"
}
. 2.sh
Both options are equivalent and will give the same result:
$ ./1.sh
one
Checking for a function availability
Sometimes it is necessary to check if a function exists before executing it. For this, we can use the declare
command.
Called with a key -f
and no arguments declare
will display a bodies of all available functions:
#!/bin/bash
one () {
echo "one"
}
two () {
echo "two"
}
declare -f
Result:
$ ./test.sh
one ()
{
echo “one”
}
two ()
{
echo “two”
}
With the key -F
- only names:
#!/bin/bash
one () {
echo "one"
}
two () {
echo "two"
}
declare -F
And:
$ ./test.sh
declare -f one
declare -f two
If you specify function names as arguments, declare
will simply display their names:
#!/bin/bash
one () {
echo "one"
}
two () {
echo "two"
}
declare -F one two
Check it:
$ ./test.sh
one
two
You can set the key -f
and a name of the function, then only the body of the specified function will be displayed:
#!/bin/bash
one () {
echo "one"
}
two () {
echo "two"
}
declare -f one
Run:
$ ./test.sh
one () {
echo “one”
}
You can check the presence of functions before executing them using an additional function, and passing names of functions to be checked:
#!/bin/bash
one () {
echo "one"
}
two () {
echo "two"
}
isDefined() {
declare -f "$@" > /dev/null && echo "Functions exist" || echo "There is no some functions!"
}
isDefined one two
Pay attention to the use of “$@
" - as it was written above, it is a parameter that displays the argument "as is", without any interpretation by bash
.
Let’s run the script to check:
$ ./test.sh
Functions exist
And now, let’s try to add one “extra” function:
#!/bin/bash
one () {
echo "one"
}
two () {
echo "two"
}
isDefined() {
declare -f "$@" > /dev/null && echo "Functions exist" || echo "There is no some functions!"
}
isDefined one two three
Result:
$ ./test.sh
There are no some functions!
declare
detected the absence of the function three
, and returned code 1, which caused the ||
.
Originally published at RTFM: Linux, DevOps, and system administration.