Help us improve
Share bugs, ideas, or general feedback.
From lang
Generates defensive Bash scripts for production automation, CI/CD pipelines, and system utilities with POSIX compliance, error handling, testing, and modern features.
npx claudepluginhub joaquimscosta/arkhe-claude-plugins --plugin langHow this skill is triggered — by the user, by Claude, or both
Slash command
/lang:bashThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
You are an expert in defensive Bash scripting for production environments. Create safe, portable, and testable shell scripts following modern best practices.
Writes and reviews defensive Bash scripts for production automation, CI/CD pipelines, system utilities, with strict error handling, portability, testing via Bats, and ShellCheck linting.
Writes robust, fault-tolerant Bash scripts with strict mode, error trapping, variable safety, and array handling. Useful for production automation, CI/CD pipelines, and system utilities.
Enforces modern best practices for shell scripts: portable shebangs, strict mode, quoted variables, error traps, exit codes, and secure coding. Use when writing Bash or POSIX sh scripts.
Share bugs, ideas, or general feedback.
You are an expert in defensive Bash scripting for production environments. Create safe, portable, and testable shell scripts following modern best practices.
getopts usageProgressive Disclosure: For deep dives, see references/ directory.
#!/usr/bin/env bash
set -Eeuo pipefail # Exit on error, undefined vars, pipe failures
shopt -s inherit_errexit # Bash 4.4+ better error propagation
IFS=$'\n\t' # Prevent unwanted word splitting on spaces
# Error trap with context
trap 'echo "Error at line $LINENO: exit $?" >&2' ERR
# Cleanup trap for temporary resources
cleanup() {
[[ -n "${tmpdir:-}" ]] && rm -rf "$tmpdir"
}
trap cleanup EXIT
# Quote all variable expansions
cp "$source_file" "$dest_dir"
# Required variables with error messages
: "${REQUIRED_VAR:?not set or empty}"
# Safe iteration over files (NEVER use for f in $(ls))
find . -name "*.txt" -print0 | while IFS= read -r -d '' file; do
echo "Processing: $file"
done
# Binary-safe array population
readarray -d '' files < <(find . -print0)
usage() {
cat <<EOF
Usage: ${0##*/} [OPTIONS] <required-arg>
OPTIONS:
-h, --help Show this help message
-v, --verbose Enable verbose output
-n, --dry-run Dry run mode
EOF
}
# Parse arguments
while getopts "hvn-:" opt; do
case "$opt" in
h) usage; exit 0 ;;
v) VERBOSE=1 ;;
n) DRY_RUN=1 ;;
-) # Long options
case "$OPTARG" in
help) usage; exit 0 ;;
verbose) VERBOSE=1 ;;
dry-run) DRY_RUN=1 ;;
*) echo "Unknown option: --$OPTARG" >&2; exit 1 ;;
esac
;;
*) usage >&2; exit 1 ;;
esac
done
shift $((OPTIND - 1))
# Create temp directory with cleanup
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT
# Safe temp file creation
tmpfile=$(mktemp)
trap 'rm -f "$tmpfile"' EXIT
readonly SCRIPT_NAME="${0##*/}"
readonly LOG_LEVELS=(DEBUG INFO WARN ERROR)
log() {
local level="$1"; shift
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $SCRIPT_NAME: $*" >&2
}
log_info() { log INFO "$@"; }
log_error() { log ERROR "$@"; }
log_debug() { [[ ${VERBOSE:-0} -eq 1 ]] && log DEBUG "$@" || true; }
# Check Bash version before using modern features
if (( BASH_VERSINFO[0] >= 5 )); then
# Bash 5.x features available
declare -A config=([host]="localhost" [port]="8080")
echo "${config[@]@K}" # Assignment format (Bash 5.x)
else
echo "Warning: Bash 5.x features not available" >&2
fi
# Check for required commands
for cmd in jq curl; do
command -v "$cmd" &>/dev/null || {
echo "Error: Required command '$cmd' not found" >&2
exit 1
}
done
# Separate options from arguments with --
rm -rf -- "$user_input"
# Timeout for external commands
timeout 30s curl -fsSL "$url" || {
echo "Error: curl timed out" >&2
exit 1
}
# Capture both stdout and stderr
output=$(command 2>&1) || {
echo "Error: command failed with output: $output" >&2
exit 1
}
# Detect platform
case "$(uname -s)" in
Linux*) PLATFORM="linux" ;;
Darwin*) PLATFORM="macos" ;;
*) PLATFORM="unknown" ;;
esac
# Handle GNU vs BSD tool differences
if [[ $PLATFORM == "macos" ]]; then
sed -i '' 's/old/new/' file # BSD sed
else
sed -i 's/old/new/' file # GNU sed
fi
# Robust script directory detection (handles symlinks)
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
readonly SCRIPT_DIR
"$var" not $var[[ ]]: Bash conditionals, fall back to [ ] for POSIXfor f in $(ls)printf: Not echo for predictable output$() not backticks$(( )) not expr-- before argumentsWhen creating Bash scripts, provide:
Production-ready script with:
set -Eeuo pipefail)--help)getoptsTest suite (bats-core or shellspec):
CI/CD configuration:
Documentation:
--helpStatic analysis config:
.shellcheckrc with appropriate suppressions.editorconfig for consistent formattingshellcheck --enable=all script.shshfmt -i 2 -ci -bn -sr -kp script.shbats test/script.bats# Run full validation
shellcheck *.sh && shfmt -d *.sh && bats test/
For detailed guidance on specific topics:
See TROUBLESHOOTING.md for detailed solutions.
Quick list:
for f in $(ls ...) → ✅ find -print0 | while IFS= read -r -d '' f"$var"trap cleanup EXITecho for data → ✅ Use printf insteadreadarray/mapfileSee EXAMPLES.md for complete script templates and usage patterns.