This a translation of a post from 2013 with some edits, but still relevant for learning BASH.
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.
Contents
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
[simterm]
$ ./example.sh One
[/simterm]
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" }
[simterm]
$ ./example.sh One ./example.sh: line 7: two: command not found
[/simterm]
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:
[simterm]
$ bash test.sh Run application? (Yes/No) y Run
[/simterm]
With answer No:
[simterm]
$ ./example.sh Run application? (Yes/No) no Not run
[/simterm]
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"
:
[simterm]
$ 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
[/simterm]
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"
[simterm]
$ ./example.sh Kill TOP application? (Yes/No) y Killing TOP
[/simterm]
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:
[simterm]
$ ./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"
[simterm]
$ ./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
[/simterm]
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 $*
[simterm]
$ ./example.sh 1 2 3 4 4
[/simterm]
Or just display all the arguments passed to it:
#!/bin/bash example () { echo $* shift } example $*
[simterm]
$ ./example.sh 1 2 3 4 1 2 3 4
[/simterm]
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
[simterm]
$ ./example.sh 1 2 3 4
[/simterm]
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:
[simterm]
$ bash test.sh 1 Variable found Can't find variable!
[/simterm]
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:
[simterm]
$ ./mat.sh 2
[/simterm]
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:
[simterm]
$ ./mat.sh 3
[/simterm]
Another option is to use arguments:
#!/bin/bash mat () { a=$1 b=$2 c=$(( a + b )) echo $c } mat $1 $2
Run it:
[simterm]
$ ./mat.sh 1 1 2
[/simterm]
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:
[simterm]
$ ./example.sh ... 913 914 915
[/simterm]
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:
[simterm]
$ ./example.sh 1 2 3 4 5
[/simterm]
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:
[simterm]
$ ./example.sh 1 2 3 4 5
[/simterm]
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:
[simterm]
$ ./1.sh 2.sh: line 3: one: command not found
[/simterm]
Now, export the function using the option export
with the key -f
:
#!/bin/bash one () { echo "one" } export -f one bash 2.sh
Run:
[simterm]
$ ./1.sh one
[/simterm]
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:
[simterm]
$ ./1.sh one
[/simterm]
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:
[simterm]
$ ./test.sh one () { echo "one" } two () { echo "two" }
[/simterm]
With the key -F
– only names:
#!/bin/bash one () { echo "one" } two () { echo "two" } declare -F
And:
[simterm]
$ ./test.sh declare -f one declare -f two
[/simterm]
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:
[simterm]
$ ./test.sh one two
[/simterm]
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:
[simterm]
$ ./test.sh one () { echo "one" }
[/simterm]
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:
[simterm]
$ ./test.sh Functions exist
[/simterm]
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:
[simterm]
$ ./test.sh There is no some functions!
[/simterm]
declare
detected the absence of the function three
, and returned code 1, which caused the ||
.