Install
$ agentstack add skill-d-padmanabhan-agent-engineering-handbook-bash-shell-scripting ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Bash & Shell Scripting
Core Principles
- DRY: Don't Repeat Yourself
- KISS: Keep It Simple
- Fail Fast: Exit on errors immediately
- Zero Warnings: Must pass shellcheck
Quick Reference
set -euo pipefail # Strict mode (fail-fast)
set -uo pipefail # Controlled mode (explicit error handling)
set -Euo pipefail # Strict + ERR trap propagation
command -v cmd >/dev/null # Check if command exists (portable)
trap 'cleanup' EXIT # Always cleanup
flock -n 200 || exit 1 # Prevent concurrent runs
readonly VAR="value" # Immutable constant
local var="value" # Function-local variable
Core Standards
| Aspect | Standard | |--------|----------| | Shebang | #!/usr/bin/env bash or #!/bin/bash | | Safety Mode | set -euo pipefail (strict) or set -uo pipefail (controlled) | | Linting | Must pass shellcheck with 0 errors/warnings | | Formatting | Must pass shfmt -i 2 -ci -sr -bn; prefer ~100-character lines | | Extension | .sh for scripts | | Variables | Uppercase constants/globals, lowercase locals, descriptive names, braces + quotes ("${VAR}") |
Generation Contract
For any non-trivial script (more than 10-15 lines, more than one command phase, argument parsing, dependency checks, config resolution, build/deploy/verify steps), generate this structure:
- Header block.
- Commented debug toggle (
# set -x), disabled by default. - Strict or controlled mode.
- Readonly constants and timestamped
LOGFILE. logmsg,die, and optionaldebughelper.require_commandfor dependencies.- Helper functions with lowercase locals.
parse_args.main().main "$@"as the final line.
Reject generated Bash that skips this structure unless the script is intentionally tiny and linear.
Error Handling Modes
Strict Mode (Fail-Fast)
set -euo pipefail # Exit immediately on any error
Use for: Simple linear scripts, dependency installation, straightforward validation.
Controlled Mode (Explicit)
set -uo pipefail # No -e: handle errors explicitly
Use for: Diagnostics, cleanup operations, commands where failure is expected.
Strict + ERR Trap
set -Euo pipefail
trap 'echo "ERROR in ${FUNCNAME[0]:-main} at line $LINENO"' ERR
Use for: Production scripts with comprehensive error handling.
Script Template
#!/usr/bin/env bash
#
# Script Name : .sh
#
# Purpose :
#
# Dependencies :
#
# Script Usage : ./.sh [options]
#
#
#
##----------------------------------------------------------------------------------------##
# Turn debug on or off
# set -x
##----------------------------------------------------------------------------------------##
set -euo pipefail
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly DTTM="$(date -u +"%Y%m%d_%H%M%S")"
readonly SCRIPT_NAME="$(basename "${0}" .sh)"
readonly LOGFILE="${SCRIPT_NAME}_${DTTM}.log"
cleanup() {
rm -f "${TEMP_FILE:-}" 2>/dev/null || true
}
trap cleanup EXIT
logmsg() {
local timestamp
timestamp="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
printf "%s: %s\n" "${timestamp}" "$*" | tee -a "${LOGFILE}" >&2
}
die() {
logmsg "ERROR: $*"
exit 1
}
debug() {
[[ "${DEBUG:-0}" == "1" ]] && logmsg "DEBUG: $*"
}
require_command() {
local command_name="$1"
command -v "${command_name}" >/dev/null 2>&1 || die "Missing command: ${command_name}"
}
main() {
local arg="${1:-}"
[[ -z "${arg}" ]] && die "Usage: ${SCRIPT_NAME} "
require_command jq
logmsg "Processing: ${arg}"
# Main logic here
}
main "$@"
Minimal dependency helper:
require_command() {
local command_name="$1"
command -v "${command_name}" >/dev/null 2>&1 || die "Missing command: ${command_name}"
}
Best Practices
Always Quote Variables
# ✅ GOOD
echo "${var}"
[[ -n "${var:-}" ]] && echo "set"
printf "%s\n" "${array[@]}"
# ❌ BAD
echo $var
[ -n $var ] && echo "set"
Process Text and JSON Deliberately
# Simple string changes: prefer Bash parameter expansion
normalized_path="${input_path//\/\//\/}"
# Line/column text processing: awk/sed are appropriate when necessary
awk -F',' 'NR > 1 && $3 == "active" { print $1 }' users.csv
sed -E 's/[[:space:]]+$//' input.txt > output.txt
# JSON: use jq; never parse JSON with grep/sed/string splitting
jq -e '.users[] | select(.active == true) | .id' users.json
Terminal-safe ANSI Colors
Define color codes only when writing to an interactive terminal. Do not emit ANSI escapes into log files, CI summaries, or non-terminal output.
if [[ -t 2 ]]; then
readonly RED=$'\033[0;31m'
readonly GREEN=$'\033[0;32m'
readonly YELLOW=$'\033[1;33m'
readonly NC=$'\033[0m'
else
readonly RED=''
readonly GREEN=''
readonly YELLOW=''
readonly NC=''
fi
if [[ -n "${GREEN}" ]]; then
printf "%sStarting job%s\n" "${GREEN}" "${NC}" >&2
fi
logmsg "Starting job"
if [[ -n "${RED}" ]]; then
printf "%sInvalid input%s\n" "${RED}" "${NC}" >&2
fi
logmsg "Invalid input"
Use Functions
process_file() {
local file="$1"
[[ -f "$file" ]] || return 1
# Process file
}
Know When Bash Is the Wrong Tool
Use Bash for glue: calling CLIs, moving files, simple validation, CI wrappers, and deployment orchestration. Prefer Python/Go/Node when the script needs complex data structures, non-trivial JSON transformation, API clients with pagination/retry state, concurrency, long-lived daemons, or more than a few hundred lines of business logic.
Use main() When the Script Has Phases
Tiny one-shot scripts can stay linear, but once a script has multiple named phases, wrap execution in main(). This separates globals/functions from execution, makes the script read like a table of contents, and makes future testing/refactoring easier.
main() {
parse_args "$@"
require_command curl
require_command git
require_command npm
resolve_env_file
install_dependencies_if_needed
build_assets
load_deploy_environment
deploy_worker_assets "$@"
verify_worker_hostnames
}
main "$@"
Check Command Existence
command -v docker >/dev/null 2>&1 || die "docker is required"
Temporary Files
readonly TEMP_FILE="$(mktemp)"
trap 'rm -f "$TEMP_FILE"' EXIT
echo "data" > "$TEMP_FILE"
Lock Files
exec 200>"/tmp/${SCRIPT_NAME}.lock"
flock -n 200 || { echo "Already running"; exit 1; }
Performance Patterns
Avoid Subshells in Loops
# ❌ BAD - subshell, variables don't persist
count=0
cat file.txt | while read -r line; do
count=$((count + 1))
done
echo "$count" # Empty! (subshell issue)
# ✅ GOOD - process substitution
count=0
while read -r line; do
count=$((count + 1))
done
Options:
-h, --help Show this help
-v, --verbose Enable verbose mode
-o, --output Output file (default: stdout)
EOF
}
parse_args() {
VERBOSE=false
OUTPUT=""
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help) show_help; exit 0 ;;
-v|--verbose) VERBOSE=true; shift ;;
-o|--output) OUTPUT="$2"; shift 2 ;;
-*) die "Unknown option: $1" ;;
*) break ;;
esac
done
[[ $# -eq 0 ]] && die "Missing required argument"
INPUT_FILE="$1"
}
Detailed References
- Shell Utilities: See [references/shell-utilities.md](references/shell-utilities.md) for curl, jq, lynx
- Makefile Patterns: See [references/makefile-patterns.md](references/makefile-patterns.md)
- CLI Design: See [references/cli-design.md](references/cli-design.md)
- Justfile Patterns: See [references/justfile.md](references/justfile.md)
- Repo Sync (rsync): See [references/rsync-repo-sync.md](references/rsync-repo-sync.md)
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: d-padmanabhan
- Source: d-padmanabhan/agent-engineering-handbook
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.