Functions in Bash let you group reusable commands, accept arguments, return values, and build modular scripts. They are the key to writing maintainable shell scripts that don't repeat code. This page covers defining functions, argument handling, local variables, and practical real-world patterns.
ShellBashFunctionsScriptinggreet() {
echo "Hello, $1!"
}
# Call the function
greet "World" # Output: Hello, World!
greet "Alice" # Output: Hello, Alice!
name() { ... }. Positional parameters $1, $2, etc. hold the arguments passed when calling. Functions must be defined before they are called in the script. No need to declare parameter types or count.calculate() {
local result=$(( $1 + $2 ))
echo "Sum: $result"
}
result="global value"
calculate 3 7 # Output: Sum: 10
echo $result # Still: global value
local keyword to declare variables that are scoped to the function. Without local, variables are global and can overwrite variables in the calling scope. Always use local for function-internal variables to prevent bugs.get_timestamp() {
echo $(date +%Y%m%d_%H%M%S)
}
ts=$(get_timestamp)
echo "Backup file: backup_${ts}.tar.gz"
return only sets an exit code (0-255). The idiomatic way to return a string value is to echo it and capture the output with command substitution $().deploy() {
local env="${1:-production}"
local branch="${2:-main}"
echo "Deploying $branch to $env"
}
deploy # production / main
deploy staging # staging / main
deploy staging dev # staging / dev
${var:-default} syntax uses default if var is unset or empty. This is the standard pattern for optional function arguments with sensible defaults. It works for positional parameters too.backup_db() {
if [ $# -lt 2 ]; then
echo "Usage: backup_db <host> <dbname>" >&2
return 1
fi
local host="$1"
local db="$2"
echo "Backing up $db from $host"
}
$# is the count of arguments passed. Check it at the start of functions that require specific arguments. Write usage messages to stderr (>&2) and use return 1 to signal an error without exiting the script.log_run() {
echo "Running: $*"
"$@"
echo "Exit code: $?"
}
log_run ls -la /tmp
"$@" expands to all positional parameters as separate quoted words — the safe way to forward all arguments to another command. $* expands to a single string. Always use "$@" when forwarding arguments.require_command() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "Error: '$1' is required but not installed." >&2
exit 1
fi
}
require_command docker
require_command jq
command -v checks if a command is available in PATH without running it. This is more portable than which. Redirecting to /dev/null suppresses all output. Use this at the start of scripts to fail fast with clear error messages.log() {
local level="$1"
shift
echo "[$(date +%H:%M:%S)] [$level] $*"
}
log INFO "Starting deployment"
log WARN "Config file not found, using defaults"
log ERROR "Database connection failed"
shift removes the first argument (level), leaving $@ as the message. This pattern makes scripts much easier to debug and monitor.retry() {
local max="${1}"; shift
local count=0
until "$@"; do
count=$((count + 1))
[ $count -ge $max ] && return 1
echo "Attempt $count failed, retrying..."
sleep 2
done
}
retry 3 curl -sf https://api.example.com/health
"$@".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
local for all variables inside functions to avoid polluting the global scope.$# at the start and print a usage message if required args are missing."$@" to forward all arguments; capture return values with $(function_name).Bash's return statement only sets an integer exit code (0 = success, non-zero = error). To return a string, echo it inside the function and capture it with command substitution: result=$(my_function). For multiple values, echo them separated by a delimiter and use IFS to split.
"$@" expands each argument as a separate quoted word: "arg1" "arg2" "arg3". "$*" expands all arguments as a single word joined by IFS (default space): "arg1 arg2 arg3". Always use "$@" when forwarding arguments to preserve spaces within individual arguments.
Yes, use export -f function_name to make a function available in child processes and subshells. This is useful when you need a function to be available in scripts called from your script. However, note that exported functions are passed via environment variables and can have security implications.