Skip to content
Modular Bash with Functions and Sourcing
step 1/5

Reading — step 1 of 5

Learn

~1 min readPerformance and Tools

Real Bash projects use functions in libraries, sourced from main scripts. The patterns:

Library files — define functions, no top-level execution:

# lib/log.sh
log_info()  { echo "[INFO]  $*" >&2; }
log_error() { echo "[ERROR] $*" >&2; }

source (or .) loads them:

#!/bin/bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
source "$SCRIPT_DIR/lib/log.sh"

log_info "starting"

Idempotent loading — guard against double-source:

# lib/log.sh
if [[ -n "${__LIB_LOG_SOURCED:-}" ]]; then return; fi
__LIB_LOG_SOURCED=1

log_info()  { ... }

Subcommand pattern — git-like CLIs:

#!/bin/bash
cmd="${1:-help}"
shift || true

cmd_init()    { echo "initializing..."; }
cmd_build()   { echo "building $@"; }
cmd_help()    { echo "usage: $0 {init|build|help}"; }

if declare -F "cmd_$cmd" > /dev/null; then
    "cmd_$cmd" "$@"
else
    cmd_help
    exit 1
fi

declare -F name checks if a function exists. Combined with naming conventions, you get extensible CLIs.

Argument parsing with getopts:

verbose=0
output="-"

while getopts "vo:h" opt; do
    case $opt in
        v) verbose=1 ;;
        o) output="$OPTARG" ;;
        h) usage; exit 0 ;;
        *) usage; exit 1 ;;
    esac
done
shift $((OPTIND - 1))      # remove parsed options
# remaining args in $@

getopts handles -v, -o file, -vo file, etc. For long options (--verbose), use getopt (different tool, less portable) or parse manually.

Trap-based main:

main() {
    parse_args "$@"
    setup
    do_work
}

main "$@"

Cleanly separates everything from top-level code, makes the script's flow obvious.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…