Skip to content

bash functions

ghdrako edited this page Apr 5, 2024 · 24 revisions

As with a bash keyword or builtin, with function the shell doesn’t need to create a separate process to run the function. That makes a function **more efficient ** than calling a separate command binary or shell script.

Function definition must occur before it is called.

function_name () {
  commands
}
function_name () { commands; }
function function_name {
  commands
}
function function_name { commands; }
hello_world () {
   echo 'hello, world'
}

hello_world
#!/bin/bash
open() {
    case "$1" in
        *.mp3|*.ogg|*.wav|*.flac|*.wma) xmms "$1";;
        *.jpg|*.gif|*.png|*.bmp)        display "$1";;
        *.avi|*.mpg|*.mp4|*.wmv)        mplayer "$1";;
    esac
}
for file; do
    open "$file"
done

Then, a for loop iterates over all of the script's positional parameters. (Remember, for file is equivalent to for file in "$@" and both of them iterate over the full set of positional parameters.)

Variables Scope

Functions may also have local variables, declared with the local or declare keywords. This lets you do work without potentially overwriting important variables from the caller's namespace.

#!/bin/bash
count() {
    local i
    for ((i=1; i<=$1; i++)); do echo $i; done
    echo 'Ah, ah, ah!'
}
for ((i=1; i<=3; i++)); do count $i; done

The local variable i inside the function is stored differently from the variable i in the outer script. This allows the two loops to operate without interfering with each other's counters.

var1='A'
var2='B'
my_function () {
  local var1='C'
  var2='D'
  echo "Inside function: var1: $var1, var2: $var2"
}

echo "Before executing function: var1: $var1, var2: $var2"

my_function

echo "After executing function: var1: $var1, var2: $var2"

#Output:
#Before executing function: var1: A, var2: B
#Inside function: var1: C, var2: D
#After executing function: var1: A, var2: D

Return Values

Bash functions, unlike functions in most programming languages do not allow you to return a value to the caller. When a bash function ends its return value is its status: zero for success, non-zero for failure. To return values, you can set a global variable with the result, or use command substitution, or you can pass in the name of a variable to use as the result variable.

The return value from bash functions is really just an exit status, the value that you can check using $? after calling the function. It actually returns the exit status of the last command executed in the function.

There are two typical approaches to return value from function

  • function output be the return values. You might pipe that output into the next part of your script that needs the results from the function, or you might capture that output using $() notation. The $() will run the function in a subshell and you can assign the result (the function’s output) to a variable, and then use that variable on another command line.

  • use global variables

function Summer {
  #local i
  local -i i # or declare -i i to declare i as an integer value, avoiding conversions to/from strin
  SUM=0      # SUM is global  because that is how the result of the function call is being returned to the caller
  for((i=0; i<$1; i++)) {
    let SUM+=i;
  }
}

Return value as global variable

my_function () {
  echo "some result"
  return 55
}

my_function
echo $?

#Output:
#some result
#55


my_function () {
  func_result="some result"
}

my_function
echo $func_result

#Output:
#some result


#### use local variables in your functions
* with use command substitution
my_function () {
  local func_result="some result"
  echo "$func_result"
}

func_result="$(my_function)"
echo func_result

#Output:
#some result

function that accepts a variable name as part of its command line and then set that variable to the result of the function

function myfunc()
{
    local  __resultvar=$1
    local  myresult='some value'
    eval $__resultvar="'$myresult'"
}

myfunc result
echo $result

Since we have the name of the variable to set stored in a variable, we can't set the variable directly, we have to use eval to actually do the setting. The eval statement basically tells bash to interpret the line twice, the first interpretation above results in the string result='some value' which is then interpreted once more and ends up setting the caller's variable.

When you store the name of the variable passed on the command line, make sure you store it in a local variable with a name that won't be (unlikely to be) used by the caller (which is why I used __resultvar rather than just resultvar).

Returning Text Values

While usually, functions communicate using a numerical return code (return [number]), they can also send back text:

function get_system_info() {
  os_name=$(uname -s)
  kernel_version=$(uname -r)
  output="Operating System: $os_name Kernel: $kernel_version"
  echo "$output" # Return the text output
}
info=$(get_system_info) # Capture the entire output in a variable
echo "$info"

If you don't, and the caller happens to choose the same name for their result variable as you use for storing the name, the result variable will not get set. For example, the following does not work:

# doesn't work !!! poniewaz zmienna w funkcji ma taka sama nazwe jak nazwa zmiennej w jej wywolaniu
function myfunc()
{
    local  result=$1
    local  myresult='some value'
    eval $result="'$myresult'"
}

myfunc result
echo $result

