POSIX shell supports function as a feature. Function is a way of separating or group a list of executions, similar to any other programming languages. It itself is considered an "object" like the variables. Function is very useful for:
Function is defined by a no-spacing name with the parenthesis (()
) right after, following by either a curly bracket ({}
) or a parenthesis containing the function contents. Depending on needs, curly bracket indicates that the function execute sequentially in a script while the parenthesis forks out a new process to execute the function in parallel.
To make sure the function executes sequentially without forking out a new process, the format is as follows:
function_name() {
...
}
The contents of the function are usually tabbed by 1 time for readability purposes.
To make sure the function executes in a separate process (or we called in parallel processing), the format is as follows:
function_name() (
...
)
The contents of the function are usually tabbed by 1 time for readability purposes.
NOTE:
Keep in mind that calling this function will create a child process (sub-shell) executing the function, not independently. Hence, you should be very careful with:
In POSIX shell, there is only one way to export: using source
command.
Basically write your functions in a separate scripts like a library. Then in your main script, source the script. Example:
# in path/to/library-script
function_name() {
...
}
# in main script
source "path/to/library-script"
function_name
This technique is widely used across Linux and Unix system such as your $HOME/.bashrc
. The compromise is that you have to write separate scripts.
Similar to any programs in Linux/Unix environment, function returns code and value depending on implementations. We will look into it in details.
All function returns integer code (0-255). That's it. As a standard practice, 0 means successful while non-0 means error in place depending on your definition. Hence, a function is:
function_name() {
...
return 0
}
function_name
return_code=$?
Return code is captured once after the execution using $?
special variable. Hence, be sure to capture and save it into a variable before losing it.
Functions does not return value (apart of integer) directly. Instead, it relies on "silent is gold" practices to return a value. In another word, function uses standard output (stdout
) and standard error (stderr
) extensively to generate the necessary output alongside with return code. The practices are:
echo
the desired output into stdout
. If there is no output, be quiet.echo
status or error messages into stderr
so that it is not recognized as an output.Here's an example:
function_name() {
1>&2 echo -n "This function is called." # status
1>&1 echo "Julia" # output
return 0
}
return_value=$(function_name)
return_code=$?
Another way is to create a variable up for the function to set as an output value. Here is an example:
return_value=""
function_name() {
1>&2 echo -n "This function is called." # status
return_value="Julia" # output
return 0
}
function_name
return_code=$?
This method works for sequential execution functions within the same script; it is not for parallel execution functions.
Similar to any other programming languages, POSIX shell allows you to create recursive functions. Here's an example:
function_r() {
i="$1"
if [ $i -eq 25 ]; then
return 0
fi
echo "Now: $i"
...
i="$((i + 1))"
function_r "$i"
}
NOTE:
Function uses the same argument system like calling any program in Linux or Unix. Unlike any programming languages, POSIX shell function doesn't needs to be descriptive or be declared. However, it is your duty to parse all the arguments properly using the special arguments like $@
, $0
, $1
, $2
, and goes on. Here's an example:
You can make use of comments to describe the positional arguments. However, you must perform the necessary parameter input before using them. A good practice is to validate the argument with only the usable value.
# $1 - verdict. Accepts only: true, false
function_name() {
if [ "$1" != "true" || "$1" != "false" ]; then
1>&2 echo "[ ERROR ] wrong value for argument 1"
return 1
fi
...
}
Another method is to process all the arguments using $@
or $#
. This allows you to use flag system to provide input. The caveat is that the processing can be lengthy and a lot of codes. Here's an example:
function_name() {
# argument processing
while [ $# -ne 0 ]; do
case "$1" in
-v|--version)
version="$1"
;;
*)
;;
esac
shift
done
# function body
...
}
That's all about POSIX functions. Feel free to make your shell codes modular with it.