Functions
Named groups of commands that can be called with arguments.
Source: src/scripting/control_flow.f90:1201-1216, src/ast/evaluator_simple_real.f90:2498-2589, src/execution/builtins.f90:2307-2539
Defining Functions
Standard Syntax
function name {
commands
}
POSIX Syntax
name() {
commands
}
Both forms are equivalent. Function definitions are stored in the shell's function table and can be called by name.
Calling Functions
greet() {
echo "Hello, $1!"
}
greet "World"
# Output: Hello, World!
Arguments
Functions receive arguments as positional parameters:
show_args() {
echo "Function: $0" # Script name (not function name)
echo "Arg count: $#"
echo "All args: $@"
echo "First: $1"
echo "Second: $2"
}
show_args one two three
The caller's positional parameters are saved before function execution and restored afterward (evaluator_simple_real.f90:2551-2553).
Local Variables
Source: builtins.f90:2307-2371
Variables declared with local are scoped to the function:
outer="global"
myfunc() {
local outer="local"
echo "Inside: $outer"
}
myfunc
echo "Outside: $outer"
# Output:
# Inside: local
# Outside: global
Declaration Forms
func() {
local var # Declare without value
local var=value # Declare with value
local a=1 b=2 c=3 # Multiple declarations
local readonly=5 # "readonly" is just a variable name here
}
Scope Rules
- Local variables shadow global variables of the same name
- Modifications to local variables don't affect globals
- Child functions can see parent's local variables (dynamic scope)
- Local declarations only valid inside functions (error otherwise)
outer() {
local x=1
inner
}
inner() {
echo "x=$x" # Sees outer's local x
}
outer
# Output: x=1
Implementation
Local variables are stored in a 2D array indexed by [function_depth, variable_index]. Each function level has its own count via local_var_counts. There's a limit of MAX_LOCAL_VARS per function level.
Return Statement
Source: builtins.f90:2510-2539
Exit a function with an optional status code:
check_file() {
if [[ ! -f "$1" ]]; then
return 1
fi
return 0
}
if check_file "/etc/passwd"; then
echo "File exists"
fi
Return Values
return # Return with last command's exit status
return 0 # Success
return 1 # Failure
return N # Return specific status (0-255)
Context Requirements
return is only valid inside:
- Functions (function_depth > 0)
- Sourced scripts (source_depth > 0)
Outside these contexts, return produces exit status 2.
Returning Data
Since return only provides a numeric status, use other methods for data:
# Command substitution
get_hostname() {
hostname -s
}
result=$(get_hostname)
# Global variable
get_hostname() {
RESULT=$(hostname -s)
}
get_hostname
echo "$RESULT"
# Nameref (if supported)
get_hostname() {
local -n ref=$1
ref=$(hostname -s)
}
get_hostname myvar
echo "$myvar"
Recursion
Functions can call themselves:
factorial() {
local n=$1
if [[ $n -le 1 ]]; then
echo 1
else
local prev=$(factorial $((n - 1)))
echo $((n * prev))
fi
}
factorial 5
# Output: 120
Note: Deep recursion may hit control stack limits.
Function Attributes
Export Functions
Make a function available to child processes:
myfunc() {
echo "Hello"
}
export -f myfunc
bash -c 'myfunc' # Works in subshell
List Functions
declare -f # List all functions with bodies
declare -F # List function names only
type myfunc # Show function definition
Unset Functions
unset -f myfunc
Common Patterns
Argument Validation
process_file() {
if [[ $# -lt 1 ]]; then
echo "Usage: process_file <filename>" >&2
return 1
fi
local file="$1"
if [[ ! -f "$file" ]]; then
echo "Error: $file not found" >&2
return 1
fi
# Process file...
}
Default Arguments
greet() {
local name="${1:-World}"
echo "Hello, $name!"
}
greet # Hello, World!
greet "User" # Hello, User!
Error Handling
die() {
echo "Error: $*" >&2
exit 1
}
[[ -f config ]] || die "Config file not found"
Cleanup on Exit
cleanup() {
rm -f "$tmpfile"
}
main() {
trap cleanup EXIT
tmpfile=$(mktemp)
# Work with tmpfile...
}
Option Parsing in Functions
create_user() {
local name="" shell="/bin/bash" home=""
while [[ $# -gt 0 ]]; do
case $1 in
-n|--name) name="$2"; shift 2 ;;
-s|--shell) shell="$2"; shift 2 ;;
-h|--home) home="$2"; shift 2 ;;
*) break ;;
esac
done
# Create user with $name, $shell, $home
}
create_user --name john --shell /bin/zsh
Library Pattern
# lib.sh
_lib_loaded=1
lib_init() {
# Initialize library
}
lib_cleanup() {
# Cleanup
}
# main.sh
source lib.sh
lib_init
# Use library...
lib_cleanup