name: claude-skill-bash description: Apply comprehensive bash scripting standards including main function pattern, usage documentation, argument parsing, dependency checking, and error handling. Triggers when creating/editing .sh files, bash scripts, or discussing shell scripting, deployment scripts, automation tasks, or bash conventions.
Bash Best Practices Skill
This skill ensures all bash scripts follow enterprise-grade best practices for maintainability, reliability, and user-friendliness.
When This Skill Applies
This skill should be automatically invoked when:
- Creating new bash scripts or shell scripts
- Editing existing .sh or .bash files
- User requests scripts for automation, deployment, backup, or system tasks
- Reviewing or refactoring bash code
- User mentions: bash, shell script, main function, argument parsing, script structure
Script Complexity Guidelines
This skill recognizes two categories of scripts with different structural requirements:
Simple Scripts
Scripts that do one simple thing well without unnecessary ceremony.
Characteristics:
- Less than ~30 lines of actual logic (excluding comments and whitespace)
- No command-line arguments required
- Single, focused purpose that's self-evident
- Linear flow or minimal branching
- Output is often consumed by other programs or scripts
Examples: Version detection, environment checks, data format conversion, simple calculations
Structural Requirements:
- Main function and guard clause: Optional
- Usage function: Not needed (no arguments to document)
- Argument parsing: Not applicable
- Color output: Optional (often unnecessary)
- Can use direct execution without function wrapping
Ordinary Scripts
Scripts with broader scope requiring full structure for maintainability.
Characteristics:
- More than ~30 lines of logic OR
- Takes command-line arguments OR
- Multiple functions or complex branching OR
- Interactive use by humans OR
- Requires detailed documentation
Examples: Deployment tools, backup utilities, system maintenance scripts, development tools
Structural Requirements:
- Main function and guard clause: Required
- Usage function: Required
- Argument parsing: Required (if taking arguments)
- Full error handling: Required
- User-friendly output: Required (for interactive scripts)
Core Principles to Apply
For Ordinary Scripts
When writing ordinary scripts, ensure:
- Main function pattern with guard clause - Every script must have a main() function
- Usage function - Clear help documentation
- Argument parsing - Structured option handling in main
- Dependency checking - Explicit validation of required tools
- Explicit error handling - Never use
set -e, handle errors explicitly - Function organization - Correct ordering and single responsibility
- User-friendly output - Colored, structured output with status indicators
For Simple Scripts
When writing simple scripts, ensure:
- Clear purpose comment - Explain what the script does at the top
- Explicit error handling - Never use
set -e, handle errors explicitly - Proper exit codes - 0 for success, non-zero for failure
- Error output to stderr - Use
>&2for error messages - Variable safety - Quote variables, handle undefined cases
Script Structure Templates
Template for Ordinary Scripts
Every new bash script should follow this structure:
#!/usr/bin/env bash
# Global declarations
DEPENDENCIES=(jq curl git) # List external tools needed
SCRIPT_NAME=$(basename "$0")
VERSION="1.0.0"
# Color definitions (if outputting to terminal)
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Allow users to disable colors
if [[ -n "${NO_COLOR:-}" ]] || [[ "${TERM:-}" == "dumb" ]]; then
RED=""
GREEN=""
YELLOW=""
BLUE=""
NC=""
fi
function usage() {
cat <<EOM
Brief description of what this script does.
usage: ${SCRIPT_NAME} [options]
options:
-i|--input <file> Input file to process (required)
-o|--output <file> Output file (optional, defaults to stdout)
-v|--verbose Enable verbose output
-h|--help Show this help message
--version Show version information
dependencies: ${DEPENDENCIES[@]}
examples:
${SCRIPT_NAME} -i data.txt -o report.json
${SCRIPT_NAME} --input data.txt --verbose
EOM
exit 1
}
function main() {
# Default values
local input_file=""
local output_file=""
local verbose=false
# Parse arguments
while [ "$1" != "" ]; do
case $1 in
-i | --input)
shift
input_file="$1"
;;
-o | --output)
shift
output_file="$1"
;;
-v | --verbose)
verbose=true
;;
--version)
echo "${SCRIPT_NAME} version ${VERSION}"
exit 0
;;
-h | --help)
usage
;;
*)
echo "Error: Unknown option '$1'"
usage
;;
esac
shift
done
# Validate required arguments
if [ -z "$input_file" ]; then
echo "Error: Input file is required" >&2
usage
fi
# Check dependencies
exit_on_missing_tools "${DEPENDENCIES[@]}"
# Main script logic
process_file "$input_file" "$output_file" "$verbose"
}
# Business logic functions
function process_file() {
local input="$1"
local output="$2"
local verbose="$3"
# Implementation here
echo "Processing ${input}..."
}
# Utility functions
function exit_on_missing_tools() {
for cmd in "$@"; do
if command -v "$cmd" &>/dev/null; then
continue
fi
printf "Error: Required tool '%s' is not installed or not in PATH\n" "$cmd"
exit 1
done
}
# Guard clause - only execute main if script is run directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
exit 0
fi
Template for Simple Scripts
For scripts that do one simple thing without arguments:
#!/usr/bin/env bash
# Purpose: Determine version for CI builds based on git state
# Usage: determine-version.sh
# Output: Version string to stdout
# For simple scripts, direct execution is fine - no main() required
# Check if we're on a tag
TAG=$(git describe --exact-match --tags HEAD 2>/dev/null)
if [ -n "$TAG" ]; then
# On a tag - use the tag as version
echo "${TAG#v}"
else
# Not on tag - determine from branch
if [ -n "$GITHUB_HEAD_REF" ]; then
# Pull request
BRANCH="$GITHUB_HEAD_REF"
elif [ -n "$GITHUB_REF_NAME" ]; then
# Push to branch
BRANCH="$GITHUB_REF_NAME"
else
# Fallback to current branch
BRANCH=$(git symbolic-ref -q --short HEAD 2>/dev/null)
if [ -z "$BRANCH" ]; then
echo "Error: Unable to determine branch" >&2
exit 1
fi
fi
# Sanitize branch name for version string
SANITIZED=$(echo "$BRANCH" | sed 's/[^a-zA-Z0-9._-]/_/g')
echo "${SANITIZED}-SNAPSHOT"
fi
Key points for simple scripts:
- Clear purpose comment at the top
- Direct execution without main() wrapper
- Errors still go to stderr with >&2
- Meaningful exit codes (0 for success, non-zero for failure)
- Variables are still properly quoted
- No need for usage() function when there are no arguments
- No need for color output when output is data
Detailed Best Practices
1. Script Bootstrapping
Main Function Pattern
- ALWAYS use a
mainfunction as the entry point - This enables safe sourcing by other scripts
- Provides clear structure and testability
The Guard Clause
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
exit 0
fi
- Must be at the very end of the script
- Enables the script to be sourced without execution
- Always include explicit
exit 0after main
2. Usage Function and Argument Parsing
Usage Function Requirements
- Must be defined before main() function
- Use heredoc for clean formatting
- Include: description, options, dependencies, examples
- Exit with status 1
Argument Parsing Best Practices
function main() {
# Set defaults first
local input_file=""
local verbose=false
# Parse with while loop and case
while [ "$1" != "" ]; do
case $1 in
-i | --input)
shift
input_file="$1"
;;
-v | --verbose)
verbose=true
;;
-h | --help)
usage
;;
*)
echo "Error: Unknown option '$1'"
usage
;;
esac
shift
done
# Validate after parsing
if [ -z "$input_file" ]; then
echo "Error: Input file is required" >&2
usage
fi
}
3. Dependency Declaration and Checking
Always Declare Dependencies
DEPENDENCIES=(jq curl git docker)
Check Before Use
function exit_on_missing_tools() {
for cmd in "$@"; do
if command -v "$cmd" &>/dev/null; then
continue
fi
printf "Error: Required tool '%s' is not installed or not in PATH\n" "$cmd"
exit 1
done
}
Include in Usage
Show dependencies in the usage function so users know requirements upfront.
4. Function Organization
Correct Function Order (MANDATORY)
usage()- First after global declarationsmain()- Immediately after usage- Business logic functions - Core functionality
- Utility functions - Generic helpers
NEVER add section comment headers like "# UTILITY FUNCTIONS". The code structure speaks for itself.
Function Design Principles
- Single Responsibility: Each function does one thing
- Clear Inputs/Outputs: Use local variables, return meaningful status
- Descriptive Names: Use verb-noun combinations (create_backup, validate_input)
- Keep Small: Break down functions longer than 50 lines
- Use Local Variables: Always declare with
local
5. Error Handling
AVOID set -e
Never use set -e. Instead, use explicit error handling:
# Good - Explicit handling
if ! command; then
echo "Error: Command failed" >&2
exit 1
fi
# Good - Chain with ||
cd /some/directory || {
echo "Error: Cannot change directory" >&2
exit 1
}
# Good - Capture and check
output=$(command 2>&1)
if [ $? -ne 0 ]; then
echo "Error: Command failed: $output" >&2
exit 1
fi
Error Handling Rules
- Always redirect errors to stderr with
>&2 - Provide context about what failed
- Use meaningful exit codes
- Clean up temporary files on failure
6. User-Friendly Output
Color Output (When Appropriate)
# Define colors at script top
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Respect NO_COLOR environment
if [[ -n "${NO_COLOR:-}" ]] || [[ "${TERM:-}" == "dumb" ]]; then
RED="" GREEN="" YELLOW="" BLUE="" NC=""
fi
Output Functions
function print_header() {
echo -e "${BLUE}=======================${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}=======================${NC}"
}
function print_success() {
echo -e "${GREEN}✅ $1${NC}"
}
function print_error() {
echo -e "${RED}❌ Error: $1${NC}" >&2
}
function print_warning() {
echo -e "${YELLOW}⚠️ Warning: $1${NC}"
}
function print_step() {
local current="$1"
local total="$2"
local message="$3"
echo -e "${YELLOW}[${current}/${total}] ${message}${NC}"
}
7. Keep Main Function Lean
Main should only:
- Parse arguments
- Validate inputs
- Check dependencies
- Call business functions
- Handle high-level errors
Business logic belongs in separate functions.
8. Code Comments
Avoid Excessive Comments
- Well-named functions are self-documenting
- Only comment the "why", not the "what"
- Use comments for:
- Complex algorithms
- Non-obvious workarounds
- Business logic rules
- External API quirks
# Bad - Obvious comment
# Increment counter
counter=$((counter + 1))
# Good - Explains why
# Exponential backoff with jitter to prevent thundering herd
delay=$((2 ** attempt * 1000 + RANDOM % 1000))
Script Generation Checklist
When generating a new script, verify:
- Shebang line:
#!/usr/bin/env bash - DEPENDENCIES array declared
- usage() function defined first
- main() function handles argument parsing
- Dependency checking with exit_on_missing_tools
- Guard clause at script end
- Explicit error handling (no
set -e) - Functions properly ordered
- Local variables in functions
- Errors redirected to stderr
- Meaningful exit codes
- Color output with NO_COLOR support (if applicable)
Common Patterns
Interactive Prompts
# Yes/no confirmation
read -p "Continue? (y/n): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Operation cancelled"
exit 0
fi
# Menu selection
echo "Select an option:"
echo "1) Full backup"
echo "2) Incremental backup"
read -p "Enter choice (1-2): " choice
# Password (hidden)
read -s -p "Enter password: " password
echo # Add newline
Temporary Files
# Create temp file safely
temp_file=$(mktemp)
trap "rm -f '$temp_file'" EXIT
# Use temp directory
temp_dir=$(mktemp -d)
trap "rm -rf '$temp_dir'" EXIT
Pipeline Error Handling
# Without set -e, check PIPESTATUS
command1 | command2 | command3
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
echo "Error: command1 failed" >&2
exit 1
fi
Examples to Follow
When users request scripts for common tasks, apply these patterns:
Backup Script
- Include timestamp in backup names
- Verify source exists before backup
- Check available disk space
- Provide progress feedback
- Clean up old backups based on retention
Deployment Script
- Validate environment first
- Show clear step progression
- Roll back on failure
- Log all operations
- Provide summary at end
Data Processing Script
- Validate input format
- Handle large files efficiently
- Provide progress indicators
- Support resume on failure
- Generate detailed reports
Choosing Between Simple and Ordinary Scripts
Decision Tree
Ask these questions to determine which pattern to use:
-
Does the script take command-line arguments?
- YES → Use ordinary script pattern (needs usage() and parsing)
- NO → Continue to question 2
-
Is the logic more than ~30 lines (excluding comments)?
- YES → Use ordinary script pattern (needs structure)
- NO → Continue to question 3
-
Does it have multiple functions or complex branching?
- YES → Use ordinary script pattern (needs organization)
- NO → Continue to question 4
-
Will it be run interactively by humans who need help?
- YES → Use ordinary script pattern (needs user-friendly features)
- NO → Continue to question 5
-
Is it doing one simple, self-evident task?
- YES → Use simple script pattern
- NO → Use ordinary script pattern
When in doubt, use the ordinary pattern. It's better to have structure you don't need than to need structure you don't have.
Examples of Simple Scripts
determine-version.sh- Outputs version string based on git statecheck-env.sh- Verifies environment variables are setformat-json.sh- Reformats JSON from stdin to stdoutcount-lines.sh- Counts lines in specified file typesis-production.sh- Returns 0 if on production branch
Examples of Ordinary Scripts
deploy.sh- Multi-step deployment with rollbackbackup.sh- Database backup with retention managementsetup-dev.sh- Developer environment initializationtest-runner.sh- Configurable test executionmigrate-data.sh- Data migration with validation
Testing Scripts
Always test generated scripts for:
- Syntax:
bash -n script.sh - Undefined variables: Run with
set -utemporarily - Dependencies: Run without required tools installed
- Error paths: Test with invalid inputs
- Sourcing: Verify guard clause works (for ordinary scripts)
Remember
Every script you generate or modify should be production-ready:
- Maintainable by others
- Self-documenting through good structure
- Gracefully handles errors
- Provides helpful output
- Follows consistent patterns
These practices ensure scripts are professional, reliable, and user-friendly.