-
-
Notifications
You must be signed in to change notification settings - Fork 0
Script Development Best Practices
Joao Palma edited this page Oct 27, 2025
·
3 revisions
Skill Level: Beginner to Intermediate
Essential practices for creating maintainable, secure, and effective DotRun scripts.
- 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- 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/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 "$@"- 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- 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
# 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# Check required parameters
if [[ -z "${1:-}" ]]; then
echo "❌ Usage: dr script-name <required-param>"
exit 1
fi
# Validate file existence
if [[ ! -f "$config_file" ]]; then
echo "❌ Configuration file not found: $config_file"
exit 1
fi
# Validate directory existence
if [[ ! -d "$target_dir" ]]; then
echo "❌ Target directory does not exist: $target_dir"
exit 1
fi# 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
}# Include context and suggestions
error() {
echo "❌ Error: $1" >&2
if [[ -n "${2:-}" ]]; then
echo "💡 Suggestion: $2" >&2
fi
exit 1
}
# Usage examples
[[ -f "$file" ]] || error "File not found: $file" "Check the file path and permissions"
command || error "Command failed" "Ensure prerequisites are installed"# ❌ 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" ]]; then
echo "❌ API_KEY environment variable required"
exit 1
fi# ❌ Bad - dangerous user input
rm -rf "$user_input"
# ✅ Good - validate input
if [[ "$user_input" =~ ^[a-zA-Z0-9/_.-]+$ ]] && [[ -d "$user_input" ]]; then
rm -rf "$user_input"
else
echo "❌ Invalid or unsafe path: $user_input"
exit 1
fi# 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# ❌ 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# ❌ Slower
grep -r "pattern" .
# ✅ Faster (if available)
if command -v rg >/dev/null; then
rg "pattern"
else
grep -r "pattern" .
fi# ❌ 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# macOS vs Linux differences
if [[ "$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"* ]]; then
# Linux-specific code
readlink_cmd="readlink"
fi# 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# 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#!/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!" ]]; 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" ]]; then
run_tests
exit $?
fi
# Regular script logic
test_function "World"
}
main "$@"# 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# ✅ Good formatting
if [[ -f "$file" ]]; then
echo "File exists"
elif [[ -d "$file" ]]; then
echo "Directory exists"
else
echo "Not found"
fi
# Use consistent indentation (4 spaces)
function long_function() {
local variable="value"
if [[ condition ]]; then
do_something
fi
}
# Quote variables to prevent word splitting
echo "Hello, $name!"
rm -f "$temp_file"# 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
}- 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.