Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Write robust, portable shell scripts with proper error handling, argument parsing, and testing. Use when automating system tasks, building CI/CD scripts, or creating container entrypoints.
.claude/skills/ancoleman-shell-scripting/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 72% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 330% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 166% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 224% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 162% | 0% |
Provides patterns and best practices for writing maintainable shell scripts with error handling, argument parsing, and portability considerations. Covers POSIX sh vs Bash decision-making, parameter expansion, integration with common utilities (jq, yq, awk), and testing with ShellCheck and Bats.
Use shell scripting when:
Consider Python/Go instead when:
Use POSIX sh (#!/bin/sh) when:
Use Bash (#!/bin/bash) when:
<(cmd) usefulFor detailed comparison and testing strategies, see references/portability-guide.md.
bash#!/bin/bash set -euo pipefail # -e: Exit on error # -u: Exit on undefined variable # -o pipefail: Pipeline fails if any command fails
Use for production automation, CI/CD scripts, and critical operations.
bash#!/bin/bash if ! command_that_might_fail; then echo "Error: Command failed" >&2 exit 1 fi
Use for custom error messages and interactive scripts.
bash#!/bin/bash set -euo pipefail TEMP_FILE=$(mktemp) cleanup() { rm -f "$TEMP_FILE" } trap cleanup EXIT
Use for guaranteed cleanup of temporary files, locks, and resources.
For comprehensive error patterns, see references/error-handling.md.
bash#!/bin/bash while getopts "hvf:o:" opt; do case "$opt" in h) usage ;; v) VERBOSE=true ;; f) INPUT_FILE="$OPTARG" ;; o) OUTPUT_FILE="$OPTARG" ;; *) usage ;; esac done shift $((OPTIND - 1))
bash#!/bin/bash while [[ $# -gt 0 ]]; do case "$1" in --help) usage ;; --verbose) VERBOSE=true; shift ;; --file) INPUT_FILE="$2"; shift 2 ;; --file=*) INPUT_FILE="${1#*=}"; shift ;; *) break ;; esac done
For hybrid approaches and validation patterns, see references/argument-parsing.md.
bash# Default values ${var:-default} # Use default if unset ${var:=default} # Assign default if unset : "${API_KEY:?Error: required}" # Error if unset # String manipulation ${#var} # String length ${var:offset:length} # Substring ${var%.txt} # Remove suffix ${var##*/} # Basename ${var/old/new} # Replace first ${var//old/new} # Replace all # Case conversion (Bash 4+) ${var^^} # Uppercase ${var,,} # Lowercase
For complete expansion patterns and array handling, see references/parameter-expansion.md.
bash# Extract field name=$(curl -sSL https://api.example.com/user | jq -r '.name') # Filter array active=$(jq '.users[] | select(.active) | .name' data.json) # Check existence if ! echo "$json" | jq -e '.field' >/dev/null; then echo "Error: Field missing" >&2 fi
bash# Read value (yq v4) host=$(yq eval '.database.host' config.yaml) # Update in-place yq eval '.port = 5432' -i config.yaml # Convert to JSON yq eval -o=json config.yaml
bash# awk: Extract columns awk -F',' '{print $1, $3}' data.csv # sed: Replace text sed 's/old/new/g' file.txt # grep: Pattern match grep -E "ERROR|WARN" logfile.txt
For detailed examples and best practices, see references/common-utilities.md.
bash# Check script shellcheck script.sh # POSIX compliance shellcheck --shell=sh script.sh # Exclude warnings shellcheck --exclude=SC2086 script.sh
bash#!/usr/bin/env bats @test "script runs successfully" { run ./script.sh --help [ "$status" -eq 0 ] [ "${lines[0]}" = "Usage: script.sh [OPTIONS]" ] } @test "handles missing argument" { run ./script.sh [ "$status" -eq 1 ] [[ "$output" =~ "Error" ]] }
Run tests:
bashbats test/
For CI/CD integration and debugging techniques, see references/testing-guide.md.
bash#!/bin/bash set -euo pipefail # Check required commands command -v jq >/dev/null 2>&1 || { echo "Error: jq required" >&2 exit 1 } # Check environment variables : "${API_KEY:?Error: API_KEY required}" # Check files [ -f "$CONFIG_FILE" ] || { echo "Error: Config not found: $CONFIG_FILE" >&2 exit 1 } # Quote all variables echo "Processing: $file" # ❌ Unquoted echo "Processing: \"$file\"" # ✅ Quoted
bash# sed in-place sed -i '' 's/old/new/g' file.txt # macOS sed -i 's/old/new/g' file.txt # Linux # Portable: Use temp file sed 's/old/new/g' file.txt > file.txt.tmp mv file.txt.tmp file.txt # readlink readlink -f /path # Linux only cd "$(dirname "$0")" && pwd # Portable
For complete platform differences, see references/portability-guide.md.
System Administration: Cron jobs, log rotation, backup automation Build/Deployment: CI/CD pipelines, Docker builds, deployments Development Tooling: Project setup, test runners, code generators Container Entrypoints: Initialization, signal handling, configuration
bash#!/bin/bash set -euo pipefail readonly SCRIPT_NAME="$(basename "$0")" readonly SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" TEMP_DIR="" cleanup() { local exit_code=$? rm -rf "$TEMP_DIR" exit "$exit_code" } trap cleanup EXIT log() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" >&2 } main() { # Check dependencies command -v jq >/dev/null 2>&1 || exit 1 # Parse arguments # Validate input # Process # Report results log "Completed successfully" } main "$@"
For complete production template, see examples/production-template.sh.
Core Tools:
Installation:
bash# macOS brew install jq yq shellcheck bats-core # Ubuntu/Debian apt-get install jq shellcheck
Reference Files:
references/error-handling.md - Comprehensive error patternsreferences/argument-parsing.md - Advanced parsing techniquesreferences/parameter-expansion.md - Complete expansion referencereferences/portability-guide.md - POSIX vs Bash differencesreferences/testing-guide.md - ShellCheck and Bats guidereferences/common-utilities.md - jq, yq, awk, sed usageExample Scripts:
examples/production-template.sh - Production-ready templateexamples/getopts-basic.sh - Simple getopts usageexamples/getopts-advanced.sh - Complex option handlingexamples/long-options.sh - Manual long option parsingexamples/error-handling.sh - Error handling patternsexamples/json-yaml-processing.sh - jq/yq examplesUtility Scripts:
scripts/lint-script.sh - ShellCheck wrapper for CIscripts/test-script.sh - Bats wrapper for CI| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 22,295 | 29,373 | +32% | 1 | 1 | 0% | 4,892 | 8,409 | +72% | 0 | 0 | — |
case-02 | pass→pass | 5,962 | 6,205 | +4% | 1 | 1 | 0% | 977 | 3,165 | +224% | 0 | 0 | — |
case-03 | pass→pass | 8,164 | 6,288 | -23% | 1 | 1 | 0% | 1,306 | 3,428 | +162% | 0 | 0 | — |
case-04 | pass→pass | 3,954 | 3,671 | -7% | 1 | 1 | 0% | 634 | 2,911 | +359% | 0 | 0 | — |
case-05 | pass→pass | 10,283 | 8,133 | -21% | 1 | 1 | 0% | 1,967 | 3,799 | +93% | 0 | 0 | — |
case-06 | pass→pass | 11,781 | 10,989 | -7% | 1 | 1 | 0% | 2,210 | 4,411 | +100% | 0 | 0 | — |
case-07 | pass→pass | 8,050 | 6,992 | -13% | 1 | 1 | 0% | 1,245 | 3,603 | +189% | 0 | 0 | — |
case-08 | pass→pass | 5,831 | 4,333 | -26% | 1 | 1 | 0% | 1,111 | 3,074 | +177% | 0 | 0 | — |
case-09 | pass→pass | 6,596 | 5,995 | -9% | 1 | 1 | 0% | 1,139 | 3,382 | +197% | 0 | 0 | — |
case-10 | fail→pass | 3,736 | 2,892 | -23% | 1 | 1 | 0% | 634 | 2,726 | +330% | 0 | 0 | — |
case-11 | pass→pass | 11,264 | 9,811 | -13% | 1 | 1 | 0% | 1,923 | 3,879 | +102% | 0 | 0 | — |
case-12 | pass→pass | 5,187 | 4,264 | -18% | 1 | 1 | 0% | 846 | 3,090 | +265% | 0 | 0 | — |
case-18 | pass→pass | 8,442 | 7,249 | -14% | 1 | 1 | 0% | 1,404 | 3,516 | +150% | 0 | 0 | — |
case-13 | fail→pass | 8,178 | 2,640 | -68% | 1 | 1 | 0% | 1,045 | 2,777 | +166% | 0 | 0 | — |
case-14 | pass→pass | 3,623 | 2,856 | -21% | 1 | 1 | 0% | 703 | 2,851 | +306% | 0 | 0 | — |
case-15 | pass→pass | 14,781 | 16,792 | +14% | 1 | 1 | 0% | 2,646 | 5,081 | +92% | 0 | 0 | — |
case-16 | pass→pass | 3,508 | 4,064 | +16% | 1 | 1 | 0% | 603 | 2,975 | +393% | 0 | 0 | — |
case-17 | pass→pass | 2,943 | 3,270 | +11% | 1 | 1 | 0% | 549 | 2,862 | +421% | 0 | 0 | — |
case-19 | pass→pass | 5,354 | 4,619 | -14% | 1 | 1 | 0% | 962 | 3,174 | +230% | 0 | 0 | — |
case-20 | pass→pass | 4,409 | 3,129 | -29% | 1 | 1 | 0% | 639 | 2,759 | +332% | 0 | 0 | — |
case-21 | pass→pass | 16,237 | 17,537 | +8% | 1 | 1 | 0% | 2,783 | 5,289 | +90% | 0 | 0 | — |
case-22 | pass→pass | 15,027 | 14,012 | -7% | 1 | 1 | 0% | 2,455 | 4,371 | +78% | 0 | 0 | — |
case-23 | pass→pass | 13,948 | 12,623 | -9% | 1 | 1 | 0% | 2,259 | 4,478 | +98% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +13 percentage points is the difference between those two pass rates over the 23 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.