Lab 03: Shell Scripting — Functions & Arguments
Time: 30 minutes | Level: Practitioner | Docker: docker run -it --rm ubuntu:22.04 bash
Overview
Functions transform scripts from linear sequences into reusable, testable components. In this lab you'll define functions, scope variables with local, capture return values, work with special argument variables ($0, $1, $@, $#, $?), parse command-line options with getopts, and use shift to consume arguments.
Step 1: Defining and Calling Functions
Two equivalent syntaxes exist. The function keyword is optional:
greet() {
echo "Hello, $1!"
}
greet "World"
greet "Alice"📸 Verified Output:
Hello, World!
Hello, Alice!💡 Tip: Functions must be defined before they're called (Bash reads top-to-bottom). A common pattern is to define all functions at the top, then have a
main()function at the bottom that runs last.
Step 2: Local Variables — Scope Isolation
Without local, all variables in a function are global and can corrupt outer state:
📸 Verified Output:
💡 Tip: Always use
localfor variables inside functions. This is especially critical when function names shadow global variables. A bug where an inner function modifies an outer variable is extremely hard to track down.
Step 3: Return Values — Numeric Exit Codes
Bash return only returns integers 0–255 (the function's exit status). To return string/complex data, use echo and command substitution:
📸 Verified Output:
💡 Tip: The pattern
result=$(func args)captures everything the function prints to stdout. This makes functions composable — the output of one function can become the input of another.
Step 4: Special Variables — $0, $1, $@, $#
These automatic variables let functions and scripts know about their invocation context:
📸 Verified Output:
💡 Tip: Inside a function,
$0still refers to the script name (not the function name). To get the function's name, use${FUNCNAME[0]}. The$@variable is the gold standard for passing all arguments to another command — it preserves quoting correctly.
Step 5: Exit Status — $?
$? holds the exit code of the last command. 0 means success; non-zero means failure:
📸 Verified Output:
💡 Tip: Check
$?immediately after a command — the next command will overwrite it. For clarity, assign it:status=$?; if [ $status -ne 0 ]; then .... Or useif command; thendirectly.
Step 6: shift — Consuming Arguments
shift removes $1 and shifts all other positional parameters down:
📸 Verified Output:
💡 Tip:
shift Nshifts N positions at once. This is useful for skipping past an option and its value together: after parsing-o value, callshift 2to consume both. Withoutshift, the loop would never terminate.
Step 7: getopts — Proper Option Parsing
getopts is the POSIX-standard way to parse -f, -v, -n value style options:
📸 Verified Output:
💡 Tip: A colon after a letter in the
getoptsstring (liken:) means that option requires an argument, available via$OPTARG. Aftergetoptsfinishes,shift $((OPTIND - 1))removes all parsed options, leaving only positional arguments in$@.
Step 8: Capstone — Reusable Script Library
Build a small library of utility functions with proper argument handling, exit codes, and option parsing — the kind you'd source into real scripts:
📸 Verified Output:
💡 Tip: Source a shared library into your scripts with
. /path/to/utils.sh(orsource /path/to/utils.sh). This lets multiple scripts share the samelog(),die(), and validation functions — keeping your codebase DRY.
Summary
Define function
name() { ... }
Define before calling
Local variable
local var="value"
Prevents global pollution
Function output
result=$(func args)
Capture via command substitution
Argument 1, 2…
$1, $2, $N
Positional parameters
All arguments
"$@"
Preserves quoting
Argument count
$#
Number of positional params
Script name
$0
Name of the script file
Last exit status
$?
0=success, non-zero=failure
Return exit code
return N
0–255 only
Shift args
shift / shift N
Consume positional params
Parse options
getopts "ab:c" opt
: means arg required
Option argument
$OPTARG
Value for options with :
Function name
${FUNCNAME[0]}
Current function's name
Last updated
