Build the mental model
You define a bash function with `name() { ... }`, and once defined, you call it just like any other command — by name, with no special syntax for 'calling' it. Arguments passed to a function are available inside it through the exact same mechanism as script arguments: `$1`, `$2`, `$@`, `$#`, all scoped to that particular function call, which means everything already learned about positional parameters and quoting `"$@"` applies directly inside functions too, without any new syntax to learn. By default, every variable in bash is global regardless of where it's assigned, even deep inside a function — so a variable assigned inside a function silently overwrites any variable with the same name that exists outside it, unless you explicitly declare it with `local`, which scopes that variable to the current function call and restores whatever value (or absence of a value) existed before the function ran. Bash functions don't 'return' a computed value the way functions do in most other languages — `return` only sets a numeric exit status between 0 and 255, meant purely for signaling success or failure to the caller (typically checked with `if function_name; then`), not for handing back data; any attempt to `return` a string, or a number outside that range, either fails or gets silently truncated. To actually get a computed value out of a function, the idiomatic pattern is to `echo` the result and capture it with command substitution, `result=$(my_function)`, or, when that's awkward, to deliberately assign to a global variable instead.
Connect it to a real scenario
A script with several repeated steps — validating input, formatting a message, logging an event — benefits from wrapping each into a small function instead of copy-pasting the same lines everywhere, the way the lesson's example wraps greeting logic into `greet()` and summing logic into `sum_args()`. Look closely at `sum_args`: it declares `local total=0` before accumulating into it, so calling it repeatedly never leaks a stale total from a previous call, and it returns its actual answer by `echo`-ing `$total` and letting the caller capture it with `result=$(sum_args 2 4 6)` — not through `return`, which is reserved for the 0/1 exit status `is_even` uses instead. The example's final section shows exactly what goes wrong without `local`: `no_local_demo` assigns `counter=1` without declaring it local, silently overwriting the outer `counter=100`, so it stays overwritten at `1` even after the function returns; `with_local_demo`, by contrast, declares `local counter=999`, and once it returns, the outer `counter` is completely untouched by whatever happened inside.
Try the working example
#!/usr/bin/env bash
greet() {
local name="$1"
echo "Hello, $name!"
}
greet "Maung Maung"
sum_args() {
local total=0
for n in "$@"; do
total=$((total + n))
done
echo "$total"
}
result=$(sum_args 2 4 6)
echo "Sum: $result"
is_even() {
local n="$1"
if [ $((n % 2)) -eq 0 ]; then
return 0
else
return 1
fi
}
if is_even 8; then
echo "8 is even (exit status was 0)"
fi
counter=100
no_local_demo() {
counter=1 # missing "local" - overwrites the outer/global counter!
echo "inside no_local_demo, counter = $counter"
}
with_local_demo() {
local counter=999 # local - only affects this function
echo "inside with_local_demo, counter = $counter"
}
no_local_demo
echo "after no_local_demo, global counter = $counter"
with_local_demo
echo "after with_local_demo, global counter = $counter"Running this script prints:
Hello, Maung Maung!
Sum: 12
8 is even (exit status was 0)
inside no_local_demo, counter = 1
after no_local_demo, global counter = 1
inside with_local_demo, counter = 999
after with_local_demo, global counter = 15-minute try-it
Write a function called `to_upper` that takes one string argument and echoes its uppercase version (you can pipe the argument through `tr 'a-z' 'A-Z'` inside the function), capture its result into a variable with command substitution, and print the result alongside a global variable to confirm the function didn't accidentally change it.
One important caution
Omitting local inside a function, so a variable name that coincidentally matches one used elsewhere in the script gets silently overwritten (accidental global leakage)
Trying to 'return' a computed string or number with return, not realizing return only accepts exit-status integers from 0-255 and truncates or errors on anything else
GNU Bash Reference Manual — Shell Functions — Bash / Shell Scripting