Bash Scripting Mastery
🚨 CRITICAL GUIDELINES
Windows File Path Requirements
MANDATORY: Always Use Backslashes on Windows for File Paths
When using Edit or Write tools on Windows, you MUST use backslashes (
) in file paths, NOT forward slashes (/
).
Examples:
-
❌ WRONG: D:/repos/project/file.tsx
-
✅ CORRECT: D:\repos\project\file.tsx
This applies to:
-
Edit tool file_path parameter
-
Write tool file_path parameter
-
All file operations on Windows systems
Documentation Guidelines
NEVER create new documentation files unless explicitly requested by the user.
-
Priority: Update existing README.md files rather than creating new documentation
-
Repository cleanliness: Keep repository root clean - only README.md unless user requests otherwise
-
Style: Documentation should be concise, direct, and professional - avoid AI-generated tone
-
User preference: Only create additional .md files when user specifically asks for documentation
Comprehensive guide for writing professional, portable, and maintainable bash scripts across all platforms.
TL;DR QUICK REFERENCE
Essential Checklist for Every Bash Script:
#!/usr/bin/env bash set -euo pipefail # Exit on error, undefined vars, pipe failures IFS=$'\n\t' # Safe word splitting
Use: shellcheck your_script.sh before deployment
Test on target platform(s) before production
Platform Compatibility Quick Check:
Linux/macOS: ✓ Full bash features
Git Bash (Windows): ✓ Most features, ✗ Some system calls
Containers: ✓ Depends on base image
POSIX mode: Use /bin/sh and avoid bashisms
Overview
This skill provides expert bash/shell scripting knowledge for ANY scripting task, ensuring professional-grade quality across all platforms.
MUST use this skill for:
-
✅ ANY bash/shell script creation or modification
-
✅ System automation and tooling
-
✅ DevOps/CI/CD pipeline scripts
-
✅ Build and deployment automation
-
✅ Script review, debugging, or optimization
-
✅ Converting manual commands to automated scripts
-
✅ Cross-platform script compatibility
What this skill provides:
-
Google Shell Style Guide compliance - Industry-standard formatting and patterns
-
ShellCheck validation - Automatic detection of common issues
-
Cross-platform compatibility - Linux, macOS, Windows (Git Bash/WSL), containers
-
POSIX compliance - Portable scripts that work everywhere
-
Security hardening - Input validation, injection prevention, privilege management
-
Error handling - Robust set -euo pipefail , trap handlers, exit codes
-
Performance optimization - Efficient patterns, avoiding anti-patterns
-
Testing with BATS - Unit testing, integration testing, CI/CD integration
-
Debugging techniques - Logging, troubleshooting, profiling
-
Production-ready patterns - Templates and best practices for real-world use
This skill activates automatically for:
-
Any mention of "bash", "shell", "script" in task
-
System automation requests
-
DevOps/CI/CD tasks
-
Build/deployment automation
-
Command line tool creation
Core Principles
- Safety First
ALWAYS start scripts with safety settings:
#!/usr/bin/env bash
Fail fast and loud
set -e # Exit on any error set -u # Exit on undefined variable set -o pipefail # Exit on pipe failure set -E # ERR trap inherited by functions
Optionally:
set -x # Debug mode (print commands before execution)
set -C # Prevent file overwrites with redirection
Safe word splitting
IFS=$'\n\t'
Script metadata
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" readonly SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")"
Why this matters:
-
set -e : Prevents cascading failures
-
set -u : Catches typos in variable names
-
set -o pipefail : Catches failures in the middle of pipes
-
IFS=$'\n\t' : Prevents word splitting on spaces (security issue)
- POSIX Compatibility vs Bash Features
Know when to use which:
POSIX-compliant (portable across shells)
#!/bin/sh
Use: [ ] tests, no arrays, no [[ ]], no <(process substitution)
Bash-specific (modern features, clearer syntax)
#!/usr/bin/env bash
Use: [[ ]], arrays, associative arrays, <(), process substitution
Decision matrix:
-
Need to run on any UNIX system → Use #!/bin/sh and POSIX only
-
Control the environment (modern Linux/macOS) → Use #!/usr/bin/env bash
-
Need advanced features (arrays, regex) → Use #!/usr/bin/env bash
- Quoting Rules (Critical)
ALWAYS quote variables to prevent word splitting and globbing
bad_cmd=$file_path # ✗ WRONG - word splitting good_cmd="$file_path" # ✓ CORRECT
Arrays: Quote expansion
files=("file 1.txt" "file 2.txt") process "${files[@]}" # ✓ CORRECT - each element quoted process "${files[*]}" # ✗ WRONG - all elements as one string
Command substitution: Quote the result
result="$(command)" # ✓ CORRECT result=$(command) # ✗ WRONG (unless you want word splitting)
Exception: When you WANT word splitting
shellcheck disable=SC2086
flags="-v -x -z" command $flags # Intentional word splitting
- Use ShellCheck
ALWAYS run ShellCheck before deployment:
Install
Ubuntu/Debian: apt-get install shellcheck
macOS: brew install shellcheck
Windows: scoop install shellcheck
Usage
shellcheck your_script.sh shellcheck -x your_script.sh # Follow source statements
In CI/CD
find . -name "*.sh" -exec shellcheck {} +
ShellCheck catches:
-
Quoting issues
-
Bashisms in POSIX scripts
-
Common logic errors
-
Security vulnerabilities
-
Performance anti-patterns
Platform-Specific Considerations
Windows (Git Bash) Path Conversion - CRITICAL
ESSENTIAL KNOWLEDGE: Git Bash/MINGW automatically converts Unix-style paths to Windows paths. This is the most common source of cross-platform scripting errors on Windows.
Complete Guide: See references/windows-git-bash-paths.md for comprehensive documentation.
Quick Reference:
Automatic conversion happens for:
/foo → C:/Program Files/Git/usr/foo --dir=/tmp → --dir=C:/msys64/tmp
Disable conversion when needed
MSYS_NO_PATHCONV=1 command /path/that/should/not/convert
Manual conversion with cygpath
unix_path=$(cygpath -u "C:\Windows\System32") # Windows to Unix win_path=$(cygpath -w "/c/Users/username") # Unix to Windows
Shell detection (fastest method)
if [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "mingw"* ]]; then echo "Git Bash detected" # Use path conversion fi
Or check $MSYSTEM variable (Git Bash/MSYS2 specific)
case "${MSYSTEM:-}" in MINGW64|MINGW32|MSYS) echo "MSYS2/Git Bash environment: $MSYSTEM" ;; esac
Common Issues:
Problem: Flags converted to paths
command /e /s # /e becomes C:/Program Files/Git/e
Solution: Use double slashes or dashes
command //e //s # OR: command -e -s
Problem: Spaces in paths
cd C:\Program Files\Git # Fails
Solution: Quote paths
cd "C:\Program Files\Git" # OR: cd /c/Program\ Files/Git
Linux
Primary target for most bash scripts:
Linux-specific features available
/proc filesystem systemd integration Linux-specific commands (apt, yum, systemctl)
Check for Linux
if [[ "$OSTYPE" == "linux-gnu"* ]]; then # Linux-specific code fi
macOS
BSD-based utilities (different from GNU):
macOS differences
sed -i '' # macOS requires empty string sed -i # Linux doesn't need it
Use ggrep, gsed, etc. for GNU versions
if command -v gsed &> /dev/null; then SED=gsed else SED=sed fi
Check for macOS
if [[ "$OSTYPE" == "darwin"* ]]; then # macOS-specific code fi
Windows (Git Bash / WSL)
Git Bash limitations:
Available in Git Bash:
- Most core utils
- File operations
- Process management (limited)
NOT available:
- systemd
- Some signals (SIGHUP behavior differs)
- /proc filesystem
- Native Windows path handling issues
Path handling
Git Bash uses Unix paths: /c/Users/...
Convert if needed:
winpath=$(cygpath -w "$unixpath") # Unix → Windows unixpath=$(cygpath -u "$winpath") # Windows → Unix
Check for Git Bash
if [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]]; then # Git Bash / Cygwin code fi
WSL (Windows Subsystem for Linux):
WSL is essentially Linux, but:
- Can access Windows filesystem at /mnt/c/
- Some syscalls behave differently
- Network configuration differs
Check for WSL
if grep -qi microsoft /proc/version 2>/dev/null; then # WSL-specific code fi
Containers (Docker/Kubernetes)
Container-aware scripting:
Minimal base images may not have bash
Use #!/bin/sh or install bash explicitly
Container detection
if [ -f /.dockerenv ] || grep -q docker /proc/1/cgroup 2>/dev/null; then # Running in Docker fi
Kubernetes detection
if [ -n "$KUBERNETES_SERVICE_HOST" ]; then # Running in Kubernetes fi
Best practices:
- Minimize dependencies
- Use absolute paths or PATH
- Don't assume user/group existence
- Handle signals properly (PID 1 issues)
Cross-Platform Template
#!/usr/bin/env bash set -euo pipefail
Detect platform
detect_platform() { case "$OSTYPE" in linux-gnu*) echo "linux" ;; darwin*) echo "macos" ;; msys*|cygwin*) echo "windows" ;; *) echo "unknown" ;; esac }
PLATFORM=$(detect_platform)
Platform-specific paths
case "$PLATFORM" in linux) SED=sed ;; macos) SED=$(command -v gsed || echo sed) ;; windows) # Git Bash specifics ;; esac
Best Practices
Function Design
Good function structure
function_name() { # 1. Local variables first local arg1="$1" local arg2="${2:-default_value}" local result=""
# 2. Input validation
if [[ -z "$arg1" ]]; then
echo "Error: arg1 is required" >&2
return 1
fi
# 3. Main logic
result=$(some_operation "$arg1" "$arg2")
# 4. Output/return
echo "$result"
return 0
}
Use functions, not scripts-in-scripts
Benefits: testability, reusability, namespacing
Variable Naming
Constants: UPPER_CASE
readonly MAX_RETRIES=3 readonly CONFIG_FILE="/etc/app/config.conf"
Global variables: UPPER_CASE or lower_case (be consistent)
GLOBAL_STATE="initialized"
Local variables: lower_case
local user_name="john" local file_count=0
Environment variables: UPPER_CASE (by convention)
export DATABASE_URL="postgres://..."
Readonly when possible
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
Error Handling
Method 1: Check exit codes explicitly
if ! command_that_might_fail; then echo "Error: Command failed" >&2 return 1 fi
Method 2: Use || for alternative actions
command_that_might_fail || { echo "Error: Command failed" >&2 return 1 }
Method 3: Trap for cleanup
cleanup() { local exit_code=$? # Cleanup operations rm -f "$TEMP_FILE" exit "$exit_code" } trap cleanup EXIT
Method 4: Custom error handler
error_exit() { local message="$1" local code="${2:-1}" echo "Error: $message" >&2 exit "$code" }
Usage
[[ -f "$config_file" ]] || error_exit "Config file not found: $config_file"
Input Validation
validate_input() { local input="$1"
# Check if empty
if [[ -z "$input" ]]; then
echo "Error: Input cannot be empty" >&2
return 1
fi
# Check format (example: alphanumeric only)
if [[ ! "$input" =~ ^[a-zA-Z0-9_-]+$ ]]; then
echo "Error: Input contains invalid characters" >&2
return 1
fi
# Check length
if [[ ${#input} -gt 255 ]]; then
echo "Error: Input too long (max 255 characters)" >&2
return 1
fi
return 0
}
Validate before use
read -r user_input if validate_input "$user_input"; then process "$user_input" fi
Argument Parsing
Simple argument parsing
usage() { cat <<EOF Usage: $SCRIPT_NAME [OPTIONS] <command>
Options: -h, --help Show this help -v, --verbose Verbose output -f, --file FILE Input file -o, --output DIR Output directory
Commands: build Build the project test Run tests EOF }
main() { local verbose=false local input_file="" local output_dir="." local command=""
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
exit 0
;;
-v|--verbose)
verbose=true
shift
;;
-f|--file)
input_file="$2"
shift 2
;;
-o|--output)
output_dir="$2"
shift 2
;;
-*)
echo "Error: Unknown option: $1" >&2
usage >&2
exit 1
;;
*)
command="$1"
shift
break
;;
esac
done
# Validate required arguments
if [[ -z "$command" ]]; then
echo "Error: Command is required" >&2
usage >&2
exit 1
fi
# Execute command
case "$command" in
build) do_build ;;
test) do_test ;;
*)
echo "Error: Unknown command: $command" >&2
usage >&2
exit 1
;;
esac
}
main "$@"
Logging
Logging levels
readonly LOG_LEVEL_DEBUG=0 readonly LOG_LEVEL_INFO=1 readonly LOG_LEVEL_WARN=2 readonly LOG_LEVEL_ERROR=3
Current log level
LOG_LEVEL=${LOG_LEVEL:-$LOG_LEVEL_INFO}
log_debug() { [[ $LOG_LEVEL -le $LOG_LEVEL_DEBUG ]] && echo "[DEBUG] $" >&2; } log_info() { [[ $LOG_LEVEL -le $LOG_LEVEL_INFO ]] && echo "[INFO] $" >&2; } log_warn() { [[ $LOG_LEVEL -le $LOG_LEVEL_WARN ]] && echo "[WARN] $" >&2; } log_error() { [[ $LOG_LEVEL -le $LOG_LEVEL_ERROR ]] && echo "[ERROR] $" >&2; }
With timestamps
log_with_timestamp() { local level="$1" shift echo "[$(date +'%Y-%m-%d %H:%M:%S')] [$level] $*" >&2 }
Usage
log_info "Starting process" log_error "Failed to connect to database"
Security Best Practices
Command Injection Prevention
NEVER use eval with user input
✗ WRONG - DANGEROUS
eval "$user_input"
NEVER use dynamic variable names from user input
✗ WRONG - DANGEROUS
eval "var_$user_input=value"
NEVER concatenate user input into commands
✗ WRONG - DANGEROUS
grep "$user_pattern" file.txt # If pattern contains -e flag, injection possible
✓ CORRECT - Use arrays
grep_args=("$user_pattern" "file.txt") grep "${grep_args[@]}"
✓ CORRECT - Use -- to separate options from arguments
grep -- "$user_pattern" file.txt
Path Traversal Prevention
Sanitize file paths
sanitize_path() { local path="$1"
# Remove .. components
path="${path//..\/}"
path="${path//\/..\//}"
# Remove leading /
path="${path#/}"
echo "$path"
}
Validate path is within allowed directory
is_safe_path() { local file_path="$1" local base_dir="$2"
# Resolve to absolute path
local real_path
real_path=$(readlink -f "$file_path" 2>/dev/null) || return 1
local real_base
real_base=$(readlink -f "$base_dir" 2>/dev/null) || return 1
# Check if path starts with base directory
[[ "$real_path" == "$real_base"/* ]]
}
Usage
if is_safe_path "$user_file" "/var/app/data"; then process_file "$user_file" else echo "Error: Invalid file path" >&2 exit 1 fi
Privilege Management
Check if running as root
if [[ $EUID -eq 0 ]]; then echo "Error: Do not run this script as root" >&2 exit 1 fi
Drop privileges if needed
drop_privileges() { local user="$1"
if [[ $EUID -eq 0 ]]; then
exec sudo -u "$user" "$0" "$@"
fi
}
Run specific command with elevated privileges
run_as_root() { if [[ $EUID -ne 0 ]]; then sudo "$@" else "$@" fi }
Temporary File Handling
Create secure temporary files
readonly TEMP_DIR=$(mktemp -d) readonly TEMP_FILE=$(mktemp)
Cleanup on exit
cleanup() { rm -rf "$TEMP_DIR" rm -f "$TEMP_FILE" } trap cleanup EXIT
Secure temporary file (only readable by owner)
secure_temp=$(mktemp) chmod 600 "$secure_temp"
Performance Optimization
Avoid Unnecessary Subshells
✗ SLOW - Creates subshell for each iteration
while IFS= read -r line; do count=$(echo "$count + 1" | bc) done < file.txt
✓ FAST - Arithmetic in bash
count=0 while IFS= read -r line; do ((count++)) done < file.txt
Use Bash Built-ins
✗ SLOW - External commands
dirname=$(dirname "$path") basename=$(basename "$path")
✓ FAST - Parameter expansion
dirname="${path%/}" basename="${path##/}"
✗ SLOW - grep for simple checks
if echo "$string" | grep -q "pattern"; then
✓ FAST - Bash regex
if [[ "$string" =~ pattern ]]; then
✗ SLOW - awk for simple extraction
field=$(echo "$line" | awk '{print $3}')
✓ FAST - Read into array
read -ra fields <<< "$line" field="${fields[2]}"
Process Substitution vs Pipes
When you need to read multiple commands' output
✓ GOOD - Process substitution
while IFS= read -r line1 <&3 && IFS= read -r line2 <&4; do echo "$line1 - $line2" done 3< <(command1) 4< <(command2)
Parallel processing
command1 & command2 & wait # Wait for all background jobs
Array Operations
✓ FAST - Native array operations
files=(*.txt) echo "Found ${#files[@]} files"
✗ SLOW - Parsing ls output
count=$(ls -1 *.txt | wc -l)
✓ FAST - Array filtering
filtered=() for item in "${array[@]}"; do [[ "$item" =~ ^[0-9]+$ ]] && filtered+=("$item") done
✓ FAST - Array joining
IFS=, joined="${array[*]}" IFS=$'\n\t'
Testing
Unit Testing with BATS
Install BATS
git clone https://github.com/bats-core/bats-core.git
cd bats-core && ./install.sh /usr/local
test/script.bats
#!/usr/bin/env bats
Load script to test
load '../script.sh'
@test "function returns correct value" { result=$(my_function "input") [ "$result" = "expected" ] }
@test "function handles empty input" { run my_function "" [ "$status" -eq 1 ] [ "${lines[0]}" = "Error: Input cannot be empty" ] }
@test "function validates input format" { run my_function "invalid@input" [ "$status" -eq 1 ] }
Run tests
bats test/script.bats
Integration Testing
integration_test.sh
#!/usr/bin/env bash set -euo pipefail
Setup
setup() { export TEST_DIR=$(mktemp -d) export TEST_FILE="$TEST_DIR/test.txt" }
Teardown
teardown() { rm -rf "$TEST_DIR" }
Test case
test_file_creation() { ./script.sh create "$TEST_FILE"
if [[ ! -f "$TEST_FILE" ]]; then
echo "FAIL: File was not created"
return 1
fi
echo "PASS: File creation works"
return 0
}
Run tests
main() { setup trap teardown EXIT
test_file_creation || exit 1
echo "All tests passed"
}
main
CI/CD Integration
.github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3
- name: Install shellcheck
run: sudo apt-get install -y shellcheck
- name: Run shellcheck
run: find . -name "*.sh" -exec shellcheck {} +
- name: Install bats
run: |
git clone https://github.com/bats-core/bats-core.git
cd bats-core
sudo ./install.sh /usr/local
- name: Run tests
run: bats test/
Debugging Techniques
Debug Mode
Method 1: set -x (print commands)
set -x command1 command2 set +x # Turn off
Method 2: PS4 for better output
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' set -x
Method 3: Conditional debugging
DEBUG=${DEBUG:-false} debug() { if [[ "$DEBUG" == "true" ]]; then echo "[DEBUG] $*" >&2 fi }
Usage: DEBUG=true ./script.sh
Tracing and Profiling
Trace function calls
trace() { echo "[TRACE] Function: ${FUNCNAME[1]}, Args: $*" >&2 }
my_function() { trace "$@" # Function logic }
Execution time profiling
profile() { local start=$(date +%s%N) "$@" local end=$(date +%s%N) local duration=$(( (end - start) / 1000000 )) echo "[PROFILE] Command '$*' took ${duration}ms" >&2 }
Usage
profile slow_command arg1 arg2
Common Issues and Solutions
Issue: Script works in bash but not in sh
Solution: Check for bashisms
checkbashisms script.sh
Issue: Works locally but not on server
Solution: Check PATH and environment
env echo "$PATH"
Issue: Whitespace in filenames breaking script
Solution: Always quote variables
for file in *.txt; do process "$file" # Not: process $file done
Issue: Script behaves differently in cron
Solution: Set PATH explicitly
PATH=/usr/local/bin:/usr/bin:/bin export PATH
Advanced Patterns
Configuration File Parsing
Simple key=value config
load_config() { local config_file="$1"
if [[ ! -f "$config_file" ]]; then
echo "Error: Config file not found: $config_file" >&2
return 1
fi
# Source config (dangerous if not trusted)
# shellcheck source=/dev/null
source "$config_file"
}
Safe config parsing (no code execution)
read_config() { local config_file="$1"
while IFS='=' read -r key value; do
# Skip comments and empty lines
[[ "$key" =~ ^[[:space:]]*# ]] && continue
[[ -z "$key" ]] && continue
# Trim whitespace
key=$(echo "$key" | tr -d ' ')
value=$(echo "$value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
# Export variable
declare -g "$key=$value"
done < "$config_file"
}
Parallel Processing
Simple background jobs
process_files_parallel() { local max_jobs=4 local job_count=0
for file in *.txt; do
# Start background job
process_file "$file" &
# Limit concurrent jobs
((job_count++))
if [[ $job_count -ge $max_jobs ]]; then
wait -n # Wait for any job to finish
((job_count--))
fi
done
# Wait for remaining jobs
wait
}
GNU Parallel (if available)
parallel_with_gnu() { parallel -j 4 process_file ::: *.txt }
Signal Handling
Graceful shutdown
shutdown_requested=false
handle_sigterm() { echo "Received SIGTERM, shutting down gracefully..." >&2 shutdown_requested=true }
trap handle_sigterm SIGTERM SIGINT
main_loop() { while [[ "$shutdown_requested" == "false" ]]; do # Do work sleep 1 done
echo "Shutdown complete" >&2
}
main_loop
Retries with Exponential Backoff
retry_with_backoff() { local max_attempts=5 local timeout=1 local attempt=1 local exitCode=0
while [[ $attempt -le $max_attempts ]]; do
if "$@"; then
return 0
else
exitCode=$?
fi
echo "Attempt $attempt failed! Retrying in $timeout seconds..." >&2
sleep "$timeout"
attempt=$((attempt + 1))
timeout=$((timeout * 2))
done
echo "Command failed after $max_attempts attempts!" >&2
return "$exitCode"
}
Usage
retry_with_backoff curl -f https://api.example.com/health
Resources for Additional Information
Official Documentation
Bash Reference Manual
-
The authoritative source for bash features and behavior
POSIX Shell Command Language
-
URL: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
-
For writing portable scripts
Style Guides
Google Shell Style Guide
-
Industry-standard practices from Google
Defensive Bash Programming
-
URL: https://kfirlavi.herokuapp.com/blog/2012/11/14/defensive-bash-programming
-
Best practices for robust scripts
Tools
ShellCheck
-
Static analysis tool for shell scripts
BATS (Bash Automated Testing System)
-
Unit testing framework for bash
shfmt
-
GitHub: https://github.com/mvdan/sh
-
Shell script formatter
Learning Resources
Bash Academy
-
Comprehensive bash learning resource
Bash Guide for Beginners
-
From The Linux Documentation Project
Advanced Bash-Scripting Guide
-
In-depth coverage of advanced topics
Bash Pitfalls
-
Common mistakes and how to avoid them
explainshell.com
-
Interactive tool to explain shell commands
Platform-Specific Resources
GNU Coreutils Manual
-
For Linux-specific commands
FreeBSD Manual Pages
-
For macOS (BSD-based) differences
Git for Windows
-
Git Bash documentation and issues
WSL Documentation
-
Windows Subsystem for Linux specifics
Community Resources
Stack Overflow - Bash Tag
-
Community Q&A
Unix & Linux Stack Exchange
-
Shell scripting expertise
Reddit - r/bash
-
Community discussions
Quick Reference
Bash Cheat Sheet
-
Quick syntax reference
ShellCheck Wiki
-
Explanations of ShellCheck warnings
Reference Files
For deeper coverage of specific topics, see the reference files:
-
references/platform_specifics.md - Detailed platform differences and workarounds
-
references/best_practices.md - Comprehensive industry standards and guidelines
-
references/patterns_antipatterns.md - Common patterns and pitfalls with solutions
When to Use This Skill
Always activate for:
-
Writing new bash scripts
-
Reviewing/refactoring existing scripts
-
Debugging shell script issues
-
Cross-platform shell scripting
-
DevOps automation tasks
-
CI/CD pipeline scripts
-
System administration automation
Key indicators:
-
User mentions bash, shell, or script
-
Task involves automation
-
Platform compatibility is a concern
-
Security or robustness is important
-
Performance optimization needed
Success Criteria
A bash script using this skill should:
-
✓ Pass ShellCheck with no warnings
-
✓ Include proper error handling (set -euo pipefail)
-
✓ Quote all variable expansions
-
✓ Include usage/help text
-
✓ Use functions for reusable logic
-
✓ Include appropriate comments
-
✓ Handle edge cases (empty input, missing files, etc.)
-
✓ Work across target platforms
-
✓ Follow consistent style (Google Shell Style Guide)
-
✓ Include cleanup (trap EXIT)
Quality checklist:
Run before deployment
shellcheck script.sh # No errors or warnings bash -n script.sh # Syntax check bats test/script.bats # Unit tests pass ./script.sh --help # Usage text displays DEBUG=true ./script.sh # Debug mode works
Troubleshooting
Script fails on different platform
-
Check for bashisms: checkbashisms script.sh
-
Verify commands exist: command -v tool_name
-
Test command flags: sed --version (GNU) vs sed (BSD)
ShellCheck warnings
-
Read the explanation: shellcheck -W SC2086
-
Fix the issue (don't just disable)
-
Only disable with justification: # shellcheck disable=SC2086 reason: intentional word splitting
Script works interactively but fails in cron
-
Set PATH explicitly
-
Use absolute paths
-
Redirect output for debugging: ./script.sh >> /tmp/cron.log 2>&1
Performance issues
-
Profile with time command
-
Enable tracing: set -x
-
Avoid unnecessary subshells and external commands
-
Use bash built-ins where possible
This skill provides comprehensive bash scripting knowledge. Combined with the reference files, you have access to industry-standard practices and platform-specific guidance for any bash scripting task.