Error Handling
Utah provides robust error handling mechanisms that make shell scripts more reliable and maintainable.
Try-Catch Blocks
Basic Syntax
try {
  // Code that might fail
  let data: string = fs.readFile("config.json");
  console.log("File loaded successfully");
}
catch {
  // Error handling
  console.log("Failed to load config file");
}
Exception Information
Access error details in catch blocks:
try {
  let result: string = `$(curl -f https://api.example.com/data)`;
}
catch (error) {
  console.log("API call failed: ${error}");
  // Handle the specific error
}
Error Propagation
Function Error Handling
Functions can propagate errors to calling code:
function loadConfig(filename: string): object {
  try {
    let content: string = fs.readFile(filename);
    return json.parse(content);
  }
  catch {
    console.log("Cannot load config from ${filename}");
    throw "Config load failed";
  }
}
try {
  let config: object = loadConfig("app.json");
  console.log("Configuration loaded");
}
catch {
  console.log("Using default configuration");
  // Fallback logic
}
Graceful Degradation
function getServerInfo(): object {
  try {
    let info: string = `$(systemctl status nginx)`;
    return { status: "active", details: info };
  }
  catch {
    console.log("Could not get server status");
    return { status: "unknown", details: "" };
  }
}
Exit Codes and Script Termination
Controlled Exit
script.exitOnError(false);  // Don't exit on first error
try {
  // Critical operation
  let backup: string = `$(rsync -av /data/ /backup/)`;
}
catch {
  console.log("Backup failed - exiting");
  exit(1);
}
Exit Code Conventions
// Success
exit(0);
// General error
exit(1);
// Misuse of shell command
exit(2);
// Permission denied
exit(77);
// Command not found
exit(127);
Error Context and Logging
Structured Error Information
function processFile(filename: string): boolean {
  try {
    if (!fs.exists(filename)) {
      throw "File not found: ${filename}";
    }
    let content: string = fs.readFile(filename);
    if (string.length(content) == 0) {
      throw "File is empty: ${filename}";
    }
    // Process the file
    return true;
  }
  catch (error) {
    console.log("Error processing ${filename}: ${error}");
    return false;
  }
}
Error Logging with Timestamps
function logError(message: string): void {
  let timestamp: string = timer.current();
  console.log("[${timestamp}] ERROR: ${message}");
}
try {
  let data: string = `$(wget -q -O - https://api.service.com/health)`;
}
catch {
  logError("Health check API is unreachable");
}
Validation and Defensive Programming
Input Validation
function deployApplication(environment: string, version: string): void {
  // Validate environment
  let validEnvs: string[] = ["dev", "staging", "prod"];
  if (!array.contains(validEnvs, environment)) {
    throw "Invalid environment: ${environment}";
  }
  // Validate version format
  if (!string.contains(version, ".")) {
    throw "Invalid version format: ${version}";
  }
  console.log("Deploying ${version} to ${environment}");
}
try {
  deployApplication("prod", "1.2.3");
}
catch (error) {
  console.log("Deployment failed: ${error}");
  exit(1);
}
Dependency Checking
function ensureDependencies(): void {
  let requiredTools: string[] = ["docker", "kubectl", "helm"];
  for (let tool: string in requiredTools) {
    if (!os.isInstalled(tool)) {
      throw "Required tool not found: ${tool}";
    }
  }
}
try {
  ensureDependencies();
  console.log("All dependencies are available");
}
catch (error) {
  console.log("Dependency check failed: ${error}");
  console.log("Please install missing tools and try again");
  exit(1);
}
Recovery Strategies
Retry Logic
function retryOperation(maxAttempts: number, operation: string): boolean {
  for (let attempt: number = 1; attempt <= maxAttempts; attempt++) {
    try {
      let result: string = "$(${operation})";
      console.log("Operation succeeded");
      return true;
    }
    catch {
      console.log("Attempt ${attempt} failed");
      if (attempt < maxAttempts) {
        console.log("Retrying in 5 seconds...");
        timer.sleep(5000);
      }
    }
  }
  console.log("All retry attempts failed");
  return false;
}
if (!retryOperation(3, "curl -f https://api.example.com/health")) {
  console.log("Service is unavailable");
  exit(1);
}
Fallback Mechanisms
function getConfiguration(): object {
  // Try primary config source
  try {
    let config: string = fs.readFile("/etc/app/config.json");
    return json.parse(config);
  }
  catch {
    console.log("Primary config not found, trying backup");
  }
  // Try backup config source
  try {
    let config: string = fs.readFile("/tmp/backup-config.json");
    return json.parse(config);
  }
  catch {
    console.log("Backup config not found, using defaults");
  }
  // Use default configuration
  return json.parse('{"timeout": 30, "retries": 3}');
}
Error Handling Patterns
Resource Cleanup
function processWithCleanup(inputFile: string): void {
  let tempFile: string = "/tmp/processing-${timer.current()}";
  try {
    // Create temporary file
    fs.writeFile(tempFile, "processing data");
    // Process data
    let result: string = "$(process-data ${inputFile} ${tempFile})";
    console.log("Processing completed");
  }
  catch (error) {
    console.log("Processing failed: ${error}");
  }
  finally {
    // Cleanup temporary file
    if (fs.exists(tempFile)) {
      fs.remove(tempFile);
    }
  }
}
Circuit Breaker Pattern
let failureCount: number = 0;
const MAX_FAILURES: number = 5;
function callExternalService(): boolean {
  if (failureCount >= MAX_FAILURES) {
    console.log("Circuit breaker: service calls suspended");
    return false;
  }
  try {
    let response: string = `$(curl -f --connect-timeout 10 https://api.service.com)`;
    failureCount = 0;  // Reset on success
    return true;
  }
  catch {
    failureCount++;
    console.log("Service call failed (${failureCount}/${MAX_FAILURES})");
    return false;
  }
}
Script-Level Error Configuration
Error Handling Modes
// Exit immediately on any error
script.exitOnError(true);
// Continue on errors (default behavior)
script.exitOnError(false);
// Enable debug mode for error tracking
script.enableDebug(true);
Global Error Handler
script.description("Data processing with comprehensive error handling");
function handleError(message: string): void {
  let timestamp: string = timer.current();
  let logFile: string = "/var/log/script-errors.log";
  fs.appendFile(logFile, "[${timestamp}] ${message}\n");
  console.log("ERROR: ${message}");
}
// Use consistent error handling throughout the script
try {
  // Main script logic
  processData();
}
catch (error) {
  handleError("Script execution failed: ${error}");
  exit(1);
}
Best Practices
- Always handle expected errors - Don't let scripts fail unexpectedly
 - Use specific error messages - Include context about what failed
 - Implement fallback strategies - Provide alternative paths when possible
 - Log errors appropriately - Include timestamps and relevant details
 - Test error paths - Ensure error handling works as expected
 - Clean up resources - Use finally blocks or explicit cleanup
 - Set appropriate exit codes - Follow standard conventions
 - Validate inputs early - Catch errors before they cause damage
 
Common Error Scenarios
File Operations
function safeFileOperation(filename: string): boolean {
  try {
    if (!fs.exists(filename)) {
      console.log("File not found: ${filename}");
      return false;
    }
    let content: string = fs.readFile(filename);
    // Process content
    return true;
  }
  catch (error) {
    console.log("File operation failed: ${error}");
    return false;
  }
}
Network Operations
function fetchData(url: string): string {
  try {
    return "$(curl -f --max-time 30 "${url}")";
  }
  catch {
    throw "Failed to fetch data from ${url}";
  }
}
System Commands
function runSystemCommand(command: string): boolean {
  try {
    let output: string = "$(${command})";
    console.log("Command completed successfully");
    return true;
  }
  catch (error) {
    console.log("Command failed: ${command}");
    console.log("Error: ${error}");
    return false;
  }
}
Error handling in Utah makes shell scripts more robust and production-ready, enabling graceful failure recovery and detailed error reporting.