skills/linux-bash-scripting/SKILL.md
Defensive Bash scripting for Linux: safe foundations, argument parsing, production patterns, ShellCheck compliance. Use when writing bash scripts, shell scripts, cron jobs, or CLI tools in bash.
npx skillsauth add iliaal/ai-skills linux-bash-scriptingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Produce bash scripts that pass shellcheck --enable=all and shfmt -d with zero warnings.
Target: GNU Bash 4.4+ on Linux. No macOS/BSD workarounds, no Windows paths, no POSIX-only restrictions.
#!/usr/bin/env bash
set -Eeuo pipefail
shopt -s inherit_errexit
readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
trap 'printf "Error at %s:%d\n" "${BASH_SOURCE[0]}" "$LINENO" >&2' ERR
trap 'rm -rf -- "${_tmpdir:-}"' EXIT
-E propagates ERR traps into functionsinherit_errexit propagates errexit into $() command substitutions_tmpdir=$(mktemp -d)main() { ... } with source guard: [[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@" -- enables sourcing for testing"$var", "$(cmd)", "${array[@]}"local for function variables, local -r for function constants, readonly for script constantsprintf '%s\n' over echo -- predictable behavior, no flag interpretation[[ ]] for conditionals; (( )) for arithmetic; $() over backticks--: rm -rf -- "$path", grep -- "$pattern" "$file": "${VAR:?must be set}"eval user input; build commands as arrays: cmd=("grep" "--" "$pat" "$f"); "${cmd[@]}"local from assignment to preserve exit codes: local val; val=$(cmd)PS4='+${BASH_SOURCE[0]}:${LINENO}: ' with bash -x -- shows file:line per commandreadonly EX_USAGE=64 EX_CONFIG=78 -- no magic numbers in exit"${PIPESTATUS[@]}" shows exit code of each pipe stage, not just last failure# NUL-delimited file processing
while IFS= read -r -d '' f; do
process "$f"
done < <(find /path -type f -name '*.log' -print0)
# Array from command output
readarray -t lines < <(command)
readarray -d '' files < <(find . -print0)
# Glob with no-match guard
for f in *.txt; do [[ -e "$f" ]] || continue; process "$f"; done
verbose=false; output=""
while [[ $# -gt 0 ]]; do
case "$1" in
-v|--verbose) verbose=true; shift ;;
-o|--output) output="$2"; shift 2 ;;
-h|--help) usage; exit 0 ;;
--) shift; break ;;
-*) printf 'Unknown: %s\n' "$1" >&2; exit 1 ;;
*) break ;;
esac
done
Dependency check:
require() { command -v "$1" &>/dev/null || { printf 'Missing: %s\n' "$1" >&2; exit 1; }; }
require jq; require curl
Dry-run wrapper:
run() { if [[ "${DRY_RUN:-}" == "1" ]]; then printf '[dry] %s\n' "$*" >&2; else "$@"; fi; }
run cp "$src" "$dst"
Atomic file write -- write to temp, rename into place:
atomic_write() { local tmp; tmp=$(mktemp); cat >"$tmp"; mv -- "$tmp" "$1"; }
generate_config | atomic_write /etc/app/config.yml
Retry with backoff:
retry() { local n=0 max=5 delay=1; until "$@"; do ((++n>=max)) && return 1; sleep $delay; ((delay*=2)); done; }
retry curl -fsSL "$url"
Script locking -- prevent concurrent runs:
exec 9>/var/lock/"${0##*/}".lock
flock -n 9 || { printf 'Already running\n' >&2; exit 1; }
Idempotent operations -- safe to rerun:
ensure_dir() { [[ -d "$1" ]] || mkdir -p -- "$1"; }
ensure_link() { [[ -L "$2" ]] || ln -s -- "$1" "$2"; }
Input validation: [[ "$1" =~ ^[1-9][0-9]*$ ]] || die "Invalid: $1" -- validate at script boundaries with [[ =~ ]]
umask 077 for scripts creating sensitive filestrap 'cleanup; exit 130' INT TERM -- preserves correct exit codes for callerslog() { printf '[%s] [%s] %s\n' "$(date -Iseconds)" "$1" "${*:2}" >&2; }
info() { log INFO "$@"; }
warn() { log WARN "$@"; }
error() { log ERROR "$@"; }
die() { error "$@"; exit 1; }
| Bad | Fix |
|-----|-----|
| for f in $(ls) | for f in *; do or find -print0 \| while read |
| local x=$(cmd) | local x; x=$(cmd) -- preserves exit code |
| echo "$data" | printf '%s\n' "$data" |
| cat file \| grep | grep pat file |
| kill -9 $pid first | kill "$pid" first, -9 as last resort |
| cd dir; cmd | cd dir || exit 1 or subshell (cd dir && cmd) |
${path%/*} not dirname, ${path##*/} not basename, ${var//old/new} not sed(( )) over expr; [[ =~ ]] over echo | grepval=$(cmd) once, reuse $valxargs -0 -P "$(nproc)" for parallel workdeclare -A map for lookups instead of repeated grep${var@Q} shell-quoted, ${var@U} uppercase, ${var@L} lowercasedeclare -n ref=varname nameref for indirect accesswait -n wait for any background job$EPOCHSECONDS, $EPOCHREALTIME -- timestamps without forking datesed -i (no '' suffix), grep -P (PCRE support), readlink -f (canonical path)timeout 30s cmd to prevent automation hangsRun shellcheck --enable=all script.sh. Key rules:
cd dir || exit${BASH_REMATCH[n]} not $n for regex capturesPre-commit: shellcheck *.sh && shfmt -i 2 -ci -d *.sh
Run shellcheck --enable=all and shfmt -d with zero warnings before declaring done. Test edge cases: empty input, missing files, spaces in paths.
tools
Tailwind CSS v4 patterns: CSS-first config, utility classes, component variants, v3 migration. Use when styling with Tailwind, configuring @theme tokens, using tailwind-variants/CVA, migrating v3 to v4, or fixing Tailwind styles and dark mode.
development
Simplifies, polishes, and declutters code without changing behavior. Use when asked to simplify, clean up, refactor, declutter, remove dead code or AI slop, or improve readability. For analysis-only reports without code changes, use code-simplicity-reviewer agent.
development
React architecture patterns, TypeScript, Next.js, hooks, and testing. Use when working with React component structure, state management, Next.js routing, Vitest, React Testing Library, or reviewing React code. For visual design and aesthetic direction, use frontend-design instead.
development
Pine Script v6: syntax, performance, error diagnosis, backtesting, visualization. Use when writing or debugging `.pine` files or TradingView Pine indicators/strategies.