Functions & robust scripts
Functions, local variables, arrays, set -euo pipefail, trap, getopts and ShellCheck.
Short scripts can get away with anything. Scripts that run unattended (from cron, CI or deployments) need to be organised and fail safely: stop at the first error, clean up after themselves, and explain what went wrong. This lesson covers functions, local variables, arrays, strict mode, traps, option parsing with getopts, debugging, and ShellCheck.
Functions#
A function is a named group of commands. It must be defined before it's called, and it receives arguments just like a script: $1, $2, $#, "$@". ($0 is still the script name.) The function greet { ... } form also exists, but name() { ... } is the portable style.
Returning values
return N sets the function's exit status (0 = success). To return data, print it and capture the output:
Local variables
Variables in bash are global by default, even inside functions. Use local to keep them private:
Make every function variable local unless you deliberately want to share it.
Arrays#
Bash has indexed arrays and (since bash 4) associative arrays:
"${arr[@]}"(quoted) expands to every element as a separate word. That's the array version of"$@".${#arr[@]}is the length and${!arr[@]}gives the indexes or keys.- Arrays are perfect for building command lines safely:
opts=(-avz --delete); rsync "${opts[@]}" src/ dest/.
Strict mode: set -euo pipefail#
By default bash keeps going after a command fails, which is dangerous:
Start serious scripts with these lines:
Know the gotchas of set -e:
- Commands in
if,while,&&/||chains are allowed to fail; that's how conditions work. ((count++))returns status 1 whencountwas 0, which exits the script. Usecount=$((count + 1))or((count++)) || true.grepreturns 1 when it finds nothing. If that's OK, writegrep pattern file || true.- Use
${VAR:-}to read an optional variable underset -u.
trap: cleanup and error reporting#
trap runs a command when the script receives a signal or exits:
EXITruns on every exit: success, error, orexit N. It's ideal for deleting temp files, removing lock files and stopping helper processes.ERRruns when a command fails (withset -e). It's useful for logging where it failed.INT TERMcatch Ctrl+C andkill:trap 'echo interrupted; exit 130' INT TERM.mktemp/mktemp -dcreate unique temp files and directories safely. Never hard-code/tmp/myfile.
Parsing options with getopts#
For real command-line tools, accept options like -v or -o file:
In the getopts string ":vn:h", a letter followed by : takes a value (-n 2), and the leading : lets you handle errors yourself. getopts handles short options only. For long options (--verbose), loop over the arguments with case "$1" in --verbose) ...; esac; shift.
Debugging#
bash -x (or set -x in the script, with set +x to turn it off) prints every command after expansion, prefixed with +. It's the fastest way to see what a script actually ran. bash -n script.sh checks the syntax without running anything.
ShellCheck: a linter for shell scripts#
ShellCheck catches quoting bugs, typos and portability issues before they bite. Install it with sudo apt install shellcheck or sudo dnf install ShellCheck, or use the editor plugins for VS Code and others:
Run ShellCheck on every script you commit; many teams enforce it in CI.
A robust script template#
Wrapping the logic in main "$@" at the bottom means every function is defined before anything runs, and the script reads top-down like a table of contents.
💡 Know when to switch languages. If a script grows past a couple of hundred lines, needs complex data structures, JSON handling or real error handling, rewrite it in Python. Bash is brilliant glue, not a general-purpose language.
Common mistakes#
- Forgetting
local: functions silently overwrite globals. - Trying to
return "string": return only takes numbers 0–255. Print and capture instead. - No strict mode: scripts keep running after
cdorcpfails. ((i++))underset -ewheniis 0, which exits the script unexpectedly.- Hard-coded temp paths like
/tmp/out.txt(collisions, security). Usemktemp. - Unquoted
"${array[@]}": elements with spaces split apart.
What's next#
Your scripts are robust enough to run unattended. Next you'll make them run on a schedule with cron and systemd timers.
Check your understanding
Quick quiz
1.What does
set -euo pipefaildo?2.How does a bash function return a string to its caller?
3.What is
trap cleanup EXITused for?
Finished reading?
Mark this lesson complete to track your progress.