Claude Code Plugins

Community-maintained marketplace

Feedback

shell-scripting

@89jobrien/steve
1
0

Shell scripting best practices and patterns. Use when writing bash/zsh

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name shell-scripting
description Shell scripting best practices and patterns. Use when writing bash/zsh scripts, automating tasks, creating CLI tools, or debugging shell commands.
author Joseph OBrien
status unpublished
updated 2025-12-23
version 1.0.1
tag skill
type skill

Shell Scripting

Comprehensive shell scripting skill covering bash/zsh patterns, automation, error handling, and CLI tool development.

When to Use This Skill

  • Writing automation scripts
  • Creating CLI tools
  • System administration tasks
  • Build and deployment scripts
  • Log processing and analysis
  • File manipulation and batch operations
  • Cron jobs and scheduled tasks

Script Structure

Template

#!/usr/bin/env bash
# Script: name.sh
# Description: What this script does
# Usage: ./name.sh [options] <args>

set -euo pipefail  # Exit on error, undefined vars, pipe failures
IFS=$'\n\t'        # Safer word splitting

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

# Default values
VERBOSE=false
DRY_RUN=false

# Functions
usage() {
    cat <<EOF
Usage: $SCRIPT_NAME [options] <argument>

Options:
    -h, --help      Show this help message
    -v, --verbose   Enable verbose output
    -n, --dry-run   Show what would be done
EOF
}

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

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

# Main logic
main() {
    # Parse arguments
    while [[ $# -gt 0 ]]; do
        case "$1" in
            -h|--help)
                usage
                exit 0
                ;;
            -v|--verbose)
                VERBOSE=true
                shift
                ;;
            -n|--dry-run)
                DRY_RUN=true
                shift
                ;;
            *)
                break
                ;;
        esac
    done

    # Your logic here
}

main "$@"

Error Handling

Set Options

set -e          # Exit on any error
set -u          # Error on undefined variables
set -o pipefail # Pipe failure is script failure
set -x          # Debug: print each command (use sparingly)

Trap for Cleanup

cleanup() {
    rm -f "$TEMP_FILE"
    log "Cleanup complete"
}
trap cleanup EXIT

# Also handle specific signals
trap 'error "Script interrupted"' INT TERM

Error Checking Patterns

# Check command exists
command -v jq >/dev/null 2>&1 || error "jq is required but not installed"

# Check file exists
[[ -f "$FILE" ]] || error "File not found: $FILE"

# Check directory exists
[[ -d "$DIR" ]] || mkdir -p "$DIR"

# Check variable is set
[[ -n "${VAR:-}" ]] || error "VAR is not set"

# Check exit status explicitly
if ! some_command; then
    error "some_command failed"
fi

Variables & Substitution

Variable Expansion

# Default values
${VAR:-default}     # Use default if VAR is unset or empty
${VAR:=default}     # Set VAR to default if unset or empty
${VAR:+value}       # Use value if VAR is set
${VAR:?error msg}   # Error if VAR is unset or empty

# String manipulation
${VAR#pattern}      # Remove shortest prefix match
${VAR##pattern}     # Remove longest prefix match
${VAR%pattern}      # Remove shortest suffix match
${VAR%%pattern}     # Remove longest suffix match
${VAR/old/new}      # Replace first occurrence
${VAR//old/new}     # Replace all occurrences
${#VAR}             # Length of VAR

Arrays

# Declare array
declare -a ARRAY=("one" "two" "three")

# Access elements
echo "${ARRAY[0]}"     # First element
echo "${ARRAY[@]}"     # All elements
echo "${#ARRAY[@]}"    # Number of elements
echo "${!ARRAY[@]}"    # All indices

# Iterate
for item in "${ARRAY[@]}"; do
    echo "$item"
done

# Append
ARRAY+=("four")

Associative Arrays

declare -A MAP
MAP["key1"]="value1"
MAP["key2"]="value2"

# Access
echo "${MAP[key1]}"

# Check key exists
[[ -v MAP[key1] ]] && echo "key1 exists"

# Iterate
for key in "${!MAP[@]}"; do
    echo "$key: ${MAP[$key]}"
done

Control Flow

Conditionals

# String comparison
[[ "$str" == "value" ]]
[[ "$str" != "value" ]]
[[ -z "$str" ]]  # Empty
[[ -n "$str" ]]  # Not empty

# Numeric comparison
[[ "$num" -eq 5 ]]  # Equal
[[ "$num" -ne 5 ]]  # Not equal
[[ "$num" -lt 5 ]]  # Less than
[[ "$num" -gt 5 ]]  # Greater than

# File tests
[[ -f "$file" ]]  # File exists
[[ -d "$dir" ]]   # Directory exists
[[ -r "$file" ]]  # Readable
[[ -w "$file" ]]  # Writable
[[ -x "$file" ]]  # Executable

# Logical operators
[[ "$a" && "$b" ]]  # AND
[[ "$a" || "$b" ]]  # OR
[[ ! "$a" ]]        # NOT

Loops

# For loop
for i in {1..10}; do
    echo "$i"
done

# While loop
while read -r line; do
    echo "$line"
done < "$file"

# Process substitution
while read -r line; do
    echo "$line"
done < <(command)

# C-style for
for ((i=0; i<10; i++)); do
    echo "$i"
done

Input/Output

Reading Input

# Read from user
read -r -p "Enter name: " name

# Read password (hidden)
read -r -s -p "Password: " password

# Read with timeout
read -r -t 5 -p "Quick! " answer

# Read file line by line
while IFS= read -r line; do
    echo "$line"
done < "$file"

Output & Redirection

# Redirect stdout
command > file      # Overwrite
command >> file     # Append

# Redirect stderr
command 2> file

# Redirect both
command &> file
command > file 2>&1

# Discard output
command > /dev/null 2>&1

# Tee (output and save)
command | tee file

Text Processing

Common Patterns

# Find and process files
find . -name "*.log" -exec grep "ERROR" {} +

# Process CSV
while IFS=, read -r col1 col2 col3; do
    echo "$col1: $col2"
done < file.csv

# JSON processing (with jq)
jq '.key' file.json
jq -r '.items[]' file.json

# AWK one-liners
awk '{print $1}' file           # First column
awk -F: '{print $1}' /etc/passwd  # Custom delimiter
awk 'NR > 1' file               # Skip header

# SED one-liners
sed 's/old/new/g' file          # Replace all
sed -i 's/old/new/g' file       # In-place edit
sed -n '10,20p' file            # Print lines 10-20

Best Practices

Do

  • Quote all variable expansions: "$VAR"
  • Use [[ ]] over [ ] for tests
  • Use $(command) over backticks
  • Check return values
  • Use readonly for constants
  • Use local in functions
  • Provide --help option
  • Use meaningful exit codes

Don't

  • Parse ls output
  • Use eval with untrusted input
  • Assume paths don't have spaces
  • Ignore shellcheck warnings
  • Write one giant script (modularize)

Reference Files

  • references/one_liners.md - Useful one-liner commands

Integration with Other Skills

  • developer-experience - For tooling automation
  • debugging - For script debugging
  • testing - For script testing patterns