Bash Functions: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
 
(46 intermediate revisions by the same user not shown)
Line 23: Line 23:
==Arguments==
==Arguments==


The function does not declare its arguments in the signature. They are available in the function's body as $1, $2, etc.
The function does not declare its arguments in the signature. They are available in the function's body as [[Bash_Parameters_and_Variables#Positional_Parameters_.241.2C_.242.2C_...|positional parameters]] $1, $2, etc.


'''Important''': For more than 9 arguments, always refer to arguments using ${}: ${10} not $10. If you use $10, you'll actually get the first argument ($1) with a "0" appended to it.
'''Important''': For more than 9 arguments, always refer to arguments using ${}: ${10} not $10. If you use $10, you'll actually get the first argument ($1) with a "0" appended to it.


The number of arguments, including the empty strings specified as "" when the function was invoked, may be obtained with [[Bash_Environment_Variables#.24.23|$#]].
The number of arguments, including the empty strings specified as "" when the function was invoked, may be obtained with [[Bash_Built-In_Variables#.24.23|$#]].


The <tt><b>shift</b></tt> keyword "shifts" to the left the argument list, discarding $1 and assigning to $1 the value that was previously assigned to $2, to $2 the value that was previously assigned to $3, and so on. <tt><b>shift</b></tt>'s exit code is 0 if there were still arguments to shift, 1 otherwise.
The <tt><b>shift</b></tt> keyword "shifts" to the left the argument list, discarding $1 and assigning to $1 the value that was previously assigned to $2, to $2 the value that was previously assigned to $3, and so on. <tt><b>shift</b></tt>'s exit code is 0 if there were still arguments to shift, 1 otherwise.
Line 41: Line 41:
}
}
</syntaxhighlight>
</syntaxhighlight>
==="Pass-by-Reference" Arguments===
All arguments are passed by value to bash functions. However, a pass-by-reference mechanism can be simulated using [[Bash_Parameter_and_Variable_Expansion#.24.21..._Indirect_Variables.2C_Variable_Indirection.2C_Indirect_Expansion|indirect variables]]: the name of the shell variables to use is passed to the function and the function assigns a value to the variable indicated by name, using [[Bash_Parameter_and_Variable_Expansion#Assignment_to_Indirect_Variables|assignment to indirect variables]]:
<syntaxhighlight lang='bash'>
function example-function() {
    local pass_by_ref_var_name=$1
    eval "${pass_by_ref_var_name}=B"
}
var1=A
echo ${var1}
example-function var1
echo ${var1}
</syntaxhighlight>
The example prints:
<syntaxhighlight lang='bash'>
A
B
</syntaxhighlight>
====Pass an Associative Array by Reference====
This is an example for a patter where a function returns a complex results (multiple key-value pairs) in a map that is passed as argument.
<syntaxhighlight lang='bash'>
function update-map() {
  local map_variable_name=$1
  eval "${map_variable_name}[\"color\"]=blue"
}
</syntaxhighlight>
The function can be used as follows:
<syntaxhighlight lang='bash'>
declare -A VALUES
update-map VALUES
echo ${VALUES["color"]}
</syntaxhighlight>
This will display "blue".
More details about indirect variable access: {{Internal|Bash_Arrays#Indirect_Variable_Access_for_Associative_Arrays|Indirect Variable Access for Associative Arrays}}


==Exit Status==
==Exit Status==
Line 55: Line 96:
If there is no explicit "return" keyword, the function's exit status is the exit status of last executed statement.
If there is no explicit "return" keyword, the function's exit status is the exit status of last executed statement.


The function's caller can retrieve the exist status with [[Bash_Environment_Variables#.24.3F|$?]].
The function's caller can retrieve the exist status with [[Bash_Built-In_Variables#.24.3F|$?]].


==Returning Values==
==Returning Values==


As mentioned above, functions do not return values. However, we may send content to stdout or stderr from the body of the function, and that content can be captured by the caller as follows:
As mentioned above, functions do not return values. A function has an [[#Exit_Status|exit status]].
 
==stdout, stderr==
 
A function may may send content to stdout or stderr from the body of the function, and that content can be captured by the caller as follows:


<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
Line 76: Line 121:
</syntaxhighlight>
</syntaxhighlight>


[[Bash Command Substitution#Overview|Command substitution]] demonstrated above will capture the content that is being sent to stdout into the local variable "content". The content sent to stderr will be sent to the stderr of the executing shell.
[[Bash Command Substitution#Overview|Command substitution]] demonstrated above ends in the capture of the content that is being sent to stdout into the local variable "content". The content sent to stderr will be sent to the stderr of the executing shell.
 
==Piping the stdout and stderr generated by a function==
 
The stdout and stderr content generated by a function can be piped in-line, as follows (we assume the callee() function has been declared as in the [[#stdout.2C_stderr|stdout, stderr]] section):
 
<syntaxhighlight lang="bash">
callee | sed -e 's/ /-/g'
</syntaxhighlight>
 
will result in "we-send-this-to-stdout" being generated at stdout.
 
Note that specifying the "|" operator causes the function to execute in a sub-shell. This can be proven by displaying [[Bash_Built-In_Variables#BASHPID|${BASHPID}]] in the caller and in the function. They will have different values.
 
=Function Invocation Mechanics=
 
The parameters are passed by value, and no matter what the function does, internally, it will not influence the parameters in the calling layer.


=Function Info=
=Function Info=
Line 93: Line 154:
<syntaxhighlight lang='bash'>
<syntaxhighlight lang='bash'>
${FUNCNAME[0]}
${FUNCNAME[0]}
</syntaxhighlight>
This is equivalent with:
<syntaxhighlight lang='bash'>
${FUNCNAME}
</syntaxhighlight>
</syntaxhighlight>


Line 111: Line 178:
=Executing a Function in Background=
=Executing a Function in Background=


Functions can be executed in background with "&" as follows:
Functions can be executed in background with '&' as shown:


<syntaxhighlight lang='bash'>
<syntaxhighlight lang='bash'>
Line 125: Line 192:
</syntaxhighlight>
</syntaxhighlight>


When a function is invoked in background within the context of a shell, a completely new sub-shell is started and begins to execute the code of function. Both processes share the same name in the process table, but the shell that invoked the function in background is the parent of the newly created sub-shell:
When a function is invoked in background within the context of a shell, a completely new sub-shell is started and begins to execute the code of function. The parent process and the child process share the same name in the process table. The shell that invoked the function in background shows as parent of the newly created sub-shell:
 
  501  <b><font color=orange>4058</font></b>  3941  0  9:07PM ttys004    0:00.01 /bin/bash ./main.sh
  501  4059  <b><font color=orange>4058</font></b>  0  9:07PM ttys004    0:00.01 /bin/bash ./main.sh
 
From this moment on, both shells are executing independently, until they exit.
 
==stdout and stderr Handling==
 
After the function was invoked in the background, stdout and stderr of the newly created sub-shell that is executing the function is automatically redirected into the parent shell's stdout and stderr.
 
If the sub-shell is still executing when the parent shell exits normally or it is killed, the sub-shell will keep executing as long as its code allows it. The [[Linux Process Management Concepts#The_init_Process|the init process]] (process ID 1) becomes its parent. The controlling terminal of the parent will keep getting sub-shell's stdout and stderr content. If the controlling terminal is terminated, the sub-shell will still keep going, but in this case the output will be discarded.
 
If it is necessary to keep capturing the sub-shell's process after the parent exits and its controlling terminal is terminated, the function must be invoked as such:
 
<syntaxhighlight lang='bash'>
function caller() {
 
    callee > ./callee.stdout 2> ./callee.stderr &
 
}
</syntaxhighlight>
 
Both stdout and stderr can be send into the same file with the following syntax:
 
<syntaxhighlight lang='bash'>
function caller() {
 
    callee > ./callee.log 2>&1 &
 
}
</syntaxhighlight>
 
It is important to specify the redirects ''before'' the '&' operator. If they are specified after, no syntax error of any kind is detected, "callee.stdout" and "callee.stderr" will be created but will stay empty.


  501  <b><font color=red>4058</font></b>  3941  0  9:07PM ttys004    0:00.01 /bin/bash ./main.sh
<syntaxhighlight lang='bash'>
  501  4059  <b><font color=red>4058</font></b>  0  9:07PM ttys004    0:00.01 /bin/bash ./main.sh
function caller() {


From this moment on, both shells are executing independently, until they exit. However, the sub-shell's stdout and stderr are directed to the parent shell's stdout and stderr.
    #
    # INCORRECT, DOES NOT WORK!
    #
    callee & > ./callee.stdout 2> ./callee.stderr


If the sub-shell is still executing while the parent shell exits or it is killed, it will keep executing as long as its code allows it, while [[Linux Process Management Concepts#The_init_Process|the init process]] (process ID 1) becomes its parent. The controller terminal of the parent will keep getting sub-shell's stdout and stderr. If the controlling terminal is stopped, the sub-shell will still keep going, but in this case the output will be discarded.
}
</syntaxhighlight>


Also see: {{Internal|Bash Process in Background|bash Process in Background}}
Also see: {{Internal|Bash Process in Background|bash Process in Background}}
=Function Patterns=
* [[Bash Return Multiple Values from a Function using an Associative Array|Return Multiple Values from a Function using an Associative Array]]

Latest revision as of 03:12, 5 January 2021

Internal

Defintion

Syntax

[function] function-name() {
    ...
}

The "function" keyword is optional.

If the function declaration is specified on a single line, then the final command of the function must be followed by a semicolon:

function one-liner() { echo "something"; echo "something else"; }

The function definition must precede the first call to it. Function bodies may not be empty.

Arguments

The function does not declare its arguments in the signature. They are available in the function's body as positional parameters $1, $2, etc.

Important: For more than 9 arguments, always refer to arguments using ${}: ${10} not $10. If you use $10, you'll actually get the first argument ($1) with a "0" appended to it.

The number of arguments, including the empty strings specified as "" when the function was invoked, may be obtained with $#.

The shift keyword "shifts" to the left the argument list, discarding $1 and assigning to $1 the value that was previously assigned to $2, to $2 the value that was previously assigned to $3, and so on. shift's exit code is 0 if there were still arguments to shift, 1 otherwise.

The correct way to iterate through a function's arguments, including the empty strings, is:

function my-function() {
    while [ $# -gt 0 ]; do
        [ "$1" = "" ] && echo "got empty string" || echo "got $1"
        shift
    done
}

"Pass-by-Reference" Arguments

All arguments are passed by value to bash functions. However, a pass-by-reference mechanism can be simulated using indirect variables: the name of the shell variables to use is passed to the function and the function assigns a value to the variable indicated by name, using assignment to indirect variables:

function example-function() {

    local pass_by_ref_var_name=$1
    eval "${pass_by_ref_var_name}=B"
}

var1=A
echo ${var1}
example-function var1
echo ${var1}

The example prints:

A
B

Pass an Associative Array by Reference

This is an example for a patter where a function returns a complex results (multiple key-value pairs) in a map that is passed as argument.

function update-map() {
  local map_variable_name=$1
  eval "${map_variable_name}[\"color\"]=blue"
}

The function can be used as follows:

declare -A VALUES
update-map VALUES
echo ${VALUES["color"]}

This will display "blue".

More details about indirect variable access:

Indirect Variable Access for Associative Arrays

Exit Status

A bash function does not return a value, it only allows to set an exit status, which is a numerical value. 0 indicates success and a non-zero value indicates failure. The exit status is declared with the "return" keyword:

 function f() {
    ...
    return 0
 }

If there is no explicit "return" keyword, the function's exit status is the exit status of last executed statement.

The function's caller can retrieve the exist status with $?.

Returning Values

As mentioned above, functions do not return values. A function has an exit status.

stdout, stderr

A function may may send content to stdout or stderr from the body of the function, and that content can be captured by the caller as follows:

 function callee() {
    
     echo "we send this to stdout"
     echo "we send this to stderr" 1>&2
 }
 
 function caller() {
     
     local content
     content=$(callee)
     echo "${content}"
 }

Command substitution demonstrated above ends in the capture of the content that is being sent to stdout into the local variable "content". The content sent to stderr will be sent to the stderr of the executing shell.

Piping the stdout and stderr generated by a function

The stdout and stderr content generated by a function can be piped in-line, as follows (we assume the callee() function has been declared as in the stdout, stderr section):

callee | sed -e 's/ /-/g'

will result in "we-send-this-to-stdout" being generated at stdout.

Note that specifying the "|" operator causes the function to execute in a sub-shell. This can be proven by displaying ${BASHPID} in the caller and in the function. They will have different values.

Function Invocation Mechanics

The parameters are passed by value, and no matter what the function does, internally, it will not influence the parameters in the calling layer.

Function Info

The FUNCNAME Array

A function has a "FUNCNAME" built-in array, which is an array variable containing the names of all shell functions currently in the execution call stack. The element with index 0 is the name of any currently-executing shell function. The bottom-most element is "main". This variable exists only when a shell function is executing. Assignments to FUNCNAME have no effect and return an error status. If FUNCNAME is unset, it loses its special properties, even if it is subsequently reset.

The Name of the Function

The name of the function may be obtained inside the function with:

${FUNCNAME[0]}

This is equivalent with:

${FUNCNAME}

Recommended Style

# Function documentation 
# Returns to stdout: ...
# Returns to stderr: ...
function some-function() {

    local arg1=$1  # arg1 documentation
    local arg2=$2  # arg1 documentation
    ...
}

Executing a Function in Background

Functions can be executed in background with '&' as shown:

 function callee() {
    ...
 }

 function caller() {

     callee &

 }

When a function is invoked in background within the context of a shell, a completely new sub-shell is started and begins to execute the code of function. The parent process and the child process share the same name in the process table. The shell that invoked the function in background shows as parent of the newly created sub-shell:

 501  4058  3941   0  9:07PM ttys004    0:00.01 /bin/bash ./main.sh
 501  4059  4058   0  9:07PM ttys004    0:00.01 /bin/bash ./main.sh

From this moment on, both shells are executing independently, until they exit.

stdout and stderr Handling

After the function was invoked in the background, stdout and stderr of the newly created sub-shell that is executing the function is automatically redirected into the parent shell's stdout and stderr.

If the sub-shell is still executing when the parent shell exits normally or it is killed, the sub-shell will keep executing as long as its code allows it. The the init process (process ID 1) becomes its parent. The controlling terminal of the parent will keep getting sub-shell's stdout and stderr content. If the controlling terminal is terminated, the sub-shell will still keep going, but in this case the output will be discarded.

If it is necessary to keep capturing the sub-shell's process after the parent exits and its controlling terminal is terminated, the function must be invoked as such:

function caller() {

     callee > ./callee.stdout 2> ./callee.stderr & 

}

Both stdout and stderr can be send into the same file with the following syntax:

function caller() {

     callee > ./callee.log 2>&1 & 

}

It is important to specify the redirects before the '&' operator. If they are specified after, no syntax error of any kind is detected, "callee.stdout" and "callee.stderr" will be created but will stay empty.

function caller() {

    #
    # INCORRECT, DOES NOT WORK!
    #
    callee & > ./callee.stdout 2> ./callee.stderr

 }

Also see:

bash Process in Background

Function Patterns