# Script Development [← User Guide](User-Guide.md) | [Home](Home.md) | [Team Workflows →](Team-Workflows.md) --- ## Table of Contents - [Script Structure](#script-structure) - [Documentation Standards](#documentation-standards) - [Error Handling](#error-handling) - [Input Validation](#input-validation) - [Testing Scripts](#testing-scripts) - [Performance Optimization](#performance-optimization) - [Security Considerations](#security-considerations) - [Next Steps](#next-steps) > **📚 Quick Navigation** > New to DotRun? Start with the [User Guide](User-Guide.md) and [Quick Start Tutorial](User-Guide.md#quick-start-tutorial) > Need examples? Check [Script Examples](Script-Examples.md) for ready-to-use templates > Working with a team? See [Team Workflows](Team-Workflows.md) for collaboration ## Script Structure ### Basic Template Every DotRun script should follow this structure: ```bash #!/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 ]]; then echo "Info: $1" fi } # Argument parsing while [[ $# -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 ]]; then error "Missing required argument" 2 fi local arg1="$1" local arg2="${2:-default}" info "Processing $arg1 with $arg2" if [[ "$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 ```bash # 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 ```bash # 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 1. **Always include `### DOC` section** 2. **Start with script name and brief description** 3. **Provide clear usage examples** 4. **Document all options and arguments** 5. **List dependencies and requirements** 6. **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 ```bash # 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 ```bash # File operations if [[ ! -f "$file" ]]; then error "File not found: $file" fi # Directory operations if [[ ! -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 ```bash # Required arguments if [[ $# -lt 1 ]]; then usage error "Missing required argument" 2 fi # Numeric validation if ! [[ "$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" =~ \.\. ]]; then error "Path cannot contain '..': $path" 2 fi # Email validation if ! [[ "$email" =~ ^[^@]+@[^@]+\.[^@]+$ ]]; then error "Invalid email format: $email" 2 fi ``` ### Interactive Confirmation ```bash confirm() { local prompt="${1:-Continue?}" local default="${2:-n}" if [[ "$default" == "y" ]]; then prompt="$prompt [Y/n] " else prompt="$prompt [y/N] " fi read -r -p "$prompt" response response=${response:-$default} [[ "$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: ```bash # In test/test_myscript.sh #!/usr/bin/env bash source "$(dirname "$0")/../bin/myscript" # Test function test_validation() { # Test valid input if validate_email "user@example.com"; 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 ```bash # 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" ]]; then echo "✓ Output test passed" else echo "✗ Output test failed" echo "Got: $output" fi # Verify exit code if [[ "${exit_code:-0}" -eq 0 ]]; then echo "✓ Exit code test passed" else echo "✗ Exit code test failed: $exit_code" fi } ``` ## Performance Optimization ### Best Practices ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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" =~ ^/ ]]; then error "Absolute paths not allowed" fi # Prevent directory traversal real_path=$(realpath "$file_path") if [[ ! "$real_path" =~ ^"$SAFE_DIR" ]]; then error "Access denied: $file_path" fi ``` ### Environment Security ```bash # Clear sensitive variables unset PASSWORD unset API_KEY # Use secure defaults umask 077 # Files created with 600 permissions # Validate environment if [[ -z "${HOME:-}" ]]; then error "HOME environment variable not set" fi ``` ## Next Steps With solid script development practices: 1. **Share with Team**: Learn about [Team Workflows](Team-Workflows.md) 2. **Manage Configuration**: Set up [Configuration Management](Configuration-Management.md) 3. **Create Documentation**: Master the [Documentation System](Documentation-System.md) 4. **Browse Examples**: Study [Example Scripts](Script-Examples.md) 5. **Optimize Workflow**: Explore [Developer Experience](Developer-Experience.md) --- [← User Guide](User-Guide.md) | [Team Workflows →](Team-Workflows.md) | [Top ↑](#script-development)