-
-
Notifications
You must be signed in to change notification settings - Fork 0
Script Development
Joao Palma edited this page Oct 27, 2025
·
3 revisions
← User Guide | Home | Team Workflows →
- 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
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 ]]; 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 "$@"# 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 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"- 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
- Script name and description
- Usage syntax
- At least one example
- Options (if any)
- Arguments (if any)
- Dependencies
- Environment variables
- Exit codes
- Notes/warnings
- Author information
- Version history
- Related scripts
- External references
# 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# 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# 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
ficonfirm() {
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"
fiCreate 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 "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# 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
}# 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)# 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# Escape special characters
sanitize() {
printf '%q' "$1"
}
# Use sanitized input
user_input=$(sanitize "$1")
eval "command $user_input"
# Better: avoid eval entirely
command "$1"# 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# 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"
fiWith 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