The shape
greet() {
local name="${1:?name required}"
echo "hi, $name"
}
greet AliceFunctions take positional args ($1, $2, $@, $#) just like the script itself. Always local your function variables — without it they leak to the caller's scope.
Return values
return N sets $? for the caller. The N must be 0–255. To return a string, echo it and capture with $( ):
upper() { echo "${1^^}"; } # bash 4+
name="$(upper hello)"Variable scope traps
# Bug: leaks 'tmp' to caller
f() { tmp=42; echo $tmp; }
tmp=hello
f # 42
echo $tmp # 42, not hello!
# Fix
f() { local tmp=42; echo $tmp; }Exporting functions
By default, functions defined in a shell are NOT visible to child processes. export -f myfunc makes it visible to bash subshells (zsh works differently). Most scripts don't need this.
Library scripts
Put helpers in lib.sh and load with source ./lib.sh (or . ./lib.sh). Functions become available in the calling shell. Common pattern: a top-level entry script + several library files of helpers, all sourced once at start.
Standard output is a function's data channel
When a caller uses value=$(lookup key), write only the result to stdout and send progress or diagnostics to stderr. A shell function's return value is an exit status, not an arbitrary string.
Loading a library executes it
source runs a file in the current shell, where it can execute commands and change variables or options. Shared libraries should minimize load-time effects and focus on definitions and constants.