Lesson 10 / 25

Functions, Arguments and Return Values

Write reusable functions with local variables and meaningful exit statuses.

Small, named building blocks

Define a function with name() { ...; } and call it like a command: greet Asha. Inside, arguments are $1, $2…, $# is their count and "$@" expands to all of them as separate, correctly quoted words; always use "$@" (with quotes) to pass arguments through, never $* or unquoted $@. shift drops the first argument and moves the rest down. Variables in functions are global by default; declare them with local to avoid accidentally overwriting variables elsewhere. A function's return N sets its exit status (0–255), which is for success or failure, not for data. To return data, print it and capture it with command substitution: result=$(get_version). exit inside a function ends the whole script (unless it runs in a subshell). Keep functions short and give them names that read like commands: log, die, require_command, backup_database.

Arguments in, status and output out

A function takes arguments, prints data on stdout and reports success or failure through its exit status.

A box with three input arrows on the left, one output arrow on the right labelled with a document icon, and a small badge on top showing a tick or cross.
Figure 4.1 — Inputs, printed output and exit status of a function.

Helper functions most scripts need

Logging to stderr, failing loudly and checking dependencies.

log()  { printf '%s [%s] %s\n' "$(date +%H:%M:%S)" "${FUNCNAME[1]:-main}" "$*" >&2; }
die()  { log "ERROR: $*"; exit 1; }

require_command() {
    local cmd
    for cmd in "$@"; do
        command -v "$cmd" > /dev/null || die "missing required command: $cmd"
    done
}

file_size_mb() {
    local file="$1"
    [[ -f $file ]] || return 1          # status for failure
    local bytes
    bytes=$(stat -c %s "$file")
    echo $(( bytes / 1024 / 1024 ))     # data on stdout
}

require_command curl jq
if size=$(file_size_mb /var/log/syslog); then log "syslog is ${size} MB"; fi

"$@" is almost always what you want

"$@" keeps each argument intact, including ones with spaces. $* joins them into one string, and unquoted $@ re-splits them. Wrappers that forget the quotes break on the first file name with a space.

Quick check: How should a Bash function return a computed string to its caller?

  • return "$value"
  • Set $? to the string
  • Print it and let the caller capture it with $(function)
  • exit "$value"
Answer

Print it and let the caller capture it with $(function) — return only sets a numeric status; data is returned by printing to stdout.