Script Development - jvPalma/dotrun GitHub Wiki
Script Development
← User Guide | Home | Team Workflows →
Table of Contents
- Script Structure
- Documentation Standards
- Error Handling
- Input Validation
- Testing Scripts
- Performance Optimization
- Security Considerations
- Next Steps
📚 Quick Navigation
New to DotRun? Start with the User Guide and Quick Start Tutorial
Need examples? Check Script Examples for ready-to-use templates
Working with a team? See Team Workflows for collaboration
Script Structure
Basic Template
Every DotRun script should follow this structure:
#!/usr/bin/env bash
### DOC
# Script Name
#
# Brief description (one line)
#
# Detailed description explaining what the script does,
# when to use it, and any important considerations.
#
# Usage: dr scriptname [options] [arguments]
#
# Options:
# -h, --help Show this help message
# -v, --verbose Enable verbose output
# -d, --dry-run Show what would be done without doing it
#
# Arguments:
# arg1 Description of first argument
# arg2 Description of second argument (optional)
#
# Examples:
# dr scriptname arg1
# dr scriptname -v arg1 arg2
# dr scriptname --dry-run arg1
#
# Dependencies:
# - tool1: Required for X functionality
# - tool2: Optional, enables Y feature
#
# Environment Variables:
# VAR_NAME Description of what this variable does
#
# Exit Codes:
# 0 Success
# 1 General error
# 2 Invalid arguments
# 3 Missing dependencies
### DOC
# Strict mode
set -euo pipefail
# Script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Default values
VERBOSE=false
DRY_RUN=false
# Functions
usage() {
grep '^#' "$0" | grep -v '^#!/' | grep -v '^### DOC' | sed 's/^# *//'
}
error() {
echo "Error: $1" >&2
exit "${2:-1}"
}
info() {
if [ "$VERBOSE" == true ](/jvPalma/dotrun/wiki/-"$VERBOSE"-==-true-); then
echo "Info: $1"
fi
}
# Argument parsing
while [ $# -gt 0 ](/jvPalma/dotrun/wiki/-$#--gt-0-); do
case $1 in
-h | --help)
usage
exit 0
;;
-v | --verbose)
VERBOSE=true
shift
;;
-d | --dry-run)
DRY_RUN=true
shift
;;
-*)
error "Unknown option: $1" 2
;;
*)
break
;;
esac
done
# Main logic
main() {
# Validate arguments
if [ $# -eq 0 ](/jvPalma/dotrun/wiki/-$#--eq-0-); then
error "Missing required argument" 2
fi
local arg1="$1"
local arg2="${2:-default}"
info "Processing $arg1 with $arg2"
if [ "$DRY_RUN" == true ](/jvPalma/dotrun/wiki/-"$DRY_RUN"-==-true-); then
echo "Would execute: command $arg1 $arg2"
else
# Actual implementation
echo "Executing: command $arg1 $arg2"
fi
}
# Run main function with remaining arguments
main "$@"
Advanced Features
Progress Indicators
# Simple progress
echo -n "Processing files..."
for file in *.txt; do
process_file "$file"
echo -n "."
done
echo " done!"
# Percentage progress
total=$(find . -name "*.log" | wc -l)
current=0
while IFS= read -r file; do
current=$((current + 1))
percent=$((current * 100 / total))
printf "\rProcessing: %3d%%" "$percent"
process_file "$file"
done < <(find . -name "*.log")
printf "\rProcessing: 100%% - Complete!\n"
Color Output
# Color functions
red() { echo -e "\033[31m$*\033[0m"; }
green() { echo -e "\033[32m$*\033[0m"; }
yellow() { echo -e "\033[33m$*\033[0m"; }
blue() { echo -e "\033[34m$*\033[0m"; }
# Usage
green "✓ Success: Operation completed"
red "✗ Error: Operation failed"
yellow "⚠ Warning: Check configuration"
blue "ℹ Info: Processing started"
Documentation Standards
Inline Documentation Rules
- Always include
### DOCsection - Start with script name and brief description
- Provide clear usage examples
- Document all options and arguments
- List dependencies and requirements
- Include exit codes for debugging
Documentation Sections
Required Sections
- Script name and description
- Usage syntax
- At least one example
Recommended Sections
- Options (if any)
- Arguments (if any)
- Dependencies
- Environment variables
- Exit codes
- Notes/warnings
Optional Sections
- Author information
- Version history
- Related scripts
- External references
Error Handling
Best Practices
# 1. Use strict mode
set -euo pipefail
# 2. Trap errors
trap 'echo "Error on line $LINENO"' ERR
# 3. Cleanup function
cleanup() {
# Remove temporary files
rm -f "$TEMP_FILE"
# Restore state
cd "$ORIGINAL_DIR"
}
trap cleanup EXIT
# 4. Check dependencies
check_dependency() {
if ! command -v "$1" &>/dev/null; then
error "$1 is required but not installed" 3
fi
}
check_dependency git
check_dependency jq
Common Patterns
# File operations
if [ ! -f "$file" ](/jvPalma/dotrun/wiki/-!--f-"$file"-); then
error "File not found: $file"
fi
# Directory operations
if [ ! -d "$dir" ](/jvPalma/dotrun/wiki/-!--d-"$dir"-); then
error "Directory not found: $dir"
fi
# Command success
if ! command_that_might_fail; then
error "Command failed"
fi
# Pipeline errors
set -o pipefail
if ! curl -s "$url" | jq '.data' >output.json; then
error "Failed to fetch and parse data"
fi
Input Validation
Argument Validation
# Required arguments
if [ $# -lt 1 ](/jvPalma/dotrun/wiki/-$#--lt-1-); then
usage
error "Missing required argument" 2
fi
# Numeric validation
if ! [ "$port" =~ ^[0-9]+$ ](/jvPalma/dotrun/wiki/-"$port"-=~-^[0-9]+$-); then
error "Port must be a number: $port" 2
fi
# Range validation
if ((port < 1 || port > 65535)); then
error "Port must be between 1 and 65535: $port" 2
fi
# File path validation
if [ "$path" =~ \.\. ](/jvPalma/dotrun/wiki/-"$path"-=~-\.\.-); then
error "Path cannot contain '..': $path" 2
fi
# Email validation
if ! [ "$email" =~ ^[^@]+@[^@]+\.[^@]+$ ](/jvPalma/dotrun/wiki/-"$email"-=~-^[^@]+@[^@]+\.[^@]+$-); then
error "Invalid email format: $email" 2
fi
Interactive Confirmation
confirm() {
local prompt="${1:-Continue?}"
local default="${2:-n}"
if [ "$default" == "y" ](/jvPalma/dotrun/wiki/-"$default"-==-"y"-); then
prompt="$prompt [Y/n] "
else
prompt="$prompt [y/N] "
fi
read -r -p "$prompt" response
response=${response:-$default}
[ "$response" =~ ^[Yy]$ ](/jvPalma/dotrun/wiki/-"$response"-=~-^[Yy]$-)
}
# Usage
if confirm "Delete all files?" "n"; then
rm -rf ./*
else
echo "Cancelled"
fi
Testing Scripts
Unit Testing
Create test files for your scripts:
# In test/test_myscript.sh
#!/usr/bin/env bash
source "$(dirname "$0")/../bin/myscript"
# Test function
test_validation() {
# Test valid input
if validate_email "[email protected]"; then
echo "✓ Valid email test passed"
else
echo "✗ Valid email test failed"
return 1
fi
# Test invalid input
if ! validate_email "invalid-email"; then
echo "✓ Invalid email test passed"
else
echo "✗ Invalid email test failed"
return 1
fi
}
# Run tests
test_validation
Integration Testing
# Test script execution
test_script_execution() {
local output
local exit_code
# Capture output and exit code
output=$(dr myscript arg1 2>&1) || exit_code=$?
# Verify output
if [ "$output" =~ "expected string" ](/jvPalma/dotrun/wiki/-"$output"-=~-"expected-string"-); then
echo "✓ Output test passed"
else
echo "✗ Output test failed"
echo "Got: $output"
fi
# Verify exit code
if [ "${exit_code:-0}" -eq 0 ](/jvPalma/dotrun/wiki/-"${exit_code:-0}"--eq-0-); then
echo "✓ Exit code test passed"
else
echo "✗ Exit code test failed: $exit_code"
fi
}
Performance Optimization
Best Practices
# 1. Avoid useless cat
# Bad
cat file.txt | grep pattern
# Good
grep pattern file.txt
# 2. Use built-in string manipulation
# Bad
echo "$string" | cut -d: -f1
# Good
echo "${string%%:*}"
# 3. Avoid repeated command calls
# Bad
for file in *.txt; do
$(dirname "$file")/process.sh "$file"
done
# Good
dir=$(dirname "$file")
for file in *.txt; do
"$dir/process.sh" "$file"
done
# 4. Use process substitution
# Bad
temp_file=$(mktemp)
command1 >"$temp_file"
command2 <"$temp_file"
rm "$temp_file"
# Good
command2 < <(command1)
Bulk Operations
# Process files in batches
find . -name "*.log" -print0 \
| xargs -0 -P 4 -n 100 process_batch
# Parallel execution
export -f process_file
find . -name "*.txt" \
| parallel -j 4 process_file
Security Considerations
Input Sanitization
# Escape special characters
sanitize() {
printf '%q' "$1"
}
# Use sanitized input
user_input=$(sanitize "$1")
eval "command $user_input"
# Better: avoid eval entirely
command "$1"
Safe File Operations
# Create temp files securely
TEMP_FILE=$(mktemp)
trap 'rm -f "$TEMP_FILE"' EXIT
# Set restrictive permissions
touch sensitive_file
chmod 600 sensitive_file
# Validate file paths
if [ "$file_path" =~ ^/ ](/jvPalma/dotrun/wiki/-"$file_path"-=~-^/-); then
error "Absolute paths not allowed"
fi
# Prevent directory traversal
real_path=$(realpath "$file_path")
if [ ! "$real_path" =~ ^"$SAFE_DIR" ](/jvPalma/dotrun/wiki/-!-"$real_path"-=~-^"$SAFE_DIR"-); then
error "Access denied: $file_path"
fi
Environment Security
# Clear sensitive variables
unset PASSWORD
unset API_KEY
# Use secure defaults
umask 077 # Files created with 600 permissions
# Validate environment
if [ -z "${HOME:-}" ](/jvPalma/dotrun/wiki/--z-"${HOME:-}"-); then
error "HOME environment variable not set"
fi
Next Steps
With solid script development practices:
- Share with Team: Learn about Team Workflows
- Manage Configuration: Set up Configuration Management
- Create Documentation: Master the Documentation System
- Browse Examples: Study Example Scripts
- Optimize Workflow: Explore Developer Experience