100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Bash

Functions in Bash

Learn how to define reusable Bash functions, pass arguments and return values, manage variable scope with local, and organize larger scripts around them.

Bash Scripting In DepthIntermediate9 min readJul 9, 2026
Analogies

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.

bash
#!/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" 80

Arguments, $#, $@, 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.

bash
# 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"; fi

return 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.

bash
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"   # 8

Bash 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.
  • return only 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 local to scope them to the function and avoid collisions.
  • local var=$(cmd) can mask the command's exit status under set -e; separate the declaration from the assignment when that matters.
  • export -f makes a function callable from a subshell, useful with tools like xargs or parallel.

Practice what you learned

Was this page helpful?

Topics covered

#Bash#LinuxShellScriptingStudyNotes#DevOps#FunctionsInBash#Functions#Defining#Calling#Arguments#StudyNotes#SkillVeris