# Functions, Arguments and Return Values — Bash / Shell Scripting

Source: https://www.skillbyai.com/en/bash/fn-basics

> 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.](assets/figures/bash/section-4-map.svg) — 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.

```bash
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.

**Quiz:** How should a Bash function return a computed string to its caller?

- [ ] return "$value"
- [ ] Set $? to the string
- [x] 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.
