| name | bash-master |
| description | Expert bash/shell scripting system across ALL platforms. PROACTIVELY activate for: (1) ANY bash/shell script task, (2) System automation, (3) DevOps/CI/CD scripts, (4) Build/deployment automation, (5) Script review/debugging, (6) Converting commands to scripts. Provides: Google Shell Style Guide compliance, ShellCheck validation, cross-platform compatibility (Linux/macOS/Windows/containers), POSIX compliance, security hardening, error handling, performance optimization, testing with BATS, and production-ready patterns. Ensures professional-grade, secure, portable scripts every time. |
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
IFS=$'\n\t'
Platform Compatibility Quick Check:
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
1. Safety First
ALWAYS start scripts with safety settings:
#!/usr/bin/env bash
set -e
set -u
set -o pipefail
set -E
IFS=$'\n\t'
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)
2. POSIX Compatibility vs Bash Features
Know when to use which:
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
3. Quoting Rules (Critical)
bad_cmd=$file_path
good_cmd="$file_path"
files=("file 1.txt" "file 2.txt")
process "${files[@]}"
process "${files[*]}"
result="$(command)"
result=$(command)
flags="-v -x -z"
command $flags
4. Use ShellCheck
ALWAYS run ShellCheck before deployment:
shellcheck your_script.sh
shellcheck -x your_script.sh
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:
/foo → C:/Program Files/Git/usr/foo
--dir=/tmp → --dir=C:/msys64/tmp
MSYS_NO_PATHCONV=1 command /path/that/should/not/convert
unix_path=$(cygpath -u "C:\Windows\System32")
win_path=$(cygpath -w "/c/Users/username")
if [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "mingw"* ]]; then
echo "Git Bash detected"
fi
case "${MSYSTEM:-}" in
MINGW64|MINGW32|MSYS)
echo "MSYS2/Git Bash environment: $MSYSTEM"
;;
esac
Common Issues:
command /e /s
command //e //s
cd C:\Program Files\Git
cd "C:\Program Files\Git"
Linux
Primary target for most bash scripts:
/proc filesystem
systemd integration
Linux-specific commands (apt, yum, systemctl)
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
fi
macOS
BSD-based utilities (different from GNU):
sed -i ''
sed -i
if command -v gsed &> /dev/null; then
SED=gsed
else
SED=sed
fi
if [[ "$OSTYPE" == "darwin"* ]]; then
fi
Windows (Git Bash / WSL)
Git Bash limitations:
- Most core utils
- File operations
- Process management (limited)
- systemd
- Some signals (SIGHUP behavior differs)
- /proc filesystem
- Native Windows path handling issues
winpath=$(cygpath -w "$unixpath")
unixpath=$(cygpath -u "$winpath")
if [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]]; then
fi
WSL (Windows Subsystem for Linux):
if grep -qi microsoft /proc/version 2>/dev/null; then
fi
Containers (Docker/Kubernetes)
Container-aware scripting:
if [ -f /.dockerenv ] || grep -q docker /proc/1/cgroup 2>/dev/null; then
fi
if [ -n "$KUBERNETES_SERVICE_HOST" ]; then
fi
Cross-Platform Template
#!/usr/bin/env bash
set -euo pipefail
detect_platform() {
case "$OSTYPE" in
linux-gnu*) echo "linux" ;;
darwin*) echo "macos" ;;
msys*|cygwin*) echo "windows" ;;
*) echo "unknown" ;;
esac
}
PLATFORM=$(detect_platform)
case "$PLATFORM" in
linux)
SED=sed
;;
macos)
SED=$(command -v gsed || echo sed)
;;
windows)
;;
esac
Best Practices
Function Design
function_name() {
local arg1="$1"
local arg2="${2:-default_value}"
local result=""
if [[ -z "$arg1" ]]; then
echo "Error: arg1 is required" >&2
return 1
fi
result=$(some_operation "$arg1" "$arg2")
echo "$result"
return 0
}
Variable Naming
readonly MAX_RETRIES=3
readonly CONFIG_FILE="/etc/app/config.conf"
GLOBAL_STATE="initialized"
local user_name="john"
local file_count=0
export DATABASE_URL="postgres://..."
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
Error Handling
if ! command_that_might_fail; then
echo "Error: Command failed" >&2
return 1
fi
command_that_might_fail || {
echo "Error: Command failed" >&2
return 1
}
cleanup() {
local exit_code=$?
rm -f "$TEMP_FILE"
exit "$exit_code"
}
trap cleanup EXIT
error_exit() {
local message="$1"
local code="${2:-1}"
echo "Error: $message" >&2
exit "$code"
}
[[ -f "$config_file" ]] || error_exit "Config file not found: $config_file"
Input Validation
validate_input() {
local input="$1"
if [[ -z "$input" ]]; then
echo "Error: Input cannot be empty" >&2
return 1
fi
if [[ ! "$input" =~ ^[a-zA-Z0-9_-]+$ ]]; then
echo "Error: Input contains invalid characters" >&2
return 1
fi
if [[ ${#input} -gt 255 ]]; then
echo "Error: Input too long (max 255 characters)" >&2
return 1
fi
return 0
}
read -r user_input
if validate_input "$user_input"; then
process "$user_input"
fi
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=""
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
if [[ -z "$command" ]]; then
echo "Error: Command is required" >&2
usage >&2
exit 1
fi
case "$command" in
build) do_build ;;
test) do_test ;;
*)
echo "Error: Unknown command: $command" >&2
usage >&2
exit 1
;;
esac
}
main "$@"
Logging
readonly LOG_LEVEL_DEBUG=0
readonly LOG_LEVEL_INFO=1
readonly LOG_LEVEL_WARN=2
readonly LOG_LEVEL_ERROR=3
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; }
log_with_timestamp() {
local level="$1"
shift
echo "[$(date +'%Y-%m-%d %H:%M:%S')] [$level] $*" >&2
}
log_info "Starting process"
log_error "Failed to connect to database"
Security Best Practices
Command Injection Prevention
eval "$user_input"
eval "var_$user_input=value"
grep "$user_pattern" file.txt
grep_args=("$user_pattern" "file.txt")
grep "${grep_args[@]}"
grep -- "$user_pattern" file.txt
Path Traversal Prevention
sanitize_path() {
local path="$1"
path="${path//..\/}"
path="${path//\/..\//}"
path="${path#/}"
echo "$path"
}
is_safe_path() {
local file_path="$1"
local base_dir="$2"
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
[[ "$real_path" == "$real_base"/* ]]
}
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
if [[ $EUID -eq 0 ]]; then
echo "Error: Do not run this script as root" >&2
exit 1
fi
drop_privileges() {
local user="$1"
if [[ $EUID -eq 0 ]]; then
exec sudo -u "$user" "$0" "$@"
fi
}
run_as_root() {
if [[ $EUID -ne 0 ]]; then
sudo "$@"
else
"$@"
fi
}
Temporary File Handling
readonly TEMP_DIR=$(mktemp -d)
readonly TEMP_FILE=$(mktemp)
cleanup() {
rm -rf "$TEMP_DIR"
rm -f "$TEMP_FILE"
}
trap cleanup EXIT
secure_temp=$(mktemp)
chmod 600 "$secure_temp"
Performance Optimization
Avoid Unnecessary Subshells
while IFS= read -r line; do
count=$(echo "$count + 1" | bc)
done < file.txt
count=0
while IFS= read -r line; do
((count++))
done < file.txt
Use Bash Built-ins
dirname=$(dirname "$path")
basename=$(basename "$path")
dirname="${path%/*}"
basename="${path##*/}"
if echo "$string" | grep -q "pattern"; then
if [[ "$string" =~ pattern ]]; then
field=$(echo "$line" | awk '{print $3}')
read -ra fields <<< "$line"
field="${fields[2]}"
Process Substitution vs Pipes
while IFS= read -r line1 <&3 && IFS= read -r line2 <&4; do
echo "$line1 - $line2"
done 3< <(command1) 4< <(command2)
command1 &
command2 &
wait
Array Operations
files=(*.txt)
echo "Found ${#files[@]} files"
count=$(ls -1 *.txt | wc -l)
filtered=()
for item in "${array[@]}"; do
[[ "$item" =~ ^[0-9]+$ ]] && filtered+=("$item")
done
IFS=,
joined="${array[*]}"
IFS=$'\n\t'
Testing
Unit Testing with BATS
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 ]
}
Integration Testing
set -euo pipefail
setup() {
export TEST_DIR=$(mktemp -d)
export TEST_FILE="$TEST_DIR/test.txt"
}
teardown() {
rm -rf "$TEST_DIR"
}
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
}
main() {
setup
trap teardown EXIT
test_file_creation || exit 1
echo "All tests passed"
}
main
CI/CD Integration
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
set -x
command1
command2
set +x
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
set -x
DEBUG=${DEBUG:-false}
debug() {
if [[ "$DEBUG" == "true" ]]; then
echo "[DEBUG] $*" >&2
fi
}
Tracing and Profiling
trace() {
echo "[TRACE] Function: ${FUNCNAME[1]}, Args: $*" >&2
}
my_function() {
trace "$@"
}
profile() {
local start=$(date +%s%N)
"$@"
local end=$(date +%s%N)
local duration=$(( (end - start) / 1000000 ))
echo "[PROFILE] Command '$*' took ${duration}ms" >&2
}
profile slow_command arg1 arg2
Common Issues and Solutions
checkbashisms script.sh
env
echo "$PATH"
for file in *.txt; do
process "$file"
done
PATH=/usr/local/bin:/usr/bin:/bin
export PATH
Advanced Patterns
Configuration File Parsing
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_file"
}
read_config() {
local config_file="$1"
while IFS='=' read -r key value; do
[[ "$key" =~ ^[[:space:]]*# ]] && continue
[[ -z "$key" ]] && continue
key=$(echo "$key" | tr -d ' ')
value=$(echo "$value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
declare -g "$key=$value"
done < "$config_file"
}
Parallel Processing
process_files_parallel() {
local max_jobs=4
local job_count=0
for file in *.txt; do
process_file "$file" &
((job_count++))
if [[ $job_count -ge $max_jobs ]]; then
wait -n
((job_count--))
fi
done
wait
}
parallel_with_gnu() {
parallel -j 4 process_file ::: *.txt
}
Signal Handling
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
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"
}
retry_with_backoff curl -f https://api.example.com/health
Resources for Additional Information
Official Documentation
-
Bash Reference Manual
-
POSIX Shell Command Language
Style Guides
-
Google Shell Style Guide
-
Defensive Bash Programming
Tools
-
ShellCheck
-
BATS (Bash Automated Testing System)
-
shfmt
Learning Resources
-
Bash Academy
-
Bash Guide for Beginners
-
Advanced Bash-Scripting Guide
-
Bash Pitfalls
-
explainshell.com
Platform-Specific Resources
-
GNU Coreutils Manual
-
FreeBSD Manual Pages
-
Git for Windows
-
WSL Documentation
Community Resources
-
Stack Overflow - Bash Tag
-
Unix & Linux Stack Exchange
-
Reddit - r/bash
Quick Reference
-
Bash Cheat Sheet
-
ShellCheck Wiki
Reference Files
For deeper coverage of specific topics, see the reference files:
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:
shellcheck script.sh
bash -n script.sh
bats test/script.bats
./script.sh --help
DEBUG=true ./script.sh
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.