๐ง Functions & Script Libraries
Functions and script libraries allow shell code to be organized into reusable, maintainable units.
They are essential for building scalable automation, reducing duplication, and enforcing consistent behavior across scripts.
๐ Who This Is For
- Engineers building shared tooling or automation frameworks
- DevOps/SRE teams standardizing shell code across environments
- Developers maintaining large or multiโfile shell projects
- Anyone writing scripts that must be testable, modular, and predictable
๐งฉ Role in the Ecosystem
Functions and libraries provide:
- Encapsulation of logic
- Reuse across multiple scripts
- Clear separation of responsibilities
- Improved readability and maintainability
- A foundation for building higherโlevel tooling
They are the closest equivalent to modules in POSIX shell scripting.
๐งฉ Key Concepts
1. Function Definitions
| log() {
printf '[%s] %s\n' "$(date -Is)" "$*" >&2
}
|
- Functions must be defined before use in POSIX sh
- They inherit the parent shell environment
- They cannot return values directly (use stdout or exit codes)
2. Return Codes
| do_step() {
command || return 1
}
|
Functions communicate success/failure via exit codes.
3. Script Libraries
| # lib/log.sh
log() { ... }
# main.sh
. "$(dirname "$0")/lib/log.sh"
log "Starting..."
|
- Loaded using
. (POSIX) or source (bash)
- Executed in the current shell, not a subshell
- Can define functions, variables, and configuration
4. Namespacing
Use prefixes to avoid collisions:
| fs_read() { ... }
fs_write() { ... }
|
๐ง Notable Techniques
Argument Handling
| greet() {
name="$1"
echo "Hello, $name"
}
|
Returning Values via stdout
| get_timestamp() {
date -Is
}
ts="$(get_timestamp)"
|
Returning Values via Variables (bash/ksh)
| get_pid() {
pid=$$
}
get_pid
echo "$pid"
|
Library Discovery
| SCRIPT_DIR="$(CDPATH= cd -- "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/lib/utils.sh"
|
Guarding Against Multiple Loads
| [ -n "${_UTILS_LOADED:-}" ] && return
_UTILS_LOADED=1
|
โ ๏ธ Limitations & Pitfalls
- No true namespaces โ name collisions are common
- No private functions or variables
- Libraries execute in the current shell (side effects persist)
- Functions cannot return structured data
- Subshells discard variable changes
- Bashโonly features break portability
๐ง When to Use Functions & Libraries
- When logic is reused across multiple scripts
- When scripts grow beyond a few dozen lines
- When building internal tooling or automation frameworks
- When enforcing consistent logging, error handling, or formatting
- When improving readability and maintainability
โ When Not to Use Them
- Extremely small oneโoff scripts
- Scripts intended to run in highly restricted environments
- When portability across shells is uncertain
- When the logic is complex enough to justify a real language (Python, Go, etc.)
โ
Best Practices
- Keep libraries POSIXโcompliant unless bash is explicitly required
- Use prefixes for namespacing (
log_, fs_, str_)
- Avoid global state unless necessary
- Document all public functions at the top of the library
- Keep functions small and focused
- Use explicit return codes:
- Use Parameter Expansion instead of external tools
- Avoid subshells when persistence is required
- Load libraries using absolute paths for reliability
๐งช Testing Functions & Libraries
| set -x
. ./lib/log.sh
log "test message"
|
Test portability:
| dash script.sh
bash --posix script.sh
ksh script.sh
|
Test return codes:
| if myfunc; then
echo ok
else
echo fail
fi
|
๐ง Summary
Functions and script libraries are the foundation of maintainable shell code.
They enable reuse, structure, and clarity โ essential for productionโgrade automation.
Use them to organize logic, enforce consistency, and build scalable tooling across your shell ecosystem.