Functions in Bash
A Bash function groups a sequence of commands under a name so they can be invoked repeatedly without duplicating code, much like a function or procedure in any other language, but with Bash's own quirks around arguments, scope, and return values. Functions are defined with name() { ...; } or the equivalent function name { ...; }, and are called exactly like any other command — simply by writing their name — which means they integrate naturally with the rest of the shell's command-execution model, including pipes, redirection, and exit-status-based conditionals.
Cricket analogy: A Bash function is like a fielding drill a coach names once ('slip catching') and calls out repeatedly during practice instead of re-explaining the full sequence of steps every single time, integrating smoothly into the day's overall training routine.
Defining and calling functions
The POSIX-compatible form name() { commands; } works in both sh and Bash; the Bash-specific function name { commands; } (or function name() { commands; }) is equivalent but not portable to strict POSIX shells. A function must be defined before it is called in the script's execution order — Bash reads and executes top to bottom, so calling a function on line 5 that's defined on line 20 fails. Functions are typically declared near the top of a script or sourced from a separate library file.
Cricket analogy: Bash reading top to bottom is like a team announcement sheet pinned before the match: you can't call on the 'death-overs specialist' role defined further down the sheet if the umpire reads the batting order before reaching that definition.
#!/usr/bin/env bash
set -euo pipefail
log() {
local level="$1"
shift
echo "[$(date +%H:%M:%S)] [$level] $*" >&2
}
check_disk_space() {
local path="$1"
local threshold="$2"
local usage
usage=$(df --output=pcent "$path" | tail -n1 | tr -dc '0-9')
if (( usage >= threshold )); then
log "WARN" "$path is ${usage}% full (threshold: ${threshold}%)"
return 1
fi
log "INFO" "$path is ${usage}% full"
return 0
}
check_disk_space "/" 90
check_disk_space "/var" 80Arguments, $#, $@, and return values
Functions receive arguments the same way scripts do: $1, $2, ... for positional parameters, $# for the argument count, and $@/$* for all arguments (with "$@" being the safe, individually-quoted form to use when forwarding arguments). Bash functions don't return arbitrary values the way most languages do — return only sets the function's exit status, an integer from 0 to 255. To 'return' actual data (a string, a computed value), the idiomatic approaches are to echo the value and capture it with command substitution, or to write into a variable the caller provides.
Cricket analogy: Passing $1, $2 to a function is like a captain relaying field positions to a bowler before each over — the specific instruction (positional parameter) changes every over, and the bowler's exit status (wicket or not) is separate from communicating the actual figures back via a scoreboard update.
# Returning data via stdout + command substitution
get_timestamp() {
date +%Y%m%d-%H%M%S
}
backup_name="backup-$(get_timestamp).tar.gz"
# Returning data by writing into a caller-provided variable name
get_free_mem_mb() {
local -n result_ref=$1 # nameref: result_ref becomes an alias for the caller's variable
result_ref=$(free -m | awk '/^Mem:/ {print $7}')
}
get_free_mem_mb free_mb
echo "Free memory: ${free_mb} MB"
# return only communicates exit status (0-255), not data
is_port_open() {
local port="$1"
nc -z localhost "$port" # nc's own exit status becomes this function's return
}
if is_port_open 8080; then echo "open"; else echo "closed"; fireturn values are clamped to 0-255 and wrap around (e.g. return 256 becomes 0, return -1 becomes 255), so never use return to communicate an actual computed number — use it strictly as a success/failure signal, and use stdout or a nameref/global variable to pass real data back to the caller.
Variable scope: local vs global
By default, variables set inside a Bash function are global — they persist and are visible outside the function once it returns, which frequently causes accidental name collisions in larger scripts. The local keyword declares a variable scoped to the function (and any functions it calls), and should be used for essentially every variable a function doesn't intend to explicitly export to the caller. Declaring local var=$(some_command) on one line is usually fine, but note that local itself has an exit status that can mask the exit status of a command substitution on the same line — assigning first and declaring local separately avoids that pitfall in strict-mode scripts.
Cricket analogy: Variables leaking outside a function without local are like a substitute fielder's temporary field position accidentally becoming the permanent formation for the rest of the innings, causing confusion when the regular fielder returns expecting their original spot.
counter=0
increment() {
local step="${1:-1}" # local: doesn't leak outside the function
counter=$((counter + step)) # intentionally modifies the global
}
increment 5
increment 3
echo "$counter" # 8Bash function definitions can be exported to subshells with export -f function_name, which is occasionally used so that tools like xargs -I{} bash -c 'my_function {}' or parallel processing frameworks can call a shell function as if it were an external command. This is a Bash-specific extension with no POSIX equivalent.
- Functions are defined with
name() { ...; }and must appear before their first call in script execution order. $1,$2,$#, and"$@"inside a function refer to that function's own arguments, not the script's.returnonly sets an integer exit status (0-255); use stdout with command substitution or a nameref to pass actual data back.- Variables inside a function are global by default — use
localto scope them to the function and avoid collisions. local var=$(cmd)can mask the command's exit status underset -e; separate the declaration from the assignment when that matters.export -fmakes a function callable from a subshell, useful with tools likexargsorparallel.
Practice what you learned
1. What does the `return` statement actually set when used inside a Bash function?
2. What is the idiomatic way for a Bash function to 'return' a computed string value to the caller?
3. By default, how is a variable assigned inside a Bash function scoped?
4. What is the key difference between $* and "$@" when forwarding arguments in a function?
5. What does `export -f my_function` allow you to do?
Was this page helpful?
You May Also Like
Writing Your First Bash Script
Get hands-on with the anatomy of a Bash script — shebang, permissions, execution, comments, and structuring commands into a reusable, repeatable tool.
Command-Line Arguments and getopts
Learn how Bash scripts receive positional parameters, how to shift and iterate over them, and how to build proper option parsing with getopts.
Exit Codes and Error Handling
Understand how every command reports success or failure through its exit status, and how to build scripts that detect and respond to errors reliably.
Variables and Quoting in Bash
Understand how Bash stores and expands variables, and why quoting rules — single, double, and unquoted — are critical to writing scripts that don't break on real-world input.
Related Reading
Related Study Notes in DevOps
Browse all study notesNginx Study Notes
DevOps · 30 topics
DevOpsAnsible Study Notes
DevOps · 30 topics
DevOpsAdvanced Kubernetes Study Notes
Kubernetes · 30 topics
DevOpsAdvanced Bash Scripting Study Notes
Bash · 30 topics
DevOpsApache Kafka Study Notes
Kafka · 30 topics
DevOpsDocker & Kubernetes Study Notes
YAML · 40 topics