bash-shell-scripting
Skill17 starsChanged 55 days agoMITView on GitHub
Bash scripting best practices for production-grade scripts, CLI tools, and Makefiles. Covers strict mode, error handling, portability, performance patterns, and argument parsing. Use when working with .sh files, Makefiles, shell scripts, or when asking about Bash, shell scripting, CLI design, or command-line tools.
In this repository53 other files it gives its agents.
- agents-sdkSkill
- agent-workflowSkill
- api-designSkill
- aws-iamSkill
- cicd-github-actionsSkill
- cloudflare-email-serviceSkill
- cloudflareSkill
- cloudflare-waf-authorSkill
- cloudflare-workers-authorSkill
- cloud-platformsSkill
- codebase-security-auditSkill
- containers-orchestrationSkill
- core-engineeringSkill
- database-postgresqlSkill
- databricksSkill
- data-engineeringSkill
- distributed-transactionsSkill
- documentation-standardsSkill
- domain-driven-designSkill
- durable-objectsSkill
- frontend-engineeringSkill
- git-workflowSkill
- go-rust-systemsSkill
- iam-security-advisorSkill
- independent-verificationSkill
- infrastructure-iacSkill
- kubernetes-containersSkill
- kubernetes-operator-developmentSkill
- mcp-developmentSkill
- memory-architectureSkill
- multi-perspective-reviewSkill
- networking-transportSkill
- observabilitySkill
- oktaSkill
- pdf-exportSkill
- python-developmentSkill
- reactflow-architecture-diagramsSkill
- sandbox-sdkSkill
- scripting-automationSkill
- security-testingSkill
- service-resilienceSkill
- single-file-dashboardSkill
- skills-compositionSkill
- skills-continuous-improvementSkill
- snowflakeSkill
- system-designSkill
- typescript-javascriptSkill
- web-perfSkill
- web-research-kb-refreshSkill
- workers-best-practicesSkill
- workload-identitySkill
- wranglerSkill
- zero-trustSkill
The file
We split this skill into the rules it gives an agent
6 rules. About 0 are common, 6 are distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈6
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 12
DRY: Don't Repeat Yourself
- Distinctive: line 13
KISS: Keep It Simple
- Distinctive: line 15
Zero Warnings: Must pass shellcheck
- Distinctive: line 66
Use for: Simple linear scripts, dependency installation, straightforward validation.
- Distinctive: line 74
Use for: Diagnostics, cleanup operations, commands where failure is expected.
- Distinctive: line 84
Use for: Production scripts with comprehensive error handling.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Bash & Shell Scripting
Mandatory gates are owned by the Bash rule (${HANDBOOK_ROOT}/rules/140-bash.mdc). This skill owns procedures and examples.
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 variableCore 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 errorUse for: Simple linear scripts, dependency installation, straightforward validation.
Controlled Mode (Explicit)
set -uo pipefail # No -e: handle errors explicitlyUse 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"' ERRUse for: Production scripts with comprehensive error handling.
Script Template
#!/usr/bin/env bash
#
# Script Name : <script_name>.sh
#
# Purpose : <One or two sentences explaining what the script does.
# Wrap continuation lines under the value column.>
#
# Dependencies : <List required commands, or "None">
#
# Script Usage : ./<script_name>.sh [options] <arguments>
#
# <Examples and argument notes.>
#
##----------------------------------------------------------------------------------------##
# 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} <argument>"
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.jsonTerminal-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 < file.txt
echo "$count" # CorrectUse Arrays
# Arrays for multiple values
files=("file1.txt" "file2.txt" "file3.txt")
for file in "${files[@]}"; do
process "$file"
done
# Append to array
files+=("file4.txt")Argument Parsing
show_help() {
cat <<EOF
Usage: $SCRIPT_NAME [OPTIONS] <file>
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 shell utilities (
${HANDBOOK_ROOT}/skills/bash-shell-scripting/references/shell-utilities.md) for curl, jq, lynx - Makefile Patterns: See Makefile patterns (
${HANDBOOK_ROOT}/skills/bash-shell-scripting/references/makefile-patterns.md) - CLI Design: See CLI design (
${HANDBOOK_ROOT}/skills/bash-shell-scripting/references/cli-design.md) - Justfile Patterns: See Justfile patterns (
${HANDBOOK_ROOT}/skills/bash-shell-scripting/references/justfile.md) - Repo Sync (rsync): See rsync repository synchronization (
${HANDBOOK_ROOT}/skills/bash-shell-scripting/references/rsync-repo-sync.md)
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

