Functions
Defining and calling Bash functions, passing arguments, return values vs exit codes, local scope, and recursion.
Defining and Calling Functions
greet() {
echo "Hello, $1!"
}
greet "World" # "Hello, World!"
# Alternative syntax (equivalent)
function greet {
echo "Hello, $1!"
}
Functions must be defined before they’re called in the script — Bash doesn’t hoist function declarations like some languages do.
Arguments Inside a Function
deploy() {
local app="$1"
local environment="$2"
echo "Deploying $app to $environment"
echo "Total arguments: $#"
echo "All arguments: $@"
}
deploy "myapp" "production"
Functions have their own $1, $2, $#, $@ — completely separate from the script’s own positional parameters.
Return Values: Exit Codes vs. Output
Bash functions don’t “return” data the way most languages do — return only sets a numeric exit status (0–255). To get actual data back, print it and capture with command substitution.
# Exit status only
is_valid_env() {
[[ "$1" =~ ^(dev|staging|production)$ ]]
}
if is_valid_env "production"; then
echo "Valid environment"
fi
# Returning DATA — echo it, capture with $()
get_current_branch() {
git rev-parse --abbrev-ref HEAD
}
BRANCH=$(get_current_branch)
echo "Current branch: $BRANCH"
local Scope
COUNTER=0
increment() {
local COUNTER=100 # shadows the global, doesn't affect it
((COUNTER++))
echo "Inside function: $COUNTER"
}
increment # "Inside function: 101"
echo "Outside: $COUNTER" # "Outside: 0" — unaffected
Without local, an assignment inside a function modifies the global variable — a very common source of hard-to-trace bugs.
Functions Calling Other Functions
log() {
echo "[$(date +'%H:%M:%S')] $*"
}
check_service() {
local name="$1"
if systemctl is-active --quiet "$name"; then
log "$name is running"
return 0
else
log "$name is NOT running"
return 1
fi
}
main() {
check_service "nginx" || exit 1
check_service "postgresql" || exit 1
log "All services healthy"
}
main "$@"
Recursion
factorial() {
local n="$1"
if (( n <= 1 )); then
echo 1
else
local prev
prev=$(factorial $((n - 1)))
echo $((n * prev))
fi
}
factorial 5 # 120
Recursion works but is rarely idiomatic in Bash — each level spawns a subshell via command substitution, making it slow. Prefer loops for anything performance-sensitive.
Production Considerations
- Structure production scripts around a
main()function called at the bottom withmain "$@"— it makes the script’s entry point explicit and testable in isolation. - Always use
localfor function-internal variables to avoid silently corrupting global state. - Remember functions “return” only an exit code (0–255) — for actual data,
echothe value and capture it with$(function_name).
Quick Interview Answer
“Bash functions are defined with
name() { ... }and must be declared before use.returnonly sets a numeric exit code, not data — to get data back, the function echoes a value and the caller captures it with command substitution ($(function_name)). Always uselocalfor variables inside a function, since without it, assignments leak into the global scope.”
Common Mistakes
- Trying to
return "some string"and expecting it to work like a normal return value — it silently truncates to an exit code or errors. - Forgetting
local, causing a function to unintentionally overwrite a global variable of the same name. - Calling a function before it’s defined in the script, resulting in “command not found.”
Add More Questions to This Guide
Know a question that should be here? Share it and help the community!
Open Google Form