The reason it doesn't work is because when eval does the second interpretation and evaluates result='some value', result is now a local variable in the function, and so it gets set rather than setting the caller's result variable.

functions that combine both result variables and command substitution:

function myfunc()
{
    local  __resultvar=$1
    local  myresult='some value'
    if [[ "$__resultvar" ]]; then
        eval $__resultvar="'$myresult'"
    else
        echo "$myresult"
    fi
}

myfunc result
echo $result
result2=$(myfunc)
echo $result2

Here, if no variable name is passed to the function, the value is output to the standard output

Dynamic Scoping

If you declare a local variable in a function and then that function calls another function, the second function will see the first function’s local variable and not the global variable of the same name. If you call that second function from the main part of your script,it will see (and use) the global variable.

Passing Arguments to Bash Functions

  • The passed parameters are $1, $2, $3 … $n, corresponding to the position of the parameter after the function’s name.
  • The $0 variable is reserved for the function’s name.
  • The $# variable holds the number of positional parameters/arguments passed to the function.
  • The $* or $@ variable holds all positional parameters/arguments passed to the function.
#!/bin/bash

greeting () {
  echo "Hello $1"
}

greeting "Joe"

Output
Hello Joe

Arguments as array

function print_items() {
  local items_array=("$@") # Access all passed arguments as an array
  for item in "${items_array[@]}"; do
    echo $item
  done
}
my_items=("apple" "banana" "orange")
print_items "${my_items[@]}"

Note: It’s essential to pass arrays with "${array_name[@]}" syntax (including quotes and [@]) to preserve any elements containing spaces.



Show all arguments

function Tell_All { echo "You called $FUNCNAME" echo "The $# args supplied are:" for arg in "$@"; do echo "$arg" done }

Validate argument in function

function create_backup() { if [ -z "$1" ]; then # Missing filename? echo "Error: please provide a filename." return 1 fi

...rest of your backup creation logic

}


### Array FUNCNAME
. There is an array variable named ``FUNCNAME`` that holds the call stack of the functions that have been invoked. Element
0 is always the **current function**, so just use ``$FUNCNAME`` (since using the** array name**
**without an index always returns the first element**, i.e., the one at index zero). That’s useful in a debugging prompt

function f1 { echo "You called $FUNCNAME" }


### Functions

Definition:

function foo () { echo "Arguments work just like script arguments: $@" echo "And: $1 $2..." echo "This is a function" return 0 }

Call the function foo with two arguments, arg1 and arg2:

foo arg1 arg2

=> Arguments work just like script arguments: arg1 arg2

=> And: arg1 arg2...

=> This is a function

or simply

bar () { echo "Another way to declare functions!" return 0 }

Call the function bar with no arguments:

bar # => Another way to declare functions!

Calling your function

foo "My name is" $Name



### Passing function as argument

function x() { echo "Hello world"; } function around() { echo before; $1; echo after; }

around x


function x() { echo "x(): Passed $1 and $2"; } function around() { echo before; "$@"; echo after; } around x 1st 2nd

-- print before x(): Passed 1st and 2nd after


function x() { echo "Hello world" }

function around() { echo "before" var=$($1) echo "after $var" }

around x


### Recursive function
* Recursive functions are slow under bash.
* Avoid using recursive functions if possible

factorial() { if [ $1 -eq 0 ]; then echo 1 else local subfactorial=$(factorial $(( $1 - 1 ))) echo $(( $1 subfactorial )) fi } result=$(factorial 5) echo "Factorial of 5 is $result"



#!/bin/bash

function f() { local n=$1 if $n -eq 0; then echo 1 else echo $((n*$(f $n-1))) fi }

for i in {1..10}; do echo "$i!=$(f $i)" done

#!/bin/bash

fact.sh - Shell script to to find factorial of given command line arg

factorial(){ local i=$1 local f declare -i i declare -i f

factorial() is called until the value of $f is returned and is it is <= 2

This is called the recursion

[ $i -le 2 ] && echo $i || { f=$(( i - 1)); f=$(factorial $f); f=$(( f * i )); echo $f; } }

display usage

[ $# -eq 0 ] && { echo "Usage: $0 number"; exit 1; }

call factorial

factorial $1





### Redirection  function definition
You can put an I/O redirection on your function definition. It takes effect when you
call that function. Here’s a common use of that feature to easily redirect the entire
usage message to STDERR (via ``1>&2``):

function Usage_Message { echo "usage: $0 value pathname" echo "where value must be positive" echo "and pathname must be an existing file" echo "for example: $0 25 /tmp/scratch.csv" } 1>&2

This function might be called when the script checks and finds that the correct
number of arguments have not been supplied or that the supplied filename doesn’t
exist

Test

Clone this wiki locally