home / skills / ancoleman / ai-design-components / shell-scripting

shell-scripting skill

/skills/shell-scripting

This skill helps you write robust portable shell scripts with error handling, argument parsing, and testing, improving automation reliability.

npx playbooks add skill ancoleman/ai-design-components --skill shell-scripting

Review the files below or copy the command above to add this skill to your agents.

Files (16)
SKILL.md
8.5 KB
---
name: shell-scripting
description: 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.
---

# Shell Scripting

## Purpose

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.

## When to Use This Skill

Use shell scripting when:
- Orchestrating existing command-line tools and system utilities
- Writing CI/CD pipeline scripts (GitHub Actions, GitLab CI)
- Creating container entrypoints and initialization scripts
- Automating system administration tasks (backups, log rotation)
- Building development tooling (build scripts, test runners)

Consider Python/Go instead when:
- Complex business logic or data structures required
- Cross-platform GUI needed
- Heavy API integration (REST, gRPC)
- Script exceeds 200 lines with significant logic complexity

## POSIX sh vs Bash

**Use POSIX sh (#!/bin/sh) when:**
- Maximum portability required (Linux, macOS, BSD, Alpine)
- Minimal container images needed
- Embedded systems or unknown target environments

**Use Bash (#!/bin/bash) when:**
- Controlled environment (specific OS, container)
- Arrays or associative arrays needed
- Advanced parameter expansion beneficial
- Process substitution `<(cmd)` useful

For detailed comparison and testing strategies, see `references/portability-guide.md`.

## Essential Error Handling

### Fail-Fast Pattern

```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.

### Explicit Exit Code Checking

```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.

### Trap Handlers for Cleanup

```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`.

## Argument Parsing

### Short Options with getopts (POSIX)

```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))
```

### Long Options (Manual Parsing)

```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`.

## Parameter Expansion Quick Reference

```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`.

## Common Utilities Integration

### JSON with jq

```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
```

### YAML with yq

```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
```

### Text Processing

```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`.

## Testing and Validation

### ShellCheck: Static Analysis

```bash
# Check script
shellcheck script.sh

# POSIX compliance
shellcheck --shell=sh script.sh

# Exclude warnings
shellcheck --exclude=SC2086 script.sh
```

### Bats: Automated Testing

```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:
```bash
bats test/
```

For CI/CD integration and debugging techniques, see `references/testing-guide.md`.

## Defensive Programming Checklist

```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
```

## Platform Considerations

### macOS vs Linux Differences

```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`.

## Script Categories

**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

## Production Script Template

```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`.

## Tool Recommendations

**Core Tools:**
- **jq**: JSON parsing and transformation
- **yq**: YAML parsing (v4 recommended)
- **ShellCheck**: Static analysis and linting
- **Bats**: Automated testing framework

**Installation:**
```bash
# macOS
brew install jq yq shellcheck bats-core

# Ubuntu/Debian
apt-get install jq shellcheck
```

## Related Skills

- **linux-administration**: System commands and administration
- **building-ci-pipelines**: Using scripts in CI/CD
- **infrastructure-as-code**: Terraform/Pulumi wrappers
- **kubernetes-operations**: kubectl scripts, Helm hooks
- **writing-dockerfiles**: Container entrypoints

## Additional Resources

**Reference Files:**
- `references/error-handling.md` - Comprehensive error patterns
- `references/argument-parsing.md` - Advanced parsing techniques
- `references/parameter-expansion.md` - Complete expansion reference
- `references/portability-guide.md` - POSIX vs Bash differences
- `references/testing-guide.md` - ShellCheck and Bats guide
- `references/common-utilities.md` - jq, yq, awk, sed usage

**Example Scripts:**
- `examples/production-template.sh` - Production-ready template
- `examples/getopts-basic.sh` - Simple getopts usage
- `examples/getopts-advanced.sh` - Complex option handling
- `examples/long-options.sh` - Manual long option parsing
- `examples/error-handling.sh` - Error handling patterns
- `examples/json-yaml-processing.sh` - jq/yq examples

**Utility Scripts:**
- `scripts/lint-script.sh` - ShellCheck wrapper for CI
- `scripts/test-script.sh` - Bats wrapper for CI

Overview

This skill helps you write robust, portable shell scripts with strong error handling, clear argument parsing, and repeatable testing. It focuses on practical patterns for POSIX sh vs Bash decisions, integration with common utilities (jq, yq, awk), and producing production-ready entrypoint and CI/CD scripts. Use it to improve reliability, maintainability, and portability of automation scripts.

How this skill works

The skill provides templates and idioms: fail-fast patterns (set -euo pipefail), explicit exit-code checks, trap-based cleanup, and a production script skeleton. It shows how to parse short and long options, use parameter expansion for defaults and validation, and integrate jq/yq/awk/sed for structured data and text processing. It also includes testing and linting guidance with ShellCheck and Bats to validate behavior before deployment.

When to use it

  • Orchestrating command-line tools or system utilities in automation
  • Writing CI/CD pipeline scripts, build steps, or test runners
  • Creating container entrypoints and initialization scripts with signal handling
  • Automating system administration tasks like backups and log rotation
  • Building lightweight dev tooling where a shell script is simpler than a compiled program

Best practices

  • Prefer POSIX sh for maximum portability; use Bash when arrays or advanced features are required
  • Use set -euo pipefail and trap cleanup to enforce fail-fast and resource cleanup
  • Validate inputs explicitly and provide clear error messages on stderr with nonzero exit codes
  • Quote all variable expansions and check required commands with command -v
  • Run ShellCheck for linting and use Bats for automated behavior tests in CI

Example use cases

  • A container ENTRYPOINT that configures environment, validates secrets, and handles SIGTERM gracefully
  • A GitHub Actions step script that parses options, downloads artifacts with curl, and processes JSON via jq
  • A CI lint-and-test runner that invokes ShellCheck and Bats, reporting failures as pipeline errors
  • A nightly backup script that creates temp files, uses trap to clean them, and uploads results
  • A cross-platform deploy helper that uses portable patterns for macOS and Linux differences

FAQ

When should I choose sh over bash?

Use sh when maximum portability and minimal base images are required. Choose bash in controlled environments needing arrays, associative arrays, or Bash-specific expansions.

How do I test scripts safely in CI?

Use ShellCheck for static analysis and Bats for unit-style tests. Run both in CI, and include wrappers that fail the job on lint or test failures.