bash-shell-scripting

SKILL.md

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
Extension .sh for scripts

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
set -euo pipefail

readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "$0")"

cleanup() {
    rm -f "${TEMP_FILE:-}" 2>/dev/null || true
}
trap cleanup EXIT

log() {
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" >&2
}

die() {
    log "ERROR: $*"
    exit 1
}

main() {
    local arg="${1:-}"
    [[ -z "$arg" ]] && die "Usage: $SCRIPT_NAME <argument>"
    
    log "Processing: $arg"
    # Main logic here
}

main "$@"

Best Practices

Always Quote Variables

# ✅ GOOD
echo "${var}"
[[ -n "${var:-}" ]] && echo "set"

# ❌ BAD
echo $var
[ -n $var ] && echo "set"

Use Functions

process_file() {
    local file="$1"
    [[ -f "$file" ]] || return 1
    # Process file
}

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"  # Correct

Use 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

Weekly Installs
2
GitHub Stars
9
First Seen
14 days ago
Installed on
opencode2
gemini-cli2
claude-code2
github-copilot2
codex2
kimi-cli2