Control Flow
Utah provides modern control flow structures that compile to efficient bash code. All control structures use familiar TypeScript-like syntax with proper type checking.
Conditional Statements
If Statements
Basic if statements with comparison operators:
let temperature: number = 25;
if (temperature > 30) {
console.log("It's hot outside");
}
Generated Bash:
temperature=25
if [ "${temperature}" -gt 30 ]; then
echo "It's hot outside"
fi
If-Else Statements
Full conditional branching:
let score: number = 85;
if (score >= 90) {
console.log("Excellent!");
} else if (score >= 80) {
console.log("Good job!");
} else if (score >= 70) {
console.log("Not bad");
} else {
console.log("Keep trying");
}
String Comparisons
String equality and pattern matching:
let environment: string = "production";
if (environment == "production") {
console.log("Running in production mode");
} else if (environment == "development") {
console.log("Running in development mode");
} else {
console.log("Unknown environment");
}
Boolean Logic
Combine conditions with logical operators:
let hasPermission: boolean = true;
let isLoggedIn: boolean = true;
let attempts: number = 3;
if (isLoggedIn && hasPermission) {
console.log("Access granted");
} else if (!isLoggedIn) {
console.log("Please log in first");
} else if (attempts < 5) {
console.log("Permission denied, but you can try again");
} else {
console.log("Too many attempts");
}
Switch Statements
Switch statements provide clean multi-way branching:
Basic Switch
let action: string = "create";
switch (action) {
case "create":
console.log("Creating new file");
break;
case "delete":
console.log("Deleting file");
break;
case "update":
console.log("Updating file");
break;
default:
console.log("Unknown action");
}
Generated Bash:
action="create"
case "${action}" in
"create")
echo "Creating new file"
;;
"delete")
echo "Deleting file"
;;
"update")
echo "Updating file"
;;
*)
echo "Unknown action"
;;
esac
Switch with Fall-through
Omit break
statements for fall-through behavior:
let fileType: string = "jpg";
switch (fileType) {
case "jpg":
case "jpeg":
console.log("JPEG image format");
break;
case "png":
case "gif":
console.log("Lossless image format");
break;
default:
console.log("Unknown image format");
}
Loop Statements
For Loops
Traditional C-style for loops:
for (let i: number = 0; i < 10; i++) {
console.log("Iteration ${i}");
}
Generated Bash:
for ((i=0; i<10; i++)); do
echo "Iteration ${i}"
done
For-In Loops
Iterate over arrays and strings:
let fruits: string[] = ["apple", "banana", "orange"];
for (let fruit: string in fruits) {
console.log("Processing: ${fruit}");
}
Generated Bash:
fruits=("apple" "banana" "orange")
for fruit in "${fruits[@]}"; do
echo "Processing: ${fruit}"
done
While Loops
Conditional iteration:
let count: number = 0;
while (count < 5) {
console.log("Count is: ${count}");
count++;
}
Generated Bash:
count=0
while [ "${count}" -lt 5 ]; do
echo "Count is: ${count}"
((count++))
done
Loop Control
Use break
to exit loops early:
let numbers: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for (let num: number in numbers) {
if (num > 5) {
break;
}
console.log("Number: ${num}");
}
Comparison Operators
Utah supports various comparison operators:
Numeric Comparisons
let a: number = 10;
let b: number = 20;
if (a == b) { console.log("Equal"); }
if (a != b) { console.log("Not equal"); }
if (a < b) { console.log("Less than"); }
if (a <= b) { console.log("Less than or equal"); }
if (a > b) { console.log("Greater than"); }
if (a >= b) { console.log("Greater than or equal"); }
String Equality Examples
let name1: string = "Alice";
let name2: string = "Bob";
if (name1 == name2) { console.log("Same name"); }
if (name1 != name2) { console.log("Different names"); }
Boolean Operations
let isActive: boolean = true;
let isVerified: boolean = false;
if (isActive) { console.log("Active"); }
if (!isVerified) { console.log("Not verified"); }
if (isActive && isVerified) { console.log("Active and verified"); }
if (isActive || isVerified) { console.log("Active or verified"); }
Ternary Operator
Conditional expressions for simple branching:
let age: number = 20;
let status: string = (age >= 18) ? "adult" : "minor";
console.log("Status: ${status}");
Generated Bash:
age=20
if [ "${age}" -ge 18 ]; then
status="adult"
else
status="minor"
fi
echo "Status: ${status}"
Nested Conditions
Complex conditional logic:
let userType: string = "admin";
let permissions: string[] = ["read", "write", "delete"];
if (userType == "admin") {
if (array.contains(permissions, "delete")) {
console.log("Admin with delete permission");
} else {
console.log("Admin without delete permission");
}
} else if (userType == "user") {
if (array.contains(permissions, "write")) {
console.log("User with write permission");
} else {
console.log("Read-only user");
}
} else {
console.log("Unknown user type");
}
Practical Examples
File Processing
let files: string[] = ["config.json", "data.txt", "backup.sql"];
for (let file: string in files) {
if (fs.exists(file)) {
let extension: string = fs.extension(file);
switch (extension) {
case "json":
console.log("Processing JSON file: ${file}");
// Process JSON
break;
case "txt":
console.log("Processing text file: ${file}");
// Process text
break;
case "sql":
console.log("Processing SQL file: ${file}");
// Process SQL
break;
default:
console.log("Unknown file type: ${file}");
}
} else {
console.log("File not found: ${file}");
}
}
User Input Validation
args.define("--port", "-p", "Server port", "number", false, 8080);
args.define("--environment", "-e", "Environment", "string", false, "development");
let port: number = args.get("--port");
let env: string = args.get("--environment");
// Validate port
if (port < 1 || port > 65535) {
console.log("Error: Port must be between 1 and 65535");
exit(1);
}
// Validate environment
let validEnvs: string[] = ["development", "staging", "production"];
if (!array.contains(validEnvs, env)) {
console.log("Error: Invalid environment. Must be one of: development, staging, production");
exit(1);
}
console.log("Starting server on port ${port} in ${env} mode");
Service Health Check
let services: string[] = ["nginx", "mysql", "redis"];
let healthyServices: string[] = [];
let unhealthyServices: string[] = [];
for (let service: string in services) {
console.log("Checking ${service}...");
if (os.isInstalled("systemctl")) {
// Use systemctl for service status
let status: string = "$(systemctl is-active ${service})";
if (status == "active") {
healthyServices[healthyServices.length] = service;
console.log("✓ ${service} is running");
} else {
unhealthyServices[unhealthyServices.length] = service;
console.log("✗ ${service} is not running");
}
} else {
console.log("Cannot check ${service}: systemctl not available");
}
}
console.log(`\nHealth Check Summary:`);
console.log("Healthy services: ${healthyServices.length}");
console.log("Unhealthy services: ${unhealthyServices.length}");
if (unhealthyServices.length > 0) {
console.log("Services need attention:");
for (let service: string in unhealthyServices) {
console.log(" - ${service}");
}
exit(1);
} else {
console.log("All services are healthy!");
}
Configuration Management
let configFile: string = "app.json";
let defaultConfig: string = "{\"debug\": false, \"port\": 3000, \"timeout\": 30}";
// Load configuration
let config: object;
if (fs.exists(configFile)) {
try {
let configData: string = fs.readFile(configFile);
if (json.isValid(configData)) {
config = json.parse(configData);
console.log("Configuration loaded from file");
} else {
console.log("Invalid JSON in config file, using defaults");
config = json.parse(defaultConfig);
}
}
catch {
console.log("Error reading config file, using defaults");
config = json.parse(defaultConfig);
}
} else {
console.log("Config file not found, using defaults");
config = json.parse(defaultConfig);
}
// Extract and validate configuration values
let debug: boolean = json.getBool(config, ".debug");
let port: number = json.getNumber(config, ".port");
let timeout: number = json.getNumber(config, ".timeout");
// Validate values
if (port < 1 || port > 65535) {
console.log("Warning: Invalid port in config, using default 3000");
port = 3000;
}
if (timeout < 1 || timeout > 300) {
console.log("Warning: Invalid timeout in config, using default 30");
timeout = 30;
}
console.log("Starting with config: debug=${debug}, port=${port}, timeout=${timeout}");
Best Practices
1. Use Explicit Type Comparisons
// Good - explicit type checking
if (count == 0) {
console.log("No items");
}
// Good - boolean variables
if (isEnabled) {
console.log("Feature enabled");
}
// Avoid - implicit truthiness
if (count) { // This might not work as expected
console.log("Has items");
}
2. Validate Input Early
function processFile(filename: string): void {
// Validate input first
if (filename == "") {
console.log("Error: Filename cannot be empty");
return;
}
if (!fs.exists(filename)) {
console.log("Error: File not found: ${filename}");
return;
}
// Process the file
console.log("Processing: ${filename}");
}
3. Use Switch for Multiple Conditions
// Good - clear and maintainable
switch (operation) {
case "create":
createFile();
break;
case "delete":
deleteFile();
break;
case "update":
updateFile();
break;
default:
console.log("Unknown operation");
}
// Avoid - nested if-else for many conditions
if (operation == "create") {
createFile();
} else if (operation == "delete") {
deleteFile();
} else if (operation == "update") {
updateFile();
} else {
console.log("Unknown operation");
}
4. Handle Edge Cases
let items: string[] = ["a", "b", "c"];
// Check array is not empty before processing
if (items.length > 0) {
for (let item: string in items) {
console.log("Processing: ${item}");
}
} else {
console.log("No items to process");
}
Common Patterns
Early Return Pattern
function validateConfig(config: object): boolean {
if (!json.has(config, ".version")) {
console.log("Missing version in config");
return false;
}
if (!json.has(config, ".database")) {
console.log("Missing database config");
return false;
}
let version: string = json.get(config, ".version");
if (version == "") {
console.log("Empty version in config");
return false;
}
console.log("Config validation passed");
return true;
}
Guard Clauses
function backupDatabase(dbName: string): void {
// Guard clauses at the top
if (dbName == "") {
console.log("Database name required");
return;
}
if (!os.isInstalled("mysqldump")) {
console.log("mysqldump not available");
return;
}
if (!fs.exists("/var/backups")) {
console.log("Backup directory not found");
return;
}
// Main logic here
console.log("Backing up database: ${dbName}");
}
State Machine Pattern
let state: string = "init";
let running: boolean = true;
while (running) {
switch (state) {
case "init":
console.log("Initializing...");
state = "loading";
break;
case "loading":
console.log("Loading data...");
state = "processing";
break;
case "processing":
console.log("Processing data...");
state = "complete";
break;
case "complete":
console.log("Processing complete");
running = false;
break;
default:
console.log("Unknown state");
running = false;
}
}
Control flow is the foundation of program logic in Utah. These structures provide the building blocks for creating sophisticated automation scripts while maintaining the readability and type safety that Utah is designed to provide.