| name | bash-scripting |
| description | Write, review, debug, and improve bash/shell scripts for any context: CI/CD pipelines (GitHub Actions, GitLab CI, Bitrise), DevOps automation, macOS/Linux system tasks, file processing, and data transformation. Use this skill whenever the user asks to write a shell script, improve an existing one, debug a bash error, or automate any task via command line — even if they just say "make a script that does X" or paste a broken script without explanation. Also trigger for requests involving cron jobs, entrypoints, deploy scripts, migration scripts, or any .sh file.
|
Bash Scripting Skill
Phase 1: Understand Before Writing
Before writing a script, extract:
- Shell target — bash? zsh? POSIX sh? If unspecified, default to
#!/usr/bin/env bash.
- Execution environment — local machine, CI runner (GitHub Actions / GitLab / Bitrise), Docker container, remote server?
- Inputs — args, env vars, stdin, files?
- Outputs — stdout, files, exit codes, side effects?
- Error behavior — fail-fast or continue? Silent or loud?
- Idempotency — must the script be safe to run multiple times?
If the user pastes a broken script, skip the interview and go straight to diagnosis.
Phase 2: Script Standards
Apply these to every script you write or review:
Safety header (always include)
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -e — exit on error
set -u — treat unset variables as errors
set -o pipefail — catch errors in pipes
IFS — safer word splitting
Exception: Omit set -e only if the script intentionally continues on errors (document why).
Variable hygiene
echo "$var"
echo $var
echo "${prefix}_suffix"
readonly MAX_RETRIES=3
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
Error handling patterns
cleanup() {
local exit_code=$?
rm -f "$tmpfile"
exit "$exit_code"
}
trap cleanup EXIT
die() {
echo "ERROR: $*" >&2
exit 1
}
require() {
command -v "$1" >/dev/null 2>&1 || die "Required tool not found: $1"
}
Argument parsing
For simple scripts use positional args with validation:
[[ $# -lt 1 ]] && die "Usage: $(basename "$0") <arg1> [arg2]"
ARG1="${1:?'arg1 is required'}"
For complex scripts use getopts:
while getopts ":f:o:vh" opt; do
case $opt in
f) INPUT_FILE="$OPTARG" ;;
o) OUTPUT_DIR="$OPTARG" ;;
v) VERBOSE=true ;;
h) usage; exit 0 ;;
:) die "Option -$OPTARG requires an argument" ;;
\?) die "Unknown option: -$OPTARG" ;;
esac
done
shift $((OPTIND - 1))
Logging
log() { echo "[$(date '+%H:%M:%S')] $*"; }
warn() { echo "[WARN] $*" >&2; }
err() { echo "[ERROR] $*" >&2; }
Phase 3: Context-Specific Patterns
CI/CD (GitHub Actions / GitLab / Bitrise)
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "${HOME}/.local/bin" >> "$GITHUB_PATH"
echo "::group::Build step"
echo "::endgroup::"
echo "::add-mask::${SECRET_VALUE}"
echo -e "section_start:$(date +%s):build\r\e[0KBuilding..."
echo -e "section_end:$(date +%s):build\r\e[0K"
CI script checklist:
- Never hardcode secrets — use env vars
- Always set
set -euo pipefail
- Exit with explicit codes for downstream steps
- Log versions of critical tools at start (
xcodebuild -version, ruby --version, etc.)
- Use
|| true consciously, never reflexively
File & data processing
tmpfile=$(mktemp)
trap 'rm -f "$tmpfile"' EXIT
while IFS= read -r -d '' file; do
process "$file"
done < <(find . -name "*.txt" -print0)
while IFS= read -r line; do
echo "$line"
done < input.txt
[[ -f "$file" ]] || die "File not found: $file"
[[ -d "$dir" ]] || mkdir -p "$dir"
macOS-specific
[[ "$(uname)" == "Darwin" ]] || die "macOS only"
require gdate
tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/myscript.XXXXXX")
Retry logic (useful in CI)
retry() {
local max_attempts=${MAX_RETRIES:-3}
local delay=${RETRY_DELAY:-5}
local attempt=1
until "$@"; do
if (( attempt >= max_attempts )); then
err "Command failed after $attempt attempts: $*"
return 1
fi
warn "Attempt $attempt failed. Retrying in ${delay}s..."
sleep "$delay"
(( attempt++ ))
done
}
retry curl -f "https://example.com/api"
Phase 4: Review Checklist
When reviewing an existing script, check in order:
- Safety — shebang +
set -euo pipefail present?
- Quoting — all
$vars quoted? "$@" not $@?
- Uninitialized vars — any variable used before being set?
- Pipe failures —
cmd1 | cmd2 — does failure in cmd1 propagate?
- Temp file leaks — cleanup on EXIT trapped?
- Hardcoded values — paths, credentials, env-specific strings?
- Exit codes — does the script exit non-zero on failure?
- Error messages — do errors go to stderr (
>&2)?
- Idempotency — what happens on second run?
- Shellcheck — mentally apply common shellcheck rules (SC2086, SC2046, SC2006)
Phase 5: Output Format
For new scripts: Write the complete script, then add a brief section:
- What it does
- How to run it (
chmod +x script.sh && ./script.sh <args>)
- Key assumptions / dependencies
For reviews: List issues by severity (🔴 Critical / 🟡 Warning / 🟢 Suggestion), then provide corrected version.
For debugging: State the root cause first, then the fix. Don't just swap code — explain why.
Common Pitfalls Reference
See references/pitfalls.md for an expanded list of bash gotchas with examples.
See references/ci-patterns.md for CI/CD-specific snippets (Bitrise, GitHub Actions, GitLab).