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
Special Functions
command_not_found_handle
Source: src/execution/executor.f90:738-791
When you type a command that doesn't exist on PATH, fortsh checks whether a function named command_not_found_handle is defined. If it is, fortsh calls it instead of printing the default error message.
This matches bash's behavior and is useful for suggesting corrections, installing missing packages, or logging unknown commands.
command_not_found_handle() {
echo "fortsh: '$1': command not found" >&2
return 127
}
Arguments
The function receives the command name as $1 and all original arguments as $2, $3, etc:
command_not_found_handle() {
local cmd="$1"
shift
echo "Unknown command: $cmd (args: $@)" >&2
return 127
}
# Typing: foobar -x hello
# Output: Unknown command: foobar (args: -x hello)
When It Triggers
The handler runs only when all of these are true:
- The command is not a builtin, alias, or function
- The command name contains no
/(path-based commands like./fooskip the handler) - The command is not found anywhere on
PATH - The
commandbuiltin is not active (i.e.,command missing_cmdbypasses the handler)
Practical Examples
Suggest similar commands:
command_not_found_handle() {
echo "fortsh: $1: command not found" >&2
# Suggest packages on Fedora/RHEL
if type dnf &>/dev/null; then
echo "Try: sudo dnf install $1" >&2
fi
return 127
}
Log unknown commands:
command_not_found_handle() {
echo "$(date): unknown command '$1'" >> ~/.fortsh_missing.log
echo "fortsh: $1: command not found" >&2
return 127
}
Return Value
The handler's exit status becomes $?. By convention, return 127 (the standard "command not found" code). If the handler returns non-zero, the ERR trap fires as usual.
Default Behavior
If no command_not_found_handle function is defined, fortsh prints a color-highlighted error with "Did you mean?" suggestions when running interactively.