Script Development Best Practices - jvPalma/dotrun GitHub Wiki
Script Development Best Practices
Skill Level: Beginner to Intermediate
Essential practices for creating maintainable, secure, and effective DotRun scripts.
Naming Conventions
Script Names
- Use descriptive, kebab-case names:
branch-cleanup,deploy-staging - Include action and context:
git-merge-tool,docker-build-image - Avoid generic names: Use
database-backupinstead ofbackup - Keep names concise but clear:
db-migrateinstead ofdatabase-migration-runner
Examples:
# Good naming
dr set git/branch-cleanup
dr set docker/build-image
dr set deploy/staging-environment
# Avoid
dr set script1
dr set backup
dr set thing
Category Organization
- Use clear category names:
git/,docker/,deployment/ - Group by technology or workflow:
frontend/,testing/,monitoring/ - Keep hierarchy shallow: Prefer
git/branch-cleanupovergit/branch/cleanup - Use consistent naming across teams
Organization Patterns:
# By Technology Stack
git/
docker/
kubernetes/
terraform/
# By Workflow Stage
development/
testing/
deployment/
monitoring/
# By Team Function
frontend/
backend/
devops/
qa/
Script Structure
Required Elements
Every script should include:
- Proper Shebang:
#!/usr/bin/env bash - Error Handling:
set -euo pipefail - Documentation:
### DOCsections - Main Function: Entry point with
"$@"
Template:
#!/usr/bin/env bash
### DOC
# Brief description of what this script does
# Usage: dr script-name [options] [arguments]
#
# Examples:
# dr script-name
# dr script-name --verbose
### DOC
set -euo pipefail
main() {
# Script logic here
echo "โ
Script completed successfully!"
}
main "$@"
Documentation Standards
Inline Documentation (### DOC)
- Start with one-line description
- Include usage examples with actual commands
- Document important options and flags
- Note dependencies and requirements
- Keep it concise but informative
### DOC
# Git branch cleanup tool with smart merge detection
#
# This script safely removes merged branches after confirming
# they have been integrated into the main branch.
#
# Usage:
# dr git/branch-cleanup # Interactive mode
# dr git/branch-cleanup --auto # Auto-remove merged branches
# dr git/branch-cleanup --dry-run # Show what would be removed
#
# Requirements: git
# Safety: Never removes current branch or main/master
### DOC
Markdown Documentation
- Follow consistent structure (Usage, Options, Examples, Notes)
- Include practical, working examples
- Document edge cases and limitations
- Keep documentation updated when script changes
- Use clear headings and formatting
Error Handling
Use Proper Bash Options
# Always include at the top of scripts
set -euo pipefail # Exit on error, undefined vars, pipe failures
# For scripts that need to continue on errors
set -uo pipefail # Allow errors but catch undefined vars and pipe failures
Validate Inputs
# Check required parameters
if [ -z "${1:-}" ](/jvPalma/dotrun/wiki/--z-"${1:-}"-); then
echo "โ Usage: dr script-name <required-param>"
exit 1
fi
# Validate file existence
if [ ! -f "$config_file" ](/jvPalma/dotrun/wiki/-!--f-"$config_file"-); then
echo "โ Configuration file not found: $config_file"
exit 1
fi
# Validate directory existence
if [ ! -d "$target_dir" ](/jvPalma/dotrun/wiki/-!--d-"$target_dir"-); then
echo "โ Target directory does not exist: $target_dir"
exit 1
fi
Check Dependencies
# Using DotRun helpers
source "$DR_CONFIG/helpers/pkg.sh"
validatePkg git docker kubectl
# Manual checking
check_dependencies() {
local deps=("git" "docker" "kubectl")
for dep in "${deps[@]}"; do
if ! command -v "$dep" >/dev/null 2>&1; then
echo "โ Required dependency not found: $dep"
echo "๐ก Install with: brew install $dep" # or appropriate command
exit 1
fi
done
}
Provide Clear Error Messages
# Include context and suggestions
error() {
echo "โ Error: $1" >&2
if [ -n "${2:-}" ](/jvPalma/dotrun/wiki/--n-"${2:-}"-); then
echo "๐ก Suggestion: $2" >&2
fi
exit 1
}
# Usage examples
[ -f "$file" ](/jvPalma/dotrun/wiki/--f-"$file"-) || error "File not found: $file" "Check the file path and permissions"
command || error "Command failed" "Ensure prerequisites are installed"
Security Considerations
Never Hardcode Secrets
# โ Bad - secrets in code
PASSWORD="secret123"
API_KEY="abc123def456"
# โ
Good - use environment variables
PASSWORD="${PASSWORD:-$(read -s -p 'Enter password: ' pwd && echo "$pwd")}"
API_KEY="${API_KEY:-}"
if [ -z "$API_KEY" ](/jvPalma/dotrun/wiki/--z-"$API_KEY"-); then
echo "โ API_KEY environment variable required"
exit 1
fi
Validate File Paths
# โ Bad - dangerous user input
rm -rf "$user_input"
# โ
Good - validate input
if [ "$user_input" =~ ^[a-zA-Z0-9/_.-]+$ ](/jvPalma/dotrun/wiki/-"$user_input"-=~-^[a-zA-Z0-9/_.-]+$-) && [ -d "$user_input" ](/jvPalma/dotrun/wiki/--d-"$user_input"-); then
rm -rf "$user_input"
else
echo "โ Invalid or unsafe path: $user_input"
exit 1
fi
Handle Temporary Files Safely
# Create secure temporary files
temp_file=$(mktemp)
trap 'rm -f "$temp_file"' EXIT
# Use secure temporary directories
temp_dir=$(mktemp -d)
trap 'rm -rf "$temp_dir"' EXIT
Performance Best Practices
Avoid Redundant Operations
# โ Slow - repeated calls
for file in *.txt; do
if git status --porcelain | grep -q "$file"; then
echo "$file is modified"
fi
done
# โ
Fast - cache the result
git_status=$(git status --porcelain)
for file in *.txt; do
if echo "$git_status" | grep -q "$file"; then
echo "$file is modified"
fi
done
Use Efficient Tools
# โ Slower
grep -r "pattern" .
# โ
Faster (if available)
if command -v rg >/dev/null; then
rg "pattern"
else
grep -r "pattern" .
fi
Parallel Execution When Appropriate
# โ Slow - sequential operations
for repo in repo1 repo2 repo3; do
git clone "$repo"
done
# โ
Fast - parallel operations
for repo in repo1 repo2 repo3; do
git clone "$repo" &
done
wait
Platform Compatibility
Handle OS Differences
# macOS vs Linux differences
if [ "$OSTYPE" == "darwin"* ](/jvPalma/dotrun/wiki/-"$OSTYPE"-==-"darwin"*-); then
# macOS-specific code
if command -v greadlink >/dev/null; then
readlink_cmd="greadlink"
else
readlink_cmd="readlink"
fi
elif [ "$OSTYPE" == "linux-gnu"* ](/jvPalma/dotrun/wiki/-"$OSTYPE"-==-"linux-gnu"*-); then
# Linux-specific code
readlink_cmd="readlink"
fi
Package Manager Detection
# Handle different package managers
if command -v apt >/dev/null; then
# Ubuntu/Debian
install_cmd="sudo apt install"
elif command -v yum >/dev/null; then
# CentOS/RHEL
install_cmd="sudo yum install"
elif command -v pacman >/dev/null; then
# Arch Linux
install_cmd="sudo pacman -S"
elif command -v brew >/dev/null; then
# macOS
install_cmd="brew install"
fi
WSL Considerations
# Handle WSL-specific issues
if grep -q microsoft /proc/version 2>/dev/null; then
# Running in WSL
# Handle line ending differences
# Use WSL paths, not Windows paths
# Be aware of file permission limitations
echo "๐ง Running in WSL environment"
fi
Testing and Validation
Include Self-Tests
#!/usr/bin/env bash
### DOC
# Script with built-in testing capability
### DOC
set -euo pipefail
run_tests() {
echo "๐งช Running self-tests..."
# Test 1: Dependency check
if ! command -v git >/dev/null; then
echo "โ Test failed: git not available"
return 1
fi
echo "โ
Dependencies available"
# Test 2: Function test
if [ "$(test_function "hello")" == "Hello, hello!" ](/jvPalma/dotrun/wiki/-"$(test_function-"hello")"-==-"Hello,-hello!"-); then
echo "โ
Function test passed"
else
echo "โ Function test failed"
return 1
fi
echo "โ
All tests passed"
}
test_function() {
local input="$1"
echo "Hello, $input!"
}
main() {
if [ "${1:-}" == "--test" ](/jvPalma/dotrun/wiki/-"${1:-}"-==-"--test"-); then
run_tests
exit $?
fi
# Regular script logic
test_function "World"
}
main "$@"
Validate with ShellCheck
# Include ShellCheck validation in your workflow
#!/usr/bin/env bash
# Validate all scripts
find ~/.config/dotrun/bin -name "*.sh" -exec shellcheck {} \;
# Or integrate into your script
if command -v shellcheck >/dev/null; then
shellcheck "$0"
fi
Code Style Guidelines
Consistent Formatting
# โ
Good formatting
if [ -f "$file" ](/jvPalma/dotrun/wiki/--f-"$file"-); then
echo "File exists"
elif [ -d "$file" ](/jvPalma/dotrun/wiki/--d-"$file"-); then
echo "Directory exists"
else
echo "Not found"
fi
# Use consistent indentation (4 spaces)
function long_function() {
local variable="value"
if [ condition ](/jvPalma/dotrun/wiki/-condition-); then
do_something
fi
}
# Quote variables to prevent word splitting
echo "Hello, $name!"
rm -f "$temp_file"
Variable Naming
# Use descriptive variable names
config_file="/path/to/config"
user_name="$1"
max_retries=3
# Use readonly for constants
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly VERSION="1.0.0"
# Use local in functions
function process_data() {
local input_file="$1"
local output_file="$2"
# Process files
}
Next Steps
- Team Collaboration Best Practices - Working effectively with teams
- Collection Organization - Structuring script collections
- Security Patterns - Advanced security practices
Following these practices will help you create professional, maintainable scripts that work reliably across different environments.