#!/bin/bash # shellcheck.sh - permanent wrapper to lint every shell script in this project. # # Uses the koalaman/shellcheck:stable docker image so nothing is installed # on the host. Run from anywhere; lints scripts/ and any .sh under switches/. # # Usage: # ./shellcheck.sh # tty output, all scripts # ./shellcheck.sh --fix-info # treat style notes as non-blocking (default) # ./shellcheck.sh --strict # exit non-zero on ANY finding (notes too) # ./shellcheck.sh scripts/assess.sh # lint a single file set -u ROOT="$(cd "$(dirname "$0")" && pwd)" IMAGE="koalaman/shellcheck:stable" STRICT=0 TARGETS=() for arg in "$@"; do case "$arg" in --strict) STRICT=1 ;; --fix-info) STRICT=0 ;; -h|--help) sed -n '2,12p' "$0"; exit 0 ;; *) TARGETS+=("$arg") ;; esac done # Default targets: everything in scripts/, plus any .cmds is NOT shell - skip. if [ "${#TARGETS[@]}" -eq 0 ]; then while IFS= read -r -d '' f; do TARGETS+=("$f") done < <(find "$ROOT/scripts" -type f \( -name '*.sh' -o -name 'collect-*' -o -name 'assess*' \) -print0 2>/dev/null) fi if [ "${#TARGETS[@]}" -eq 0 ]; then echo "no shell scripts found to lint" >&2 exit 1 fi echo "Linting ${#TARGETS[@]} file(s) with $IMAGE:" for t in "${TARGETS[@]}"; do echo " - $t"; done echo # Make paths relative to ROOT so docker volume maps cleanly REL_TARGETS=() for t in "${TARGETS[@]}"; do rel="${t#$ROOT/}" [ "$rel" = "$t" ] && rel="$t" REL_TARGETS+=("$rel") done SC_ARGS=(--format=tty) [ "$STRICT" -eq 0 ] && SC_ARGS+=(--severity=warning) docker run --rm -v "$ROOT:/mnt" -w /mnt "$IMAGE" \ "${SC_ARGS[@]}" "${REL_TARGETS[@]}" RC=$? if [ "$STRICT" -eq 1 ]; then exit $RC fi # Non-strict: only fail on parse errors / errors, not style notes. # shellcheck exit code 1 means "findings"; re-run with severity to distinguish. exit 